» Publishers, Monetize your RSS feeds with FeedShow: More infos (Show/Hide Ads)
I got a few notes about my signature line on my previous post ‘Program Manager, Hyper-V’… Yep I am now a program manager on the Hyper-V team – I moved over from the Hyper-V test team a few weeks ago and now work on Ben Armstrong’s (virtual pc guy) team. I have spent almost my entire career in test, over 11 years including over nine years on the virtualization team, first on Virtual PC then Virtual Server and most recently on Hyper-V. I loved being a tester and the Hyper-V test team is among the best in the industry in my opinion but it was time for a new challenge. Microsoft is an amazing company to work for with excellent managers that enabled me for many years to walk a line between the roles and responsibilities of a tester and those of a PM and over the past few years I’ve deviated further and further toward the PM side so this was a pretty natural move to make. There aren’t many companies that allow you to craft your own role and facilitate your growth in that role for years, Microsoft is an amazing company to work for…
-Taylor Brown
-Program Manager, Hyper-V
Some folks have asked creating there own VMConnect application – it’s actually really easy. Here’s a very basic example of how to create your own VMConnect application. You would likely want to add some error handling, change the window sizing, maybe a WMI call to retrieve the name of the VM vs the ID but with Hyper-V’s WMI API you can do all of that and more after all now it’s your VMConnect application.
Steps To Create Basic Application
- Create a new C# Windows Forms project In Visual Studio
- Add a COM reference to mstscax.dll (Microsoft Terminal Service Active Client 1.0 Type Library)
- Add a new RDP Client Control to the form
- From the form designer right click in the toolbox and select Choose Toolbox Item
- Select COM Components and find and check “Microsoft RDP Client Control – version 9
- From All Windows Forms (generally at the bottom) find the “Microsoft RDP Client Control – version 9” option and drag it to the form
- Add a text box for the VM ID and a connect button to the form
- For the connect button’s click event specify the following basic code
private void ConnectButton_Click(object sender, EventArgs e) { //specify the server the VM is running on axMsRdpClient8NotSafeForScripting1.Server = "localhost"; //enable relative mouse mode and smart sizing axMsRdpClient8NotSafeForScripting1.AdvancedSettings7.RelativeMouseMode = true; axMsRdpClient8NotSafeForScripting1.AdvancedSettings7.SmartSizing = true; //specify the authentication service - this is required and set the authentication level axMsRdpClient8NotSafeForScripting1.AdvancedSettings7.AuthenticationServiceClass = "Microsoft Virtual Console Service"; axMsRdpClient8NotSafeForScripting1.AdvancedSettings6.AuthenticationLevel = 0; //retrieve the activeX control and enable CredSSP and disable NegotiateSecurity MSTSCLib.IMsRdpClientNonScriptable3 Ocx = (MSTSCLib.IMsRdpClientNonScriptable3)axMsRdpClient8NotSafeForScripting1.GetOcx(); Ocx.EnableCredSspSupport = true; Ocx.NegotiateSecurityLayer = false; //retrieve the activeX control and disable CredentialsDelegation MSTSCLib.IMsRdpExtendedSettings rdpExtendedSettings = (MSTSCLib.IMsRdpExtendedSettings)axMsRdpClient8NotSafeForScripting1.GetOcx(); object True = true; rdpExtendedSettings.set_Property("DisableCredentialsDelegation", ref True); //set the RDPPort and set the PCB string to the VM's ID axMsRdpClient8NotSafeForScripting1.AdvancedSettings2.RDPPort = 2179; axMsRdpClient8NotSafeForScripting1.AdvancedSettings7.PCB = vmIDTextBox.Text; //connect to the VM axMsRdpClient8NotSafeForScripting1.Connect(); }
Step 1 - Create a new C# Windows Forms project In Visual Studio

Step 2 - Add a COM reference to mstscax.dll (Microsoft Terminal Service Active Client 1.0 Type Library)

Step 3.1 - Add a new RDP Client Control to the form, From the form designer right click in the toolbox and select Choose Toolbox Item

Step 3.2 - Add a new RDP Client Control to the form, Select COM Components and find and check “Microsoft RDP Client Control – version 9

Step 3.2 - Add a new RDP Client Control to the form, From All Windows Forms (generally at the bottom) find the “Microsoft RDP Client Control – version 9” option and drag it to the form

Step 4 - Add a text box for the VM ID and a connect button to the form

Step 5 - For the connect button’s click event specify the following basic code

Your new solution in action!
-taylorb
Program Manager, Hyper-V
I was reviewing my blog statistics the other day and noticed that a number of my old posts going all the way back to 2008 are still getting a lot of traffic. A lot has changed since 2008 and while in most cases the information has stayed the same in many cases there are new or better ways to accomplish the same thing. So over the next few weeks my goal is to update some of the old posts that still get a lot of traffic. In some cases I will just edit the old post and add appropriate information – in other cases I will make a brand new post with new information and link between them.
Hopefully this is helpful to both the folks who stumble across the old posts as well as folks that read the new ones…
-taylorb
This is effectively and update from one my very old posts Hyper-V WMI: KVP Exchange aka Data Exchange (Retrieving and Modifying Parent/Host KVP’s) which still receives a lot of traffic. The same code from back in 2008 still works today even on Windows Server 2012 – however with Windows Server 2012 we have introduced a new version of our WMI API’s (WMI v2) and for future releases the old code may stop working as we have not committed to (or not to) supporting the old version 1 API’s. In the case of the Msvm_KvpExchangeComponent class not much has changed – literally the only difference between the version 1 sample and version 2 sample below is the ‘\v2’ in the namespace, this is not true for most other components.
Adding/Modifying Key Value Pairs From A Guest
Adding values from within the guest is as simple as creating a new registry value under the HKLM\Software\Microsoft\Virtual Machine\Guest section of the registry. You do have to be an administrator in the VM in order to access this section of the registry. Retreating the value from the host or a remote machine with permissions on the host is just a WMI query away.
Adding A New Key Value Pair In The Guest
$regPath = "HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest“
Set-ItemProperty -Path $regPath -Name "Status" -Value “Ready" -Type String
Modifying the value is as easy as just changing the value above…
Reading The Value From The Host – Using WMI Version 2 Namespace
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_ComputerSystem -Filter {ElementName = 'Vm1'}
$vm.GetRelated("Msvm_KvpExchangeComponent").GuestExchangeItems | % { `
$GuestExchangeItemXml = ([XML]$_).SelectSingleNode(`
"/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text() = 'Status']")
if ($GuestExchangeItemXml -ne $null)
{
$GuestExchangeItemXml.SelectSingleNode(`
"/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()").Value
}
}
Reading The Value From The Host – Using WMI Version 1 Namespace
$vm = Get-WmiObject -Namespace root\virtualization -Class `
Msvm_ComputerSystem -Filter {ElementName = 'Vm1'}
$vm.GetRelated("Msvm_KvpExchangeComponent").GuestExchangeItems | % { `
$GuestExchangeItemXml = ([XML]$_).SelectSingleNode(`
"/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text() = 'Status']")
if ($GuestExchangeItemXml -ne $null)
{
$GuestExchangeItemXml.SelectSingleNode(`
"/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()").Value
}
}
Adding/Modifying Key Value Pairs From A Host
To add a key value pair from the host you must get the instance of the management service as well as the VM. You must also create a new instance of the Msvm_KvpExchangeDataItem class – I build the path to this class using the instance of the management service such that if the script is running remotely it will connect to the right server. Once you have the new instance of the class you just need to specify the Name, Data and Source parameters (source must be 0). Then just call the AddKvpItems Method providing the instance of the VM and the instance of the created class.
Querying for key value pairs created from the host is very similar to ones from the guest except that you have to go though one more association to get to the Msvm_KvpExchangeComponentSettingsData class other than that it’s pretty much the same process. Modifying and deleting values is almost the same as creating them – you just specify the same Key name as the value you wish to update/delete and call the Modify or Delete method.
Note that the example below utilizes the V2 namespace, if you are using Windows Server 2008 or R2 you can remove the \v2 and the script will work just fine.
Adding A New Key Value Pair
$VmMgmt = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_VirtualSystemManagementService
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_ComputerSystem -Filter {ElementName='VM1'}
$kvpDataItem = ([WMIClass][String]::Format("\\{0}\{1}:{2}", `
$VmMgmt.ClassPath.Server, `
$VmMgmt.ClassPath.NamespacePath, `
"Msvm_KvpExchangeDataItem")).CreateInstance()
$kvpDataItem.Name = "Name"
$kvpDataItem.Data = "Data"
$kvpDataItem.Source = 0
$VmMgmt.AddKvpItems($Vm, $kvpDataItem.PSBase.GetText(1))
Querying For Key Value Pairs In The Guest
$regPath = "HKLM:\SOFTWARE\Microsoft\Virtual Machine\External"
Get-ItemProperty -Path $regPath -Name "Name"
Querying For Key Value Pairs On The Host
$VmMgmt = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_VirtualSystemManagementService
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_ComputerSystem -Filter {ElementName='VM1'}
($vm.GetRelated("Msvm_KvpExchangeComponent")[0] `
).GetRelated("Msvm_KvpExchangeComponentSettingData").HostExchangeItems | % { `
$GuestExchangeItemXml = ([XML]$_).SelectSingleNode(`
"/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text() = 'Name2']")
if ($GuestExchangeItemXml -ne $null)
{
$GuestExchangeItemXml.SelectSingleNode(`
"/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()").Value
}
}
Modifying Key Value Pairs
$VmMgmt = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_VirtualSystemManagementService
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_ComputerSystem -Filter {ElementName='VM1'}
$kvpDataItem = ([WMIClass][String]::Format("\\{0}\{1}:{2}", `
$VmMgmt.ClassPath.Server, `
$VmMgmt.ClassPath.NamespacePath, `
"Msvm_KvpExchangeDataItem")).CreateInstance()
$kvpDataItem.Name = "Name"
$kvpDataItem.Data = "Data2"
$kvpDataItem.Source = 0
$VmMgmt.ModifyKvpItems($Vm, $kvpDataItem.PSBase.GetText(1))
Removing Key Value Pairs
$VmMgmt = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_VirtualSystemManagementService
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_ComputerSystem -Filter {ElementName='VM1'}
$kvpDataItem = ([WMIClass][String]::Format("\\{0}\{1}:{2}", `
$VmMgmt.ClassPath.Server, `
$VmMgmt.ClassPath.NamespacePath, `
"Msvm_KvpExchangeDataItem")).CreateInstance()
$kvpDataItem.Name = "Name"
$kvpDataItem.Data = [String]::Empty
$kvpDataItem.Source = 0
$VmMgmt.RemoveKvpItems($Vm, $kvpDataItem.PSBase.GetText(1))
-taylorb
This post is to update an old from one 2008 Hyper-V WMI Using PowerShell Scripts – Part 3 (KVP's - Guest OS Version) – in reviewing that post the most coming inquiry was looking for the guests IP address, well we got that for you in PowerShell now… With the introduction of Windows Server 2012 Hyper-V now includes 164 different PowerShell cmdlet’s. On of those is Get-VMNetworkAdapter and one of the properties we included in that cmdlet is the IPAddress that network adapter has assigned to it. We retrieve that information using the tried and true key value pair integration component.
PowerShell Example
PS C:\> (Get-VMNetworkAdapter -VMName VM1).IpAddresses
192.168.0.104
fe80::3114:f7d4:4561:9ea2
2001:1234:a:2:7891:f7d4:4561:9ea2
WMI Example Querying The Key Value Pair (KVP) Integration Component
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class `
Msvm_ComputerSystem -Filter {ElementName='VM1'}
$vm.GetRelated("Msvm_KvpExchangeComponent").GuestIntrinsicExchangeItems | % { `
$GuestExchangeItemXml = ([XML]$_).SelectSingleNode(`
"/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text()='NetworkAddressIPv4']")
if ($GuestExchangeItemXml -ne $null)
{
$GuestExchangeItemXml.SelectSingleNode(`
"/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()").Value
}
}
Other Key Value Pair’s Provided
| KVP Name | Sample Value |
| FullyQualifiedDomainName | vm1.contoso.com |
| OSName | Windows Server 2008 R2 Enterprise |
| OSVersion | 6.1.7601 |
| CSDVersion | Service Pack 1 |
| OSMajorVersion | 6 |
| OSMinorVersion | 1 |
| OSBuildNumber | 7601 |
| OSPlatformId | 2 |
| ServicePackMajor | 1 |
| ServicePackMinor | 0 |
| SuiteMask | 274 |
| ProductType | 3 |
| OSVendor | 1 |
| OSSignature | 4 |
| OSEditionId | 10 |
| ProcessorArchitecture | 9 |
| IntegrationServicesVersion | 6.2.9200.16433 |
| NetworkAddressIPv4 | 192.168.0.123 |
| NetworkAddressIPv6 | fe80::3114:f7d4:4561:9ea2;2001:1234:a:2:7891:f7d4:4561:9ea2 |
| RDPAddressIPv4 | 192.168.0.123 |
| RDPAddressIPv6 | fe80::3114:f7d4:4561:9ea2;2001:1234:a:2:7891:f7d4:4561:9ea2 |
-taylorb

In my post yesterday (Remote Administration Without Constrained Delegation Using PrincipalsAllowedToDelegateToAccount) I showed how to utilize the new resource-based Kerberos constrained delegation to configure remote administration with Hyper-V over SMB. The other place that Hyper-V requires constrained delegation for remote management is for standalone live migration as well as for storage migration. The good news is that that the same functionality works for these scenarios as well.
Basic Configuration
In this example below we are just getting the active directory computer object for the destination Hyper-V server and then setting the PrincipalsAllowedToDelegateToAccount with the active directory computer object of the source Hyper-V server. In the background this sets msDS-AllowedToActOnBehalfOfOtherIdentity property to an NT Security Descriptor for the Hyper-V server’s computer account. If you wanted to live migration both directions (which typically you would) than you would need to call this a second time specifying the source/destination with the other direction.
Get-ADComputer -Filter {Name -Like "DestinationHVServer"} | Set-ADComputer -PrincipalsAllowedToDelegateToAccount (Get-ADComputer -Filter {Name -Like "SourceHVServer"})
Advanced Configuration
The function below does a much more complete job of configuration the PrincipalsAllowedToDelegateToAccount. It adds the appropriate entries for both migration directions as well as it read’s in existing entries preserving previously configured options.
function Add-Hyper-V-LiveMigrationHosts
{
Param
(
[String[]]
$HyperVHosts
)
$HvAdObj = @()
foreach ($HvHost in $HyperVHosts)
{
$HvAdObj+= Get-ADComputer -Filter {Name -Like $HvHost} `
-Properties msDS-AllowedToActOnBehalfOfOtherIdentity
}
for ($destinationCtr = 0; $destinationCtr -lt $HvAdObj.Count; $destinationCtr++)
{
for ($sourceCtr = 0; $sourceCtr -lt $HvAdObj.Count; $sourceCtr++)
{
if ($sourceCtr -ne $destinationCtr)
{
$deligationPrinciples = @()
foreach ($AllowedAccount in `
$HvAdObj[$destinationCtr]."msDS-AllowedToActOnBehalfOfOtherIdentity".Access)
{
$samAccountName = $AllowedAccount.IdentityReference.Value
$samAccountName = $samAccountName.Remove(0, ($samAccountName.IndexOf("\")+1))
$deligationPrinciples+=Get-ADComputer -Filter `
{SamAccountName -Like $samAccountName}
}
$deligationPrinciples += $HvAdObj[$sourceCtr]
$HvAdObj[$destinationCtr] | `
Set-ADComputer -PrincipalsAllowedToDelegateToAccount $deligationPrinciples
}
}
}
}
-taylorb
You may have read my previous posts on remote administration and constrained delegation (Enabling Hyper-V Remote Management - Configuring Constrained Delegation For SMB and Highly Available SMB or Enabling Hyper-V Remote Management - Configuring Constrained Delegation For Non-Clustered Live Migration or Scripting Constrained Delegation Settings) one of biggest challenges people voice over constrained delegation is that it requires active directory administrator permissions. In Windows Server 2012 the Active Directory team introduced resource-based Kerberos constrained delegation this feature provides several significant advantages over traditional constrained delegation. Specifically it no longer requires a domain administrator to configure it and it works across domain/forest trusts removing the requirement that both servers reside in the same domain and it’s really easy to configure!
One thing to keep in mind you still have to configure the share permissions and file/folder permissions properly (see the Create SMB Share For Virtual Machines section of Enabling Hyper-V Remote Management - Configuring Constrained Delegation For SMB and Highly Available SMB).
Basic Configuration
In this example below we are just getting the active directory computer object for the fileserver and then setting the PrincipalsAllowedToDelegateToAccount with the active directory computer object of the Hyper-V server. In the background this sets msDS-AllowedToActOnBehalfOfOtherIdentity property to an NT Security Descriptor for the Hyper-V server’s computer account.
Get-ADComputer -Filter {Name -Like "FileServer"} | Set-ADComputer -PrincipalsAllowedToDelegateToAccount (Get-ADComputer -Filter {Name -Like "HyperVServer"})
Advanced Configuration
The function below does a much more complete job of adding the Hyper-V server to the File Server’s PrincipalsAllowedToDelegateToAccount, specifically it read’s in existing entries building an array of allowed servers.
function Add-PrincipalsAllowedToDelegateToAccount
{
Param
(
[String]
$FileServer,
[String]
$HyperVServer
)
$deligationPrinciples = @()
$fsAD = Get-ADComputer -Filter {Name -Like $FileServer} `
-Properties msDS-AllowedToActOnBehalfOfOtherIdentity
foreach ($AllowedAccount in $fsAD."msDS-AllowedToActOnBehalfOfOtherIdentity".Access)
{
$samAccountName = $AllowedAccount.IdentityReference.Value
$samAccountName = $samAccountName.Remove(0, ($samAccountName.IndexOf("\")+1))
$deligationPrinciples+=Get-ADComputer -Filter {SamAccountName -Like $samAccountName}
}
$deligationPrinciples += Get-ADComputer -Identity $HyperVServer
$fsAD | Set-ADComputer -PrincipalsAllowedToDelegateToAccount $deligationPrinciples
}
I hope this helps everyone, it sure has helped my team!
-taylorb
With the release of Windows Server 2012 we introduced a new virtual hard disk format known as VHDX. This new format provided the foundation for a number of features and performance enhancements. One such feature is the ability to independently expand differencing virtual hard disks without impacting the base or parent hard disk or any siblings. From the UI you can simply select ‘Edit Disk…’ input the path of the virtual hard disk and select expand from the options provided. Similarly you can utilize the new Resize-VHD cmdlet in PowerShell providing the path to the disk and the new size in bytes for example Resize-VHD -Path "C:\VHDs\Child.vhdx" -SizeBytes 80GB.
-taylorb
One of the most commonly requested features from customers using dynamic memory was the ability to see within the guest what the maximum memory was configured for. So with Windows Server 2012 Hyper-V servers and with Windows Server 2012 or Windows 8 guests we added the maximum memory to the task manager.
-taylorb
A few times I’ve had people ask me about how they automate moving VM’s off of a cluster node based on some type of detected failure or performance issue. In general my answer is to utilize System Center specifically Operations Manager – many customers I work with take this advice and leverage the management packs and broad insight that Operations Manager provides to create automatic Diagnostics and Recoveries tied to Monitors. But for other customers this is to heavy handed or they
don’t have the resources to leverage Operations Manager. So are they out of luck? Not really – there are two commonly overlooked feature in Windows, the Task Scheduler and Performance Monitor.
Within the Windows Task Scheduler you can create a task which is automatically executed every time an event is generated the task can then run a script. For example you can create a task which is tied to the event that is logged when a network adapter that is connected to a virtual switch has it’s network cable disconnected, when that event is detected you can have a script that pauses the cluster node and migrates all of the VM’s off of it. Similarly you can configure the Windows Performance Monitor to trigger a scheduled task when a performance threshold has been exceeded. What actions these tasks take is completely up to your imagination and the how complex you want to make the scripts they execute.
The biggest draw back to this method vs using something like Operations Manager is that magic term ‘centralization’ i.e. these tasks have to be configured on each server and any changes have to be replicated to each server. Additionally either the scripts have to be flexible enough work across varying server configurations (for example one NIC or two, teamed or not) or you have to configure the script/task individually for each server. However going back to our original statement this is really an answer for environments where Operations Manger is too heavy thus limited number of servers.
I’m going to provide a few basic examples just to get you thinking and as a proof of concept. Specifically I am going to focus on the disconnected network adapter and some overall performance metrics (CPU utilization, Disk throughput and Network throughput). We’ll start with the disconnected network adapter.
Creating a Task Triggered By Events
Creating a Performance Monitor Alert To Trigger a Task
More Robust Disconnected NIC Script
This is a more robust script for the disconnected host NIC – what the script does is identifies which NIC is disconnected and any VM’s that are impacted by that NIC being disconnected. It then checks all of the other cluster nodes to see if there NIC is also disconnected before migrating only the effected VM’s.
$NicDisconnectedLog = [String]::Empty
$NicDisconnectedLog += "NIC Adapter Disconnection Detected - Attempting to Move VMs`n"
$Event = Get-EventLog -LogName System -Source Microsoft-Windows-Hyper-V-VmSwitch `
-InstanceId 24 -Newest 1
$NicDisconnectedLog += ("Disconnected NIC Description: " + `
$Event.ReplacementStrings[3] + "`n")
$Switch = Get-VMSwitch -SwitchType External | Where-Object `
{$_.NetAdapterInterfaceDescription -eq $Event.ReplacementStrings[3]}
$NicDisconnectedLog += ("Associated Switch Name: " + $Switch.Name + "`n")
$NicDisconnectedLog += ("Determining Available Cluster Nodes`n")
$AvalableClusterNodes = @()
foreach ($clusterNode in (Get-ClusterNode | Where-Object {$_.State -eq "Up"}))
{
$destSwitch = Get-VMSwitch -Name $Switch.Name -ComputerName $clusterNode.Name
if ((Get-NetAdapter -InterfaceDescription $destSwitch.NetAdapterInterfaceDescription `
-CimSession $clusterNode.Name).Status -eq "Up")
{
$AvalableClusterNodes+= $clusterNode
$NicDisconnectedLog += ("Node: " + $clusterNode.Name + " is available.`n")
}
else
{
$NicDisconnectedLog += ("Node: " + $clusterNode.Name + `
" also has a disconnected switch.`n")
}
}
if ($AvalableClusterNodes.Count -eq 0)
{
$NicDisconnectedLog += ("No Available Cluster Nodes - Exiting`n")
}
else
{
$NicDisconnectedLog += ("Determining Effected VM's`n")
$EffectedNics = Get-VMNetworkAdapter -VMName * | Where-Object `
{$_.SwitchName -eq $Switch.Name}
$VMsToMove = @()
foreach ($Nic in $EffectedNics)
{
if (!$VMsToMove.Contains($Nic.VMId))
{
$NicDisconnectedLog += ("VM: " + $Nic.VMName + "ID:(" + `
$Nic.VMId + ")" +" is effected - preparing to move.`n")
$VMsToMove += $Nic.VMId
}
}
$NicDisconnectedLog += ("Preparing to Move Effected VM's`n")
for ($MoveCounter = $VMsToMove.Count; $MoveCounter -gt 0; $MoveCounter--)
{
$attemptCount = 0
do {
$destinationNode = $AvalableClusterNodes[(($MoveCounter+$attemptCount) `
% $AvalableClusterNodes.Count)]
$NicDisconnectedLog += ("Moving VM with ID: " + $VMsToMove[($MoveCounter-1)] `
+ " to node: " + $destinationNode.Name + "`n")
$result = Move-ClusterVirtualMachineRole -VMId $VMsToMove[($MoveCounter-1)] `
-Node $destinationNode.Name -MigrationType Live
$attemptCount++
}
while (($result.OwnerNode -ne $destinationNode.Name) `
-and ($attemptCount -lt $AvalableClusterNodes.Count))
}
}
$NicDisconnectedLog | Out-File "C:\Scripts\NicLog.txt"
-taylorb
With all of the new PowerShell cmdlet’s in available for Hyper-V in Window's Server 2012 the one I personally had the hardest time with was Move-VMStorage. The Move-VMStorage cmdlet is used to perform storage migrations for Hyper-V virtual machines i.e. moving a virtual hard disk from one drive to another or from one SMB share to another. The tricky part about this cmdlet comes in when you want to move only a part of the VM’s storage (so only the VHDs or only a single VHD) – in that case you need to provide an array of hastables? What goes in that hashtable? How do you create it? Here’s how.
Overview
There are effectively two way’s to call Move-VMStorage each with two minor variations (a VM object vs. a VM name). The first one is pretty straight forward – you call Move-VMStorage with either a VM object or name and provide a Destination Storage Path, this will move all on the VM’s virtual hard disks, it’s configuration, any snapshots etc… You can see an example of that below. The second method allows you to specify only parts of the VM to move – i.e. a specific virtual hard disk, in many cases this is very helpful if for example you want to balance IO load or increase redundancy or if your VM’s are configured with different storage paths upfront and you want to move one of those VHD’s.
PS C:\> Get-Help Move-VMStorage
NAME
Move-VMStorage
SYNTAX
Move-VMStorage [-VMName] <string> [-DestinationStoragePath] <string> [-ComputerName <string[]>]
[-ResourcePoolName <string>] [-RetainVhdCopiesOnSource] [-AllowUnverifiedPaths] [-AsJob]
[-WhatIf] [-Confirm] [<CommonParameters>]
Move-VMStorage [-VMName] <string> [-ComputerName <string[]>] [-VirtualMachinePath <string>]
[-SnapshotFilePath <string>] [-SmartPagingFilePath <string>] [-Vhds <hashtable[]>]
[-ResourcePoolName <string>] [-RetainVhdCopiesOnSource] [-AllowUnverifiedPaths] [-AsJob]
[-WhatIf] [-Confirm] [<CommonParameters>]
Move-VMStorage [-VM] <VirtualMachine> [-VirtualMachinePath <string>] [-SnapshotFilePath
<string>] [-SmartPagingFilePath <string>] [-Vhds <hashtable[]>] [-ResourcePoolName <string>]
[-RetainVhdCopiesOnSource] [-AllowUnverifiedPaths] [-AsJob] [-WhatIf] [-Confirm]
[<CommonParameters>]
Move-VMStorage [-VM] <VirtualMachine> [-DestinationStoragePath] <string> [-ResourcePoolName
<string>] [-RetainVhdCopiesOnSource] [-AllowUnverifiedPaths] [-AsJob] [-WhatIf] [-Confirm]
[<CommonParameters>]
The Basic Storage Migration
A basic migration operation where you want to migrate all of the VM’s storage is pretty straight forward – let’s look at an example with a VM that has three VHDs all located in the same folder as it’s configuration and the migrate them to another location.
Storage Configuration Prior Migration
PS C:\> (Get-VM Demo).ConfigurationLocation
V:\Share\VMs\Demo
PS C:\> Get-VMHardDiskDrive -VMName Demo
| VMName | ControllerType | ControllerNumber | ControllerLocation | DiskNumber | Path |
| ------ | -------------- | ---------------- | ------------------ | ---------- | ---- |
| Demo | IDE | 0 | 0 | V:\Share\VMs\Demo\OS.vhdx | |
| Demo | SCSI | 0 | 0 | v:\Share\VMs\Demo\SCSIDisk1.vhdx | |
| Demo | SCSI | 0 | 1 | v:\Share\VMs\Demo\SCSIDisk2.vhdx |
Migrating The VM’s Storage
PS C:\> Move-VMStorage -VMName Demo -DestinationStoragePath T:\Share2\VMs\Demo
Storage Configuration Post Migration
PS C:\> (Get-VM Demo).ConfigurationLocation
T:\Share2\VMs\Demo
PS C:\> Get-VMHardDiskDrive -VMName Demo
| VMName | ControllerType | ControllerNumber | ControllerLocation | DiskNumber | Path |
| ------ | -------------- | ---------------- | ------------------ | ---------- | ---- |
| Demo | IDE | 0 | 0 | T:\Share2\VMs\Demo\OS.vhdx | |
| Demo | SCSI | 0 | 0 | T:\Share2\VMs\Demo\SCSIDisk1.vhdx | |
| Demo | SCSI | 0 | 1 | T:\Share2\VMs\Demo\SCSIDisk2.vhdx |
Migrating a Single Virtual Hard Disk
Migrating a single virtual hard disk is a slightly more involved operation – once you see how it’s done the process is pretty simple. We’ll use the same VM as before but in this case we will only migrate the virtual hard disk attached to the SCSI controller at location 1. The first operation thing we need to do is retrieve the path of the virtual hard disk as it is today and we will use the Get-VMHardDiskDrive cmdlet for this. Then we need to create a hash table with two key value pairs, the first pair has a key name of “SourceFilePath” with a value of the location of the virtual disk, the second pair has a key name of “DestinationFilePath” and a value of the location where the virtual hard disk will be migrated to. We then take that hashtable and put it into an array that is then passed to Move-VMStorage.
Here is the complete example:
| $vhdToMove = Get-VMHardDiskDrive -VMName Demo -ControllerLocation 1 $vhdMoveMap = New-Object -TypeName System.Collections.Hashtable $vhdMoveMap.Add("SourceFilePath", $vhdToMove.Path) $vhdMoveMap.Add("DestinationFilePath", "T:\Share2\VMs\Demo\SCSIDisk2.vhdx") $vhds = @($vhdMoveMap) Move-VMStorage -VMName Demo -Vhds $vhds |
Migrating Multiple Virtual Hard Disks
In similar fashion to the first single virtual hard disk migration we can move multiple disks at the same time by just adding more hashtable’s to the array.
For example if we wanted to move two virtual hard disks:
$vhdToMove = Get-VMHardDiskDrive -VMName Demo -ControllerLocation 0 -ControllerType SCSI
$vhdMoveMap = New-Object -TypeName System.Collections.Hashtable
$vhdMoveMap.Add("SourceFilePath", $vhdToMove.Path)
$vhdMoveMap.Add("DestinationFilePath", "T:\Share2\VMs\Demo\SCSIDisk1.vhdx")
$vhds = @($vhdMoveMap)
$vhdToMove = Get-VMHardDiskDrive -VMName Demo -ControllerLocation 1 -ControllerType SCSI
$vhdMoveMap = New-Object -TypeName System.Collections.Hashtable
$vhdMoveMap.Add("SourceFilePath", $vhdToMove.Path)
$vhdMoveMap.Add("DestinationFilePath", "S:\Share3\VMs\Demo\SCSIDisk2.vhdx")
$vhds += @($vhdMoveMap)
Move-VMStorage -VMName Demo -Vhds $vhds
Or if we wanted to move all virtual hard disks for any VM starting with “D”:
Get-VM -Name "D*" | % {
$vhds = $null
foreach ($VHD in (Get-VMHardDiskDrive -VM $_))
{
$vhdMap = New-Object -TypeName System.Collections.Hashtable
$vhdMap.Add("SourceFilePath", $VHD.Path)
$vhdMap.Add("DestinationFilePath", ($VHD.Path.Replace("V:\Share\VMs\", "T:\Share2\VMs\")))
$vhds += @($vhdMap)
}
Move-VMStorage -VM $_ -Vhds $vhds
}
I hope this helps better explain the Move-VMStorage cmdlet… Happy migrating.
-taylorb
I hope everyone that attended TechEd had a great time – this was far and away the best TechEd I have personally attended. I had promised all the scripts from my session and I decided to wait until the recording/slides where available and just do one big post so here it is…
Session Recording
http://channel9.msdn.com/Events/TechEd/NorthAmerica/2012/VIR301
Scripts
Keeping The PowerShell Window On Top
I already posted this earlier at Scripts From TechEd 2012… Part 1 (Keeping PowerShell Window On Top) but here it is again…
$signature = @’
[DllImport("user32.dll")]
public static extern bool SetWindowPos(
IntPtr hWnd,
IntPtr hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
uint uFlags);
‘@
$type = Add-Type -MemberDefinition $signature -Name SetWindowPosition -Namespace SetWindowPos -Using System.Text -PassThru
$handle = (Get-Process -id $Global:PID).MainWindowHandle
$alwaysOnTop = New-Object -TypeName System.IntPtr -ArgumentList (-1)
$type::SetWindowPos($handle, $alwaysOnTop, 0, 0, 0, 0, 0x0003)
Configuring Virtual Fibre Channel
$vm = (Get-ClusterGroup -Name "FibreChannelDemo" -Cluster "hv-teched-c" | Get-VM)
$vm
$adapterA = Add-VMFibreChannelHba -VM $vm -GenerateWwn -SanName "Virtual SAN - A" -Passthru
$adapterA
$adapterB = Add-VMFibreChannelHba -VM $vm -GenerateWwn -SanName "Virtual SAN - B" -Passthru
Import-Module DataONTAP
Connect-NaController -Name "#Name/IP Of NetApp SAN"
$igroup = New-NaIgroup -Name "FibreChannelDemo" -Protocol fcp -Type hyper_v
Add-NaIgroupInitiator -Igroup $igroup.Name -Initiator $adapterA.WorldWidePortNameSetA
Add-NaIgroupInitiator -Igroup $igroup.Name -Initiator $adapterA.WorldWidePortNameSetB
Add-NaIgroupInitiator -Igroup $igroup.Name -Initiator $adapterB.WorldWidePortNameSetA
Add-NaIgroupInitiator -Igroup $igroup.Name -Initiator $adapterB.WorldWidePortNameSetB
New-NaVol -Name "FibreChannelDemo" -Aggregate aggr1 -Size 100G -SpaceReserve "none"
New-NaLun -Path "/vol/FibreChannelDemo/FibreChannelDemo" -Size 100G -Type hyper_v -Unreserved
Add-NaLunMap -InitiatorGroup $igroup -Path "/vol/FibreChannelDemo/FibreChannelDemo"
Start-vm -VM $vm
Displaying SQL Transactions
#Insert the PowerShell Function to Keep the Window On Top
$sqlConn = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Data Source=HV-TE-SQL-1\MSSQLSERVER2;Integrated Security=TRUE;Initial Catalog=Demo"
$sqlConn.Open()
$sqlCommand = new-object System.Data.SqlClient.SqlCommand -ArgumentList @("EXEC [dbo].[AddData];", $sqlConn)
while ($true)
{
$startTime = [DateTime]::Now
for ($counter = 0; $startTime.AddSeconds(1) -gt [DateTime]::Now; $counter++)
{
$sqlCommand.ExecuteReader().Close()
}
Clear-Host
"Executing $counter Transactions Per Second" | write-host -NoNewline
}
Displaying VHD/VHDx Size On Disk
#Insert the PowerShell Function to Keep the Window On Top
function Write-VHDSize {
$vhdSize = ((Get-ItemProperty -Path "m:\teched01_Trim_vhd\trim.vhd").Length/1GB)
$vhdxSize = ((Get-ItemProperty -Path "N:\teched01_Trim_vhdx\trim.vhdx").Length/1GB)
Clear-Host
Write-Host "Current VHD and VHDx Size On Disk"
[String]::Format("VHD Size:`t{0:F2}GB`nVHDx Size:`t{1:F2}GB", $vhdSize, $vhdxSize) | Write-Host -NoNewline
Start-Sleep -Milliseconds 300
}
while ($true)
{
Write-VHDSize
}
Displaying the VHDx and SAN Space Used
#Insert the PowerShell Function to Keep the Window On Top
function WriteVHDVolSize ()
{
$vhdxSize = ((Get-Item -Path "O:\teched01-Trim-san\trim.vhdx").Length/1MB)
$EqlVolumeSize = (Get-EqlVolume -VolumeName "teched01-Trim-san").AllocatedMB
Clear-Host
[String]::Format("VHDx Size:`t{0:F2}MB`nUsed SAN Space:`t{1:F2}MB", $vhdxSize, $EqlVolumeSize) | Write-Host -NoNewline
Start-Sleep -Milliseconds 300
}
if (!(Get-Module EqlPSTools))
{
Import-Module "c:\Program Files\EqualLogic\bin\EqlPSTools.dll"
}
$Creds = new-object -typename System.Management.Automation.PSCredential -argumentlist "demo", (ConvertTo-SecureString -AsPlainText "#Password" -Force)
Connect-EqlGroup -GroupAddress "#Name/IP Of EqualLogic SAN" -Credential $Creds
while ($true)
{
WriteVHDVolSize
}
Initializing the Virtual Disk In The VM
Write-Host "Initializing VHDx Disk..."
$disk = Get-Disk | Where-Object {$_.OperationalStatus -eq "Offline"}
$disk | Set-Disk -IsOffline:$false
if ($disk -eq $null)
{
$disk = Get-Disk | Where-Object {$_.PartitionStyle -eq "RAW"}
}
$disk | Initialize-Disk -PartitionStyle GPT
Write-Host "Creating X: Partition of 40GB on VHDx Disk..."
New-Partition -DiskId $disk.UniqueId -Size 40GB -DriveLetter ([Char]"X") | Format-Volume -FileSystem NTFS -Confirm:$false
Write-Host "Creating Y: Partition of 40GB on VHDx Disk..."
New-Partition -DiskId $disk.UniqueId -Size 40GB -DriveLetter ([Char]"Y") | Format-Volume -FileSystem NTFS -Confirm:$false
Start-Process E:
Optimizing The Volume
Optimize-Volume -DriveLetter X –ReTrim
Full Script For UnMap/Trim Demo Of VHDx On SAN
function Copy-File {
param(
[String]$Source,
[String]$Destination
)
$sourceFile = [System.IO.File]::OpenRead($Source)
$destinationFile = [System.IO.File]::OpenWrite($Destination)
Write-Progress -ID 1 -Activity "Copying BigFile" -status "$Source -> $Destination" -PercentComplete 0
try {
[byte[]]$buffer = new-object byte[] 4096
[int]$total = [int]$counter = 0
do {
$counter = $sourceFile.Read($buffer, 0, $buffer.Length)
$destinationFile.Write($buffer, 0, $counter)
$total += $counter
if ($total % 1mb -eq 0) {
Write-Progress -ID 1 -Activity "Copying BigFile" -status "$Source -> $Destination" `
-PercentComplete ([int]($total/$sourceFile.Length* 100))
}
} while ($counter -gt 0)
}
finally {
Write-Progress -ID 1 -Activity "Copying BigFile" -Completed:$true
$sourceFile.Close()
$destinationFile.Close()
}
}
"Windows PowerShell" | Write-Host
"Copyright (C) 2012 Microsoft Corporation. All rights reserved.`n" | Write-Host
"PS C:\> Copy-File -From `"E:\MyBigFile.bin`" -To `"X:\MyBigFile.bin`"" | Write-Host -NoNewline
Read-Host |Out-Null
Copy-File -From "E:\MyBigFile.bin" -To "X:\MyBigFile.bin" | Write-Host -NoNewline
Write-Host "Flushing File System Cache...`n"
Set-Disk -Number 2 -IsOffline:$true
Set-Disk -Number 2 -IsOffline:$false
"PS C:\> Remove-Item `"X:\MyBigFile.bin`" -Force:`$true -Confirm:`$false" | Write-Host -NoNewline
Read-Host |Out-Null
Remove-Item "x:\MyBigFile.bin" -Force:$true -Confirm:$false
"PS C:\> Optimize-Volume -DriveLetter X -ReTrim" | Write-Host -NoNewline
Read-Host |Out-Null
Optimize-Volume -DriveLetter X –ReTrim
-taylorb
I just finished my speaking session at TechEd 2012, once the recording is available I will post a link… I’ve already had an overwhelming number of requests for the scripts that I used during the session. I’m going to do a few posts over the next few days, as I can steal 10 min from all the TechEd activities.
The first script and the one that every seemed the most interested in keeps a PowerShell Window on top i.e. the top most window. The way I did it was a bit evil – well very non PowerShell but it works. I use PInvoke…
$signature = @’
[DllImport("user32.dll")]
public static extern bool SetWindowPos(
IntPtr hWnd,
IntPtr hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
uint uFlags);
‘@
$type = Add-Type -MemberDefinition $signature -Name SetWindowPosition -Namespace SetWindowPos -Using System.Text -PassThru
$handle = (Get-Process -id $Global:PID).MainWindowHandle
$alwaysOnTop = New-Object -TypeName System.IntPtr -ArgumentList (-1)
$type::SetWindowPos($handle, $alwaysOnTop, 0, 0, 0, 0, 0x0003)
So there you go – look for more of the scripts latter tonight or tomorrow…
-taylorb
After a very long day of travel yesterday I am even happier to be at TechEd 2012 now. This is going to be the most exciting TechEd that I can remember - there is so much stuff in Windows Server 2012 it's really unbelievable.
In any case if you are at TechEd stop by and say hi or even better come by my session!
VIR301 - Windows Server 2012 Hyper-V Storage
Speaker(s): Taylor Brown, Senthil Rajaram
Tuesday, June 12 at 1:30 PM - 2:45 PM in S310A
In this session, we cover the changes to the Windows Server 2012 Hyper-V storage stack. We dive into the enhancements around the new virtual disk format, integrating new storage industry innovations around offloaded data transfer and large sector disks, and detail the new storage options for Hyper-V including Storage spaces and SMB file storage. We present performance and scaling improvements and conclude the session with sample configurations, including both standalone and clustered options. #TEVIR301
Taylor Brown
Hyper-V Enterprise Deployment Team
taylorb@microsoft.com
http://blogs.msdn.com/taylorb
![]()
I originally wrote this script for my own clusters to upgrade them from Windows Server 2008 R2 to Windows Server “8” Beta and since then I have shared it with a few others who also found it very helpful. I had some designs on improving it but decided I would put those on hold until later. The script is pretty straight forward it gathers all of the information about a VM from the source cluster and then does in import from the source cluster to the destination machine having Hyper-V copy all of the VHD and other configuration files. The script takes advantage of a few new Hyper-V features in Window Server “8” Beta – specifically the ability to import directly from a VM configuration file as well as our new Hyper-V PowerShell cmdlet’s. If you want more details on Compare-VM, Import-VM or any of the other cmdlet’s take a look at the TechNet documentation.
Script Limitations and Assumptions
All of these can be worked around – but many will be handled better in future releases (i.e. RC/RTM) thus I did not spend to much time scripting around them.
- Must Be Run on a Windows Server “8” Beta Hyper-V Host
- VM’s must be using CSV
- VM’s VHD’s must all be on the same CSV and the same folder and must be located under the “Virtual Hard Disks” Folder
- VM’s can not have snapshots (due to a bug in Windows Server “8” Beta)
function Migrate-VMFromR2Cluster {
param (
[Parameter(Mandatory=$true,Position=0,HelpMessage="Name Of The VM To Be Migrated")]
[String]
$VmName,
[Parameter(Mandatory=$true,Position=0,HelpMessage="Name Of The Source Cluster")]
[String]
$SourceCluster,
[Parameter(Mandatory=$true,Position=0,HelpMessage="Path Where The VM Will Be Copied To On Local Machine")]
[String]
$TargetVMPath,
[Parameter(Mandatory=$true,Position=0,HelpMessage="Name Of The Virtual Switch The VM Will Be Connected To")]
[String]
$TargetHyperVSwitchName
)
#Get all of the VM's settings on the source server, we have to do this prior to offlining the configuration file
$SourceServer = ((Get-ClusterGroup $VmName -Cluster $SourceCluster).OwnerNode).Name
$SourceVM = Get-WmiObject -Namespace "root\virtualization" -ComputerName $SourceServer -Class "Msvm_ComputerSystem" -Filter "ElementName = '$VmName'"
$VmDataRoot = ($SourceVM.GetRelated("Msvm_VirtualSystemGlobalSettingData").ExternalDataRoot.ToLower()).Replace("c:\", "\\$SourceServer\c`$\")
$VmConfigurationFile = ($VmDataRoot+ "\Virtual Machines\" + $SourceVM.Name + ".xml")
$VhdSourcePath = ($VmDataRoot + "\Virtual Hard Disks")
$Msvm_VirtualSystemSettingData = ($SourceVM.GetRelated("Msvm_VirtualSystemSettingData") | % {$_})
$Msvm_SyntheticEthernetPortSettingDataCollection = $Msvm_VirtualSystemSettingData.GetRelated("Msvm_SyntheticEthernetPortSettingData")
#Get all of the VLAN and networking configurations for the VM
# - this is requried as in Windows 2008 and 2008 R2 we store the network settings in the switch configuration not with the VM
# - in Windows Server "8" Beta there was a bug that this data was also not present in the export file
$SwitchPortSettings = New-Object -TypeName System.Collections.Hashtable
foreach ($Msvm_SyntheticEthernetPortSettingData in $Msvm_SyntheticEthernetPortSettingDataCollection)
{
$Msvm_SwitchPort = $Msvm_SyntheticEthernetPortSettingData | % {[WMI]($_.Connection| % {$_})}
if ($Msvm_SwitchPort.__Path -ne $null)
{
$Msvm_VLANEndPoint = $Msvm_SwitchPort.GetRelated("Msvm_VLANEndPoint") | % {$_}
$Msvm_VLANEndpointSettingData = $Msvm_VLANEndPoint.GetRelated("Msvm_VLANEndpointSettingData") | % {$_}
$SwitchPortSettings.Add($Msvm_SyntheticEthernetPortSettingData.InstanceID,
@($Msvm_SwitchPort, $Msvm_VLANEndpointSettingData.AccessVLAN))
}
}
#Offline the VM's configuration file so that we can open in for Import
(Get-ClusterGroup $VmName -cluster $SourceCluster) | Get-ClusterResource | Stop-ClusterResource | Out-Host
#Generate a VM compatability object which will be used to fixup all the network adapters
# - note that copy is specified meaning the VHDs will be copied over the network
$compare = Compare-VM -Copy -Path $VmConfigurationFile -VhdSourcePath $VhdSourcePath -VhdDestinationPath $TargetVMPath -VirtualMachinePath $TargetVMPath
#Fixing network adapters and make sure they use correct VMSwitch and keep VLAN ID and mac spoofing settings
Foreach ($NetworkAdapter in $Compare.vm.NetworkAdapters)
{
$NetworkAdapter | Connect-VMNetworkAdapter -SwitchName $TargetHyperVSwitchName
$NicSetting = $SwitchPortSettings[$NetworkAdapter.Id]
if ($NicSetting -ne $null)
{
$NetworkAdapter | Set-VMNetworkAdapterVlan -Access -VlanId $NicSetting[1]
if ($NetworkAdapter[0].AllowMacSpoofing)
{
$NetworkAdapter | Set-VMNetworkAdapter -MacAddressSpoofing On
}
}
}
Import-VM -CompatibilityReport $Compare
Set-VM -Name $VmName -AutomaticStopAction ShutDown
}
Taylor Brown
Hyper-V Enterprise Deployment Team
taylorb@microsoft.com
http://blogs.msdn.com/taylorb
![]()

In my last post we talked about PowerShell Session Configurations and specifically about the new RunAs functionality in PowerShell 3.0. But there’s some other incredibly cool and powerful functionality within session configurations – Session Configuration Files. You can see the full beta documentation on Session Configuration Files on TechNet but I am going to focus on a few specific features.
ModulesToImport and VisibleCmdlets
When you create a new PSSessionConfiguration file you can specify what modules to import and more specifically what cmdlets, aliases, types etc… will be visible to the user. Using this functionality we can create PSSessions that will only allow specific users to do very specific actions such as starting or stopping a VM. In the example below I create a configuration that will only allow users to use the Get-VM* cmdlet along with Start-VM and Stop-VM.
New-PSSessionConfigurationFile -Path C:\psSessions\HyperVRemoteLimitedUser.pssc `
-SessionType RestrictedRemoteServer `
-LanguageMode RestrictedLanguage `
-ModulesToImport Hyper-V `
-VisibleCmdlets @("Get-VM*", "Start-VM", "Stop-VM") `
Register-PSSessionConfiguration -Name "HyperVRemoteLimitedUser" `
-Path C:\psSessions\HyperVRemoteLimitedUser.pssc `
-SessionType DefaultRemoteShell -AccessMode Remote
When a remote users connects to this session they will only be able to use cmdlet’s meting those specifications…
PS C:\> $session = New-PSSession -ComputerName 37-4611K2615L -ConfigurationName HyperVRemoteLimitedUser
PS C:\> Enter-PSSession $session
[37-4611K2615L]: PS>get-vm
Name State CPUUsage(%) MemoryAssigned(M) MemoryDemand(M) MemoryStatus Uptime Status ReplicationState
---- ----- ----------- ----------------- --------------- ------------ ------ ------ ----------------
Test Off 0 0 0 00:00:00 Operating normally Disabled
Test3 Off 0 0 0 00:00:00 Operating normally Disabled
[37-4611K2615L]: PS>new-vm
The term 'New-VM' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.
+ CategoryInfo : ObjectNotFound: (New-VM:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
[37-4611K2615L]: PS>get-host
The term 'Get-Host' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.
+ CategoryInfo : ObjectNotFound: (Get-Host:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
[37-4611K2615L]: PS>Get-VMSwitch
Name SwitchType NetAdapterInterfaceDescription
---- ---------- ------------------------------
External External Microsoft Network Adapter Multiplexor Driver
Creating Multiple Configurations For Different Users
In my last post I provided a handy script New-SSDLForAdGroup to use with Register-PSSessionConfiguration. Combining that with PSSessionConfigurationFiles – I’m going to create three different session types depending on the user.
Function New-SSDLForADGroup
{
param(
[String]$GroupName = [String]::Empty
)
try {
#retreave the default SSDL as a base
$defaultSSDL = (Get-Item WsMan:\localhost\service\rootSDDL).Value
$isContainer = $false
$isDS = $false
$SecurityDescriptor = New-Object -TypeName `
Security.AccessControl.CommonSecurityDescriptor `
$isContainer, $isDS, $defaultSSDL
#remove the default groups (Administrators and Interactive User)
while($SecurityDescriptor.DiscretionaryAcl.Count -gt 0)
{
$ssdl = $SecurityDescriptor.DiscretionaryAcl[0]
$SecurityDescriptor.DiscretionaryAcl.RemoveAccess(
[System.Security.AccessControl.AccessControlType]::Allow,
$ssdl.SecurityIdentifier,
$ssdl.AccessMask,
$ssdl.InheritanceFlags,
$ssdl.PropagationFlags) | Out-Null
}
#get the SID for the specified Group and add it to the SSDL
$AdminGroup = New-Object Security.Principal.NTAccount $GroupName
$AdminGroupSid = $AdminGroup.Translate(`
[Security.Principal.SecurityIdentifier]).Value
$SecurityDescriptor.DiscretionaryAcl.AddAccess(
[System.Security.AccessControl.AccessControlType]::Allow,
$AdminGroupSid,
268435456, #full control all operations
[System.Security.AccessControl.InheritanceFlags]::None,
[System.Security.AccessControl.PropagationFlags]::None) | Out-Null
return $SecurityDescriptor.GetSddlForm("All")
}
catch [Exception] {
Write-Error -Message "Failed To Generate SSDL (review inner exception):`n $_.Message" `
-Exception $_.Exception
}
}
$HyperVRemoteAdminRunAs = Get-Credential "hyper-v\HyperVRemoteCommands"
#Limited Users
$ssdlForSession = New-SSDLForADGroup -GroupName "hyper-v\LimitedUsers"
New-PSSessionConfigurationFile -Path C:\psSessions\HyperVRemoteLimitedUser.pssc `
-SessionType RestrictedRemoteServer `
-LanguageMode RestrictedLanguage `
-ModulesToImport Hyper-V `
-VisibleCmdlets @("Get-VM*", "Start-VM", "Stop-VM") `
Register-PSSessionConfiguration -Name "HyperVRemoteLimitedUser" `
-Path C:\psSessions\HyperVRemoteLimitedUser.pssc `
-SessionType DefaultRemoteShell -AccessMode Remote `
-NoServiceRestart -Force `
-SecurityDescriptorSddl $ssdlForSession `
-RunAsCredential $HyperVRemoteAdminRunAs
#PowerUsers
#Only use the cmdlet's in the allowed modules
$moduleList = @("Hyper-V")
$cmdLets = $null
foreach ($Module in $moduleList)
{
Import-Module $Module
$Mod = Get-Module $Module
$cmdLets += $Mod.ExportedCmdlets.Keys
}
$ssdlForSession = New-SSDLForADGroup -GroupName "hyper-v\hv-PowerUsers"
New-PSSessionConfigurationFile -Path C:\psSessions\HyperVRemotePowerUser.pssc `
-SessionType RestrictedRemoteServer `
-LanguageMode RestrictedLanguage `
-ModulesToImport $moduleList `
-VisibleCmdlets ([Array]$cmdLets)
Register-PSSessionConfiguration -Name "HyperVRemotePowerUser" `
-Path C:\psSessions\HyperVRemotePowerUser.pssc `
-SessionType DefaultRemoteShell -AccessMode Remote `
-NoServiceRestart -Force `
-SecurityDescriptorSddl $ssdlForSession `
-RunAsCredential $HyperVRemoteAdminRunAs
#ServerAdmins
$ssdlForSession = New-SSDLForADGroup -GroupName "hyper-v\hv-admins"
Register-PSSessionConfiguration -Name "HyperVServerAdmin" `
-SessionType DefaultRemoteShell -AccessMode Remote `
-NoServiceRestart -Force `
-SecurityDescriptorSddl $ssdlForSession `
-RunAsCredential $HyperVRemoteAdminRunAs
Restart-Service -Name WinRM –Force
Taylor Brown
Hyper-V Enterprise Deployment Team
taylorb@microsoft.com
http://blogs.msdn.com/taylorb
![]()
In response to my recent set of posts someone pointed me to a new PowerShell 3.0 feature of PowerShell sessions – RunAs… I had seen people use PowerShell session configurations in the past but I had never really seen a need to use them myself that is until now. With the challenges of constrained delegation and some of the limitations of CredSSP the ability to define an account on your Hyper-V hosts for which all commands will run in the context of can be very powerful and that’s exactly what this provides.
Basic Configuration
For example let’s say I defined a new domain user “HyperVRemoteCommands” and I restrict the account as I would any other service account – no interactive login, no remote desktop login etc… and I add it to the local Hyper-V Administrators group on my Windows 8 Hyper-V servers which will give that account permissions to all of Hyper-V (creating VM’s, deleting VM’s, creating switches etc…) If I’m using SMB storage, which I am, I would also add that account to the security group that had permissions on my SMB storage.
Now I’ve got a user that can do all of my Hyper-V administration but has no login rights – basically just like an SCVMM service account or a SQL account. Here’s the cool part you can use the Register-PSSessionConfiguration cmdlet which with PowerShell 3.0 has a new parameter RunAsCredential to create a new WSMan session that will run as that user account. Here’s an Example:
Register-PSSessionConfiguration -Name HyperVRemoteAdmin -SessionType DefaultRemoteShell -AccessMode Remote -RunAsCredential (Get-Credential "hyper-v\HyperVRemoteCommands")
Now from a remote machine we can connect to that session and execute commands just as we normally would – however they are being run in the context of the RunAs account.
$session = New-PSSession -ComputerName 37-4611K2615L -ConfigurationName HyperVRemoteAdmin
Invoke-Command -Session $session -ScriptBlock {New-VM –Name Demo -Path \\hv-w8-beta-smb\VirtualMachines\Demo}
Advanced Security
The question that remains is who has access to connect to this session and use these permissions? By default access is same for the new session as the default WSMan instance – which is the local administrators group and interactive users, this can be overridden very easily with custom permissions. If you specify the –ShowSecurityDescriptorUI flag the cmdlet will prompt you for the credentials requires to this configuration. There’s another parameter you can specify as well –SecurityDescriptorSddl which is much more script friendly but does require some work as it takes an Sddl string.
So I wrote a function to generate one – it takes the default WSMan SSDL – removes all of the permissions (local administrators group and interactive users) and then adds a domain security group with full permissions.
Function New-SSDLForADGroup
{
param(
[String]$GroupName = [String]::Empty
)
try {
#retreave the default SSDL as a base
$defaultSSDL = (Get-Item WsMan:\localhost\service\rootSDDL).Value
$isContainer = $false
$isDS = $false
$SecurityDescriptor = New-Object -TypeName `
Security.AccessControl.CommonSecurityDescriptor `
$isContainer, $isDS, $defaultSSDL
#remove the default groups (Administrators and Interactive User)
while($SecurityDescriptor.DiscretionaryAcl.Count -gt 0)
{
$ssdl = $SecurityDescriptor.DiscretionaryAcl[0]
$SecurityDescriptor.DiscretionaryAcl.RemoveAccess(
[System.Security.AccessControl.AccessControlType]::Allow,
$ssdl.SecurityIdentifier,
$ssdl.AccessMask,
$ssdl.InheritanceFlags,
$ssdl.PropagationFlags) | Out-Null
}
#get the SID for the specified Group and add it to the SSDL
$AdminGroup = New-Object Security.Principal.NTAccount $GroupName
$AdminGroupSid = $AdminGroup.Translate(`
[Security.Principal.SecurityIdentifier]).Value
$SecurityDescriptor.DiscretionaryAcl.AddAccess(
[System.Security.AccessControl.AccessControlType]::Allow,
$AdminGroupSid,
268435456, #full control all operations
[System.Security.AccessControl.InheritanceFlags]::None,
[System.Security.AccessControl.PropagationFlags]::None) | Out-Null
return $SecurityDescriptor.GetSddlForm("All")
}
catch [Exception] {
Write-Error -Message "Failed To Generate SSDL (review inner exception):`n $_.Message" `
-Exception $_.Exception
}
}
Now if we combine that with some active directory scripting to grab all of the Hyper-V Servers in my security group and run the register command on them then all of my Hyper-V hosts will be setup to run delegated commands for any user in my Hyper-V administrators group. You may notice that I specify –NoServiceRestart in the first command and then restart the service in the second – the reason for this is to ensure that if the first command failed I would get full exception handling.
$HyperVServersGroup = "hyper-v\hv-hosts"
$HyperVAdminsGroup = "hyper-v\hv-admins"
$HyperVRemoteAdminRunAs = Get-Credential "hyper-v\HyperVRemoteCommands"
$HvServersAD = Get-ADGroupMember $HyperVServersGroup
$ssdlForSession = New-SSDLForADGroup -GroupName "hyper-v\hv-admins"
$remoteScriptBlock = {Register-PSSessionConfiguration -Name "HyperVRemoteAdmin" `
-SessionType DefaultRemoteShell -AccessMode Remote `
-NoServiceRestart -Force -SecurityDescriptorSddl `
$args[0] -RunAsCredential $args[1]}
foreach ($computer in $HvServersAD)
{
Invoke-Command -ComputerName $computer.Name -ScriptBlock $remoteScriptBlock `
-ArgumentList @($ssdlForSession, $HyperVRemoteAdminRunAs)
Invoke-Command -ComputerName $computer.Name `
-ScriptBlock {Restart-Service -Name WinRM -Force} -ErrorAction Ignore
}
Taylor Brown
Hyper-V Enterprise Deployment Team
taylorb@microsoft.com
http://blogs.msdn.com/taylorb
![]()
In reference to Enabling Hyper-V Remote Management - Configuring Constrained Delegation For SMB and Highly Available SMB and Enabling Hyper-V Remote Management - Configuring Constrained Delegation For Non-Clustered Live Migration I’ve had some people ask me about scripting these settings… Well in the first post there was the optional step of creating a security group for all of your Hyper-V servers – there’s actually another reason that I like to do this.
Here’s the script I use… It takes the name of the security group, the name of the SMB server and wither or not live migration should be enabled. This does require that you have the Active Directory PowerShell module.
$HyperVServersGroup = "hv-hosts"
$SMBServer = "HV-W8-BETA-SMB"
$EnableLiveMigration = $true
$SMBServerAD = Get-ADComputer $SMBServer
$AllowedToDelegateToSMB = @(
("cifs/"+$SMBServerAD.Name),
("cifs/"+$SMBServerAD.DNSHostName))
$HvServersAD = Get-ADGroupMember $HyperVServersGroup
for ($serverCounter = 0; $serverCounter -lt $HvServersAD.Count; $serverCounter++)
{
$AllowedToDelegateTo = $AllowedToDelegateToSMB
if ($EnableLiveMigration)
{
for ($deligateCounter = 0; $deligateCounter -lt $HvServersAD.Count; $deligateCounter++)
{
if ($deligateCounter -ne $serverCounter)
{
$deligationServer = $HvServersAD[$deligateCounter] | Get-ADComputer
$AllowedToDelegateTo += @(
("Microsoft Virtual System Migration Service/"+$deligationServer.Name),
("Microsoft Virtual System Migration Service/"+$deligationServer.DNSHostName))
}
}
}
($HvServersAD[$serverCounter] | Get-ADComputer) | Set-ADObject -Add @{"msDS-AllowedToDelegateTo"=$AllowedToDelegateTo}
}
Taylor Brown
Hyper-V Enterprise Deployment Team
taylorb@microsoft.com
http://blogs.msdn.com/taylorb
![]()
You may have already read my previous two posts on Hyper-V remote management using the UI and configuring Constrained Delegation, for some people that will not be an option or is just undesirable... Well with Windows 8 and the joy of PowerShell we have another option. Many of the new Hyper-V PowerShell cmdlets (http://technet.microsoft.com/en-us/library/hh848559.aspx) take a computername parameter to allow for remote usage however if you are creating a virtual machine using SMB storage or if your are live migrating a VM you run into the same double hop constrained delegation problem as with the UI. Luckily we have PowerShell remoting or WinRM remoting more specifically.
Going back to the example I’ve been using we have a two node Windows Server 8 Scale-Out file server cluster, two standalone Hyper-V Servers and a remote management workstation. In this case we will be using PowerShell from the management workstation as opposed to the Hyper-V UI. The other nice part about PowerShell remoting is that your management workstation could be of a different domain or no domain at all depending on how you choice to send the credentials.
Creating a New VM Remotely with SMB Storage – Failure
I’ll start with the error you will get if you try to create a VM remotely using SMB storage in Beta – the error message is wrong (it’s a bug and already fixed) but non the less you can see that using ComputerName parameter with the VM on an SMB share it fails.
PS C:\Windows\system32> New-VM -Name "VMOverSMB" -Path \\hv-w8-beta-smb\VirtualMachines -ComputerName 37-4611K2615L
New-VM : The Virtual Machine Management Service on host '37-4611K2615L' is not running.
At line:1 char:1
+ New-VM -Name "VMOverSMB" -Path \\hv-w8-beta-smb\VirtualMachines -ComputerName 37 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-VM], VirtualizationOperationFailedException
+ FullyQualifiedErrorId : Microsoft.HyperV.PowerShell.Commands.NewVMCommand
Credential Delegation – CredSSP/Kerberos
In order to avoid the double hop and credential delegation issues we need to issue the command in the context of the Hyper-V server. The easiest way to accomplish this is by using CredSSP and WsMan/WinRM. By default CredSSP is disabled or more specifically the “AllowFreshCredentials” and “Allow CredSSP authentication” polcies are disabled you can either enable these via group policy or you can enabled them via the Enable-WSManCredSSP cmdlet. I utilize group policy to enable CredSSP for my environment and have it pretty well locked down such that only systems on my domain and specific trusted domains can provide credentials and further more WinRM only listens on specific IP ranges.
Enable-WSManCredSSP -Role Client -DelegateComputer "*.hyper-v.selfhost.corp.microsoft.com"
Enable-WSManCredSSP -Role Server
It is worth stating the Kerberos delegation is also an option – it does carry with it some caveats as well. When using Kerberos delegation you have to enable unconstrained delegation on the computer account and unconstrained delegation and constrained don’t seem to get along so it prevents Hyper-V remote UI management.
Using Credential Delegation
There are a few ways in PowerShell to use credential delegation – you can use Invoke-Command with a script block, computer name, credentials or you can create a PSSession and either pass that to InvokeCommand or enter the PSSession and execute the commands locally. I’ll start with the PSSession example as it breaks down the concepts well.
The first step is getting the credentials that will be used on the remote machine – this is done via the Get-Credential cmdlet which will popup a standard Windows credential dialog, in my case I provide my username so I don’t have to type it in again. The next step is to create a session via the New-PSSession cmdlet using CredSSP as my authentication and the credentials I got in step 1. Next I enter the session with Enter-PSSession, after this command I am effectively typing on the remote computer and that’s evidenced by the computer name before my command prompt. At this point I can create the New VM and away we go.
PS C:\> $remotingCreds = Get-Credential -Credential "hyper-v\taylorb"
PS C:\> $Session = New-PSSession -Authentication Credssp -Credential $remotingCreds -ComputerName "37-4611K2617L"
PS C:\> Enter-PSSession $Session
[37-4611K2617L]: PS C:\Users\taylorb\Documents> New-VM -Name "VMOverSMB" -Path \\hv-w8-beta-smb\VirtualMachines
Name State CPUUsage(%) MemoryAssigned(M) MemoryDemand(M) MemoryStatus Uptime Status ReplicationState
---- ----- ----------- ----------------- --------------- ------------ ------ ------ ----------------
VMOverSMB Off 0 0 0 00:00:00 Operating normally Disabled
In this example the first two steps are the same but as opposed to entering the PS Sessions I use Invoke-Command to execute a script block on the remote server.
PS C:\> $remotingCreds = Get-Credential -Credential "hyper-v\taylorb"
PS C:\> $Session = New-PSSession -Authentication Credssp -Credential $remotingCreds -ComputerName "37-4611K2615L"
PS C:\> Invoke-Command -Session $Session -ScriptBlock {New-VM -Name "VMOverSMB" -Path \\hv-w8-beta-smb\VirtualMachines}
We can also use Invoke-Command without a PSSession (though it will just create one for you and close it when it’s done).
PS C:\> Invoke-Command -Authentication Credssp -Credential (Get-Credential) -ComputerName "37-4611K2617L" -ScriptBlock {New-VM -Name "VMOverSMB" -Path \\hv-w8-beta-smb\VirtualMachines}
Or if you have unconstrained delegation enabled you can use Kerberos – also note that with Kerberos there is no need to provide credentials as the current users credentials will be delegated for you.
PS C:\> Invoke-Command -Authentication Kerberos -ComputerName "37-4611K2617L" -ScriptBlock {New-VM -Name "VMOverSMB" -Path \\hv-w8-beta-smb\VirtualMachines}
Live Storage Migration and Live Migration
These seem to be the biggest challenges for remote management – since they implicitly require a third hop I wanted to make sure there where some samples…
PS C:\> $remotingCreds = Get-Credential -Credential "hyper-v\taylorb"
PS C:\> $Session = New-PSSession -Authentication Credssp -Credential $remotingCreds -ComputerName "37-4611K2617L"
PS C:\> Invoke-Command -Session $Session -ScriptBlock {Move-VM -Name "VMOverSMB" -DestinationHost "37-4611K2615L"}
PS C:\> $remotingCreds = Get-Credential -Credential "hyper-v\taylorb"
PS C:\> $Session = New-PSSession -Authentication Credssp -Credential $remotingCreds -ComputerName "37-4611K2617L"
PS C:\> Invoke-Command -Session $Session -ScriptBlock {Move-VMStorage -VMName LocalVM -DestinationStoragePath \\hv-w8-beta-smb\VirtualMachines\LocalVM
Taylor Brown
Hyper-V Enterprise Deployment Team
taylorb@microsoft.com
http://blogs.msdn.com/taylorb
![]()

In Windows Server 8 we added the ability to live migrate virtual machines without the requirement of a cluster i.e. standalone live migration. For this feature to work the storage the virtual machine is using must be available to both Hyper-V severs which implies that it’s hosted on an SMB share – we also have the ability to perform a live storage migration in concert with the virtual machine live migration in Windows Server 8 but I’ll get to that latter. If you read my last post on Enabling Hyper-V Remote Management - Configuring Constrained Delegation For SMB and Highly Available SMB which discusses configuring the Hyper-V severs to delegate credentials to the SMB server this process is similar to that and the configuration of the SMB delegation is a prerequisite for this post.
Going back to the example from my last post let’s take an environment similar to this – we have a two node Windows Server 8 Scale-Out file server cluster, two standalone Hyper-V servers and a remote management workstation. In the last post we configured constrained delegation between the two Hyper-V servers and the SMB server which allowed us to create a new virtual machine on the one of the Hyper-V servers with the virtual machines storage residing on the SMB share. Now we want to live migrate that virtual machine to the second Hyper-V server. In order to accomplish this we again must enable constrained delegation.
Overview of Process
- Configure Constrained Delegation Between the two Hyper-V Servers
- Enable Live Migration on Both Hyper-V Servers
- Live Migrate The Virtual Machine
Configure Constrained Delegation Between the two Hyper-V Servers
For Each Hyper-V Server…
- Using The Active Directory Users and Computers Dialog Open The Properties Dialog On The Computer Account and Select The Delegation Tab
- “Trust this computer for deliberation to the specified services only” Correction Use Kerberos only works
and “Use any authentication protocol”should already be selected and the CIFS service should be enabled with the SMB server. - Select “Add” and Provide the Name Of The Other Hyper-V Server(s) (37-4611K2717L in my example)

Enable Live Migration on Both Hyper-V Servers
For each Hyper-V Server you need to enable live migration this is disabled by default as a security precaution as not every server may want to allow migrations to and from it.
- From the Hyper-V Manager UI open the Hyper-V Settings
- Select the Live Migration node
- Check the “Enable incoming and outgoing live migrations” option
- Select “Use Kerberos” from the authentication protocol – if you don’t select this when you try to live migrate using a remote UI you will get an error (here’s the error message so bing will find it when someone forgets this step :)
“Virtual machine migration failed at migration source. Failed to establish a connection with host <destination> The credentials supplied to the package where not recognized (0x8009030D). Failed to authenticate the connection at the source host: no suitable credentials available.” - Optionally you can specify the networks that allow live migrations over them – this is recommended to prevent live migrations (which are unencrypted) from going over public networks.
Live Migrate The Virtual Machine
We are now ready to live migrate the virtual machine.
- From the Hyper-V Manager Right Click on The Virtual Machine and Select Move
- Select “Move the virtual machine” to specify a live migration
- From the Move Options Page Select “Move only the virtual machine” as the VHD and configuration are already on our SMB server
- Select Finish To Start the Live Migration

Done…
Taylor Brown
Hyper-V Enterprise Deployment Team
taylorb@microsoft.com
http://blogs.msdn.com/taylorb
![]()







