Senior Staff Engineer, Office of the CTO at Nerdio - on end user computing, virtualisation, modern device management, enterprise mobility, & automation
Like the last article, I’ve taken the PowerShell generated by Citrix Studio, banged my head against the wall a few times, and improved it to create the code presented in this article.
Linking the Code to the UI
To help explain the code, I’ll first run through the Create Delivery Group wizard and show how the code relates to options in the wizard and the Delivery Group properties.
Assigning users to the Desktop Group - New-BrokerEntitlementPolicyRule -Name “Windows 8 x86_1” -IncludedUsers “HOME\Domain Users” -DesktopGroupUid 11
Add-BrokerMachineConfiguration adds StoreFront and UPM configurations to a Delivery Group. The function just adds a machine configuration - the configuration is setup separately. To avoid selecting a StoreFront server for the Delivery Group, don’t use this function.
Selecting a StoreFront server - Add-BrokerMachineConfiguration -DesktopGroup “Windows 8 x86” -InputObject @(1005)
When calling New-BrokerDesktopGroup, the Delivery Group name, display or published name and description is specified.
Group name, Display name and description - New-BrokerDesktopGroup -Name “Windows 8 x86” -PublishedName “Windows 8 x86” -Description “Windows 8 x86 with Office 2013, Pooled desktops”*
The wizard does not expose all settings for the Delivery Group, so additional settings require opening the properties of the new group. These can be set during creation of the group when using PowerShell.
The same call to New-BrokerDesktopGroup is used to specify user settings including colour depth and time zone preferences.
Controlling various user settings - New-BrokerDesktopGroup -ColorDepth TwentyFourBit -TimeZone “AUS Eastern Standard Time” -SecureIcaRequired $False
New-BrokerDesktopGroup and New-BrokerPowerTimeScheme are both used to manage virtual machine power management settings. Setting or modifying the peak and off peak hours isn’t friendly either.
New-BrokerAccessPolicyRule modifies the access policies. This is called twice - once for connections through NetScaler Gateway and once for direct connections.
Creating the Delivery Group is relatively straight-forward; however there are some additional steps, such as creating a StoreFront server and working out how to manage peak and off peak times, that require a bit more investigation.
The Code
Below is the full code listing with comments inline that should provide some detail on the process the code follows. At this point the code provides some error checking for the most important steps. There are still some additional steps and error checking that could be integrated into the code.
#---------------------------------------------------------------------------# Author: Aaron Parker# Desc: Using PowerShell to create a XenDesktop 7.x Delivery Group# Date: Aug 23, 2014# Site: http://stealthpuppy.com#---------------------------------------------------------------------------# # Set variables for the target infrastructure# ----------$adminAddress='xd71.home.stealthpuppy.com'#The XD Controller we're going to execute against$xdControllers='xd71.home.stealthpuppy.com'# Desktop Group properties$desktopGroupName="Windows 8 desktops"$desktopGroupPublishedName="Windows 8 desktops"$desktopGroupDesc="Windows 8 x86 with Office 2013, Pooled desktops"$colorDepth='TwentyFourBit'$deliveryType='DesktopsOnly'$desktopKind='Shared'$sessionSupport="SingleSession"#Also: MultiSession$functionalLevel='L7'$timeZone='AUS Eastern Standard Time'$offPeakBuffer=10$peakBuffer=10$assignedGroup="HOME\Domain Users"#Machine Catalog$machineCatalogName="Windows 8 x86"# ----------# Change to SilentlyContinue to avoid verbose output$VerbosePreference="Continue"# Create the Desktop Group# http://support.citrix.com/proddocs/topic/citrix-broker-admin-v2-xd75/new-brokerdesktopgroup-xd75.htmlIf(!(Get-BrokerDesktopGroup-Name$desktopGroupName-ErrorActionSilentlyContinue)){Write-Verbose"Creating new Desktop Group: $desktopGroupName"$desktopGroup=New-BrokerDesktopGroup-ErrorActionSilentlyContinue-AdminAddress$adminAddress-Name$desktopGroupName-DesktopKind$desktopKind-DeliveryType$deliveryType-Description$desktopGroupPublishedName-PublishedName$desktopGroupPublishedName-MinimumFunctionalLevel$functionalLevel-ColorDepth$colorDepth-SessionSupport$sessionSupport-ShutdownDesktopsAfterUse$True-TimeZone$timeZone-InMaintenanceMode$False-IsRemotePC$False-OffPeakBufferSizePercent$offPeakBuffer-PeakBufferSizePercent$peakBuffer-SecureIcaRequired$False-TurnOnAddedMachine$False-OffPeakDisconnectActionSuspend-OffPeakDisconnectTimeout15-Scope@()}# At this point, we have a Desktop Group, but no users or desktops assigned to it, no power management etc.# Open the properties of the new Desktop Group to see what's missing.# If creation of the desktop group was successful, continue modifying its propertiesIf($desktopGroup){# Add a machine configuration to the new desktop group; This line adds an existing StoreFront server to the desktop group# Where does Input Object 1005 come from?# http://support.citrix.com/proddocs/topic/citrix-broker-admin-v2-xd75/add-brokermachineconfiguration-xd75.html# Write-Verbose "Adding machine configuration to the Desktop Group: $desktopGroupName"# Add-BrokerMachineConfiguration -AdminAddress $adminAddress -DesktopGroup $desktopGroup -InputObject @(1005)# Add machines to the new desktop group. Uses the number of machines available in the target machine catalog# http://support.citrix.com/proddocs/topic/citrix-broker-admin-v2-xd75/add-brokermachinestodesktopgroup-xd75.htmlWrite-Verbose"Getting details for the Machine Catalog: $machineCatalogName"$machineCatalog=Get-BrokerCatalog-AdminAddress$adminAddress-Name$machineCatalogNameWrite-Verbose"Adding $machineCatalog.UnassignedCount machines to the Desktop Group: $desktopGroupName"$machinesCount=Add-BrokerMachinesToDesktopGroup-AdminAddress$adminAddress-Catalog$machineCatalog-Count$machineCatalog.UnassignedCount-DesktopGroup$desktopGroup# Create a new broker user/group object if it doesn't already exist# http://support.citrix.com/proddocs/topic/citrix-broker-admin-v2-xd75/new-brokeruser-xd75.htmlWrite-Verbose"Creating user/group object in the broker for $assignedGroup"If(!(Get-BrokerUser-AdminAddress$adminAddress-Name$assignedGroup-ErrorActionSilentlyContinue)){$brokerUsers=New-BrokerUser-AdminAddress$adminAddress-Name$assignedGroup}Else{$brokerUsers=Get-BrokerUser-AdminAddress$adminAddress-Name$assignedGroup}# Create an entitlement policy for the new desktop group. Assigned users to the desktop group# First check that we have an entitlement name available. Increment until we do.$Num=1Do{# http://support.citrix.com/proddocs/topic/citrix-broker-admin-v2-xd75/test-brokerentitlementpolicyrulenameavailable-xd75.html$Test=Test-BrokerEntitlementPolicyRuleNameAvailable-AdminAddress$adminAddress-Name@($desktopGroupName+"_"+$Num.ToString())-ErrorActionSilentlyContinueIf($Test.Available-eq$False){$Num=$Num+1}}While($Test.Available-eq$False)#http://support.citrix.com/proddocs/topic/citrix-broker-admin-v2-xd75/new-brokerentitlementpolicyrule-xd75.htmlWrite-Verbose"Assigning $brokerUsers.Name to Desktop Catalog: $machineCatalogName"$EntPolicyRule=New-BrokerEntitlementPolicyRule-AdminAddress$adminAddress-Name($desktopGroupName+"_"+$Num.ToString())-IncludedUsers$brokerUsers-DesktopGroupUid$desktopGroup.Uid-Enabled$True-IncludedUserFilterEnabled$False# Check whether access rules exist and then create rules for direct access and via Access Gateway# http://support.citrix.com/proddocs/topic/citrix-broker-admin-v2-xd75/new-brokeraccesspolicyrule-xd75.html$accessPolicyRule=$desktopGroupName+"_Direct"If(Test-BrokerAccessPolicyRuleNameAvailable-AdminAddress$adminAddress-Name@($accessPolicyRule)-ErrorActionSilentlyContinue){Write-Verbose"Allowing direct access rule to the Desktop Catalog: $machineCatalogName"New-BrokerAccessPolicyRule-AdminAddress$adminAddress-Name$accessPolicyRule-IncludedUsers@($brokerUsers.Name)-AllowedConnections'NotViaAG'-AllowedProtocols@('HDX','RDP')-AllowRestart$True-DesktopGroupUid$desktopGroup.Uid-Enabled$True-IncludedSmartAccessFilterEnabled$True-IncludedUserFilterEnabled$True}Else{Write-Error"Failed to add direct access rule $accessPolicyRule. It already exists."}$accessPolicyRule=$desktopGroupName+"_AG"If(Test-BrokerAccessPolicyRuleNameAvailable-AdminAddress$adminAddress-Name@($accessPolicyRule)-ErrorActionSilentlyContinue){Write-Verbose"Allowing access via Access Gateway rule to the Desktop Catalog: $machineCatalogName"New-BrokerAccessPolicyRule-AdminAddress$adminAddress-Name$accessPolicyRule-IncludedUsers@($brokerUsers.Name)-AllowedConnections'ViaAG'-AllowedProtocols@('HDX','RDP')-AllowRestart$True-DesktopGroupUid$desktopGroup.Uid-Enabled$True-IncludedSmartAccessFilterEnabled$True-IncludedSmartAccessTags@()-IncludedUserFilterEnabled$True}Else{Write-Error"Failed to add Access Gateway rule $accessPolicyRule. It already exists."}# Create weekday and weekend access rules# http://support.citrix.com/proddocs/topic/citrix-broker-admin-v2-xd75/new-brokerpowertimescheme-xd75.html$powerTimeScheme="Windows 8 Pooled Desktop_Weekdays"If(Test-BrokerPowerTimeSchemeNameAvailable-AdminAddress$adminAddress-Name@($powerTimeScheme)-ErrorActionSilentlyContinue){Write-Verbose"Adding new power scheme $powerTimeScheme"New-BrokerPowerTimeScheme-AdminAddress$adminAddress-DisplayName'Weekdays'-Name$powerTimeScheme-DaysOfWeek'Weekdays'-DesktopGroupUid$desktopGroup.Uid-PeakHours@($False,$False,$False,$False,$False,$False,$False,$True,$True,$True,$True,$True,$True,$True,$True,$True,$True,$True,$True,$False,$False,$False,$False,$False)-PoolSize@(0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0)}Else{Write-Error"Failed to add power scheme rule $powerTimeScheme. It already exists."}$powerTimeScheme="Windows 8 Pooled Desktop_Weekend"If(Test-BrokerPowerTimeSchemeNameAvailable-AdminAddress$adminAddress-Name@($powerTimeScheme)-ErrorActionSilentlyContinue){Write-Verbose"Adding new power scheme $powerTimeScheme"New-BrokerPowerTimeScheme-AdminAddress$adminAddress-DisplayName'Weekend'-Name$powerTimeScheme-DaysOfWeek'Weekend'-DesktopGroupUid$desktopGroup.Uid-PeakHours@($False,$False,$False,$False,$False,$False,$False,$True,$True,$True,$True,$True,$True,$True,$True,$True,$True,$True,$True,$False,$False,$False,$False,$False)-PoolSize@(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)}Else{Write-Error"Failed to add power scheme rule $powerTimeScheme. It already exists."}}#End If DesktopGroup
Comments or feedback on bugs, better ways to do things or additional steps is welcome.
Driving XenDesktop with PowerShell is a challenge to say the least. While documentation for the XenDesktop PowerShell modules is OK and Citrix Studio outputs PowerShell code after you’ve completed a task in the console, there’s still plenty of work to get that code into something usable.
As part of an ongoing series of articles themed around automating virtual desktop deployment, I’ve written some PowerShell code to automate the creation of an non-persistent, MCS-based Machine Catalog based on a specific Windows image, that we’ve already automated with a solution such as MDT.
Don’t expect to copy and paste the PowerShell output in Citrix Studio and have a complete script. The code is missing a number of lines that link tasks together. I found this article on the Citrix Blogs quite useful - Using PowerShell to Create a Catalog of Machine Creations Services Machines; however I’ve taken my script a few steps further.
Linking the Code to the UI
While the Create Machine Catalog wizard doesn’t expose everything that goes on behind the scenes when a machine catalog is created, I think it’s still worth showing how specific functions relate to choices that the administrator makes in the wizard.
The screenshots below show just a snippet of the functions required to automate the catalog creation using PowerShell. These walkthrough the same environment that the full code listing at the end of this article is creating. See the image captions for example code that applies to each step.
New-BrokerCataog is used to create the machine catalog and set a number of properties. You’ll see New-BrokerCatalog across a number of these screen shots. First up is setting the broker type - in this instance, I’m deploying a Windows 8 image, so need to choose ‘Windows Desktop OS’:
Selecting the Machine Catalog type - New-BrokerCatalog -SessionSupport SingleSession
Because were using MCS, I’m going to specify that I’m using virtual machines and choose the storage on which to deploy those VMs and use the ProvisioningType parameter on New-BrokerCatalog to specify MCS. This is done in PowerShell via a number of commands - see around line 45 where we specify the hypervisor management and storage resource to use.
Selecting the provisioning type - New-BrokerCatalog -ProvisioningType MCS
Also on the New-BrokerCatalog, we can specify that this is a set of randomly assigned desktops.
Selecting Random or Static desktops - New-BrokerCatalog -AllocationType Random
To find the image to use, I’ve obtained the path to the master image and its snapshot via the Get-ChildItem command (on the path XDHyp:\HostingUnits<Storage Resource>) and passed that to New-ProvScheme.
Selecting the master image and snapshot to use - New-ProvScheme -ProvisioningSchemeName “Windows 8” -HostingUnitName “HV1-LocalStorage” -MasterImageVM “XDHyp:\HostingUnits\HV1-LocalStorage\WIN81.vm\MasterImage.snapshot”
Also with New-ProvScheme we can set the number of virtual CPUs and the amount of RAM to assign to each virtual desktop. To specify the number of desktops to create, we’re actually first specifying the number of AD machine accounts to create via New-AcctADAccount and then creating the same number of desktops to assign to those accounts.
New-AcctIdentityPool is used to create an identity pool that stores the machine accounts by specifying the naming convention and where the accounts will be stored.
Again we can see where New-BrokerCataog is used to specify the catalog name and description.
Setting the machine catalog name and description - New-BrokerCatalog -Name “Windows 8 x86” -Description “Windows 8.1 x86 SP1 with Office 2013”
There’s plenty that the wizard does to hide the complexity of setting up a catalog from the administrator. If you attempt the same via PowerShell, what goes on under the hood is laid bare.
The Code
Below is the full code listing with comments inline that should provide some detail on the process the code follows. At this point the code provides some error checking for the most important steps. There are still some additional steps and error checking that could be integrated:
This code should find the last snapshot of the target master image; it would be simple enough to specify a particular snapshot if required
Checking whether provisioning schemes are already available or exist before attempting to create a new provisioning scheme
Additional checking that some tasks have completed successfully before continuing
#---------------------------------------------------------------------------# Author: Aaron Parker# Desc: Using PowerShell to create a XenDesktop 7.x machine catalog # Date: Aug 19, 2014# Site: http://stealthpuppy.com#---------------------------------------------------------------------------# Set variables for the target infrastructure# ----------$adminAddress='xd71.home.stealthpuppy.com'#The XD Controller we're going to execute against$xdControllers='xd71.home.stealthpuppy.com'# Hypervisor and storage resources# These need to be configured in Studio prior to running this script# This script is hypervisor and management agnostic - just point to the right infrastructure$storageResource="HV1-LocalStorage"#Storage$hostResource="Lab SCVMM"#Hypervisor management# Machine catalog properties$machineCatalogName="Windows 8 x86"$machineCatalogDesc="Windows 8.1 x86 SP1 with Office 2013"$domain="home.stealthpuppy.com"$orgUnit="OU=MCS Pooled,OU=Workstations,DC=home,DC=stealthpuppy,DC=com"$namingScheme="W8-MCS-###"#AD machine account naming conventions$namingSchemeType="Numeric"#Also: Alphabetic$allocType="Random"#Also: Static$persistChanges="Discard"#Also: OnLocal, OnPvD$provType="MCS"#Also: Manual, PVS$sessionSupport="SingleSession"#Also: MultiSession$masterImage="WIN81*"$vCPUs=2$VRAM=2048# ----------# Change to SilentlyContinue to avoid verbose output$VerbosePreference="Continue"# Load the Citrix PowerShell modulesWrite-Verbose"Loading Citrix XenDesktop modules."Add-PSSnapinCitrix*# Get information from the hosting environment via the XD Controller# Get the storage resourceWrite-Verbose"Gathering storage and hypervisor connections from the XenDesktop infrastructure."$hostingUnit=Get-ChildItem-AdminAddress$adminAddress"XDHyp:\HostingUnits"|Where-Object{$_.PSChildName-like$storageResource}|Select-ObjectPSChildName,PsPath# Get the hypervisor management resources$hostConnection=Get-ChildItem-AdminAddress$adminAddress"XDHyp:\Connections"|Where-Object{$_.PSChildName-like$hostResource}$brokerHypConnection=Get-BrokerHypervisorConnection-AdminAddress$adminAddress-HypHypervisorConnectionUid$hostConnection.HypervisorConnectionUid$brokerServiceGroup=Get-ConfigServiceGroup-AdminAddress$adminAddress-ServiceType'Broker'-MaxRecordCount2147483647# Create a Machine Catalog. In this case a catalog with randomly assigned desktopsWrite-Verbose"Creating machine catalog. Name: $machineCatalogName; Description: $machineCatalogDesc; Allocation: $allocType"$brokerCatalog=New-BrokerCatalog-AdminAddress$adminAddress-AllocationType$allocType-Description$machineCatalogDesc-Name$machineCatalogName-PersistUserChanges$persistChanges-ProvisioningType$provType-SessionSupport$sessionSupport# The identity pool is used to store AD machine accountsWrite-Verbose"Creating a new identity pool for machine accounts."$identPool=New-AcctIdentityPool-AdminAddress$adminAddress-Domain$domain-IdentityPoolName$machineCatalogName-NamingScheme$namingScheme-NamingSchemeType$namingSchemeType-OU$orgUnit# Creates/Updates metadata key-value pairs for the catalog (no idea why).Write-Verbose"Retrieving the newly created machine catalog."$catalogUid=Get-BrokerCatalog|Where-Object{$_.Name-eq$machineCatalogName}|Select-ObjectUid$guid=[guid]::NewGuid()Write-Verbose"Updating metadata key-value pairs for the catalog."Set-BrokerCatalogMetadata-AdminAddress$adminAddress-CatalogId$catalogUid.Uid-Name'Citrix_DesktopStudio_IdentityPoolUid'-Value$guid# Check to see whether a provisioning scheme is already availableWrite-Verbose"Checking whether the provisioning scheme name is unused."If(Test-ProvSchemeNameAvailable-AdminAddress$adminAddress-ProvisioningSchemeName@($machineCatalogName)){Write-Verbose"Success."# Get the master VM image from the same storage resource we're going to deploy to. Could pull this from another storage resource available to the hostWrite-Verbose"Getting the master image details for the new catalog: $masterImage"$VM=Get-ChildItem-AdminAddress$adminAddress"XDHyp:\HostingUnits\$storageResource"|Where-Object{$_.ObjectType-eq"VM"-and$_.PSChildName-like$masterImage}# Get the snapshot details. This code will assume a single snapshot exists - could add additional checking to grab last snapshot or check for no snapshots.$VMDetails=Get-ChildItem-AdminAddress$adminAddress$VM.FullPath# Create a new provisioning scheme - the configuration of VMs to deploy. This will copy the master image to the target datastore.Write-Verbose"Creating new provisioning scheme using $VMDetails.FullPath"# Provision VMs based on the selected snapshot.$provTaskId=New-ProvScheme-AdminAddress$adminAddress-ProvisioningSchemeName$machineCatalogName-HostingUnitName$storageResource-MasterImageVM$VMDetails.FullPath-CleanOnBoot-IdentityPoolName$identPool.IdentityPoolName-VMCpuCount$vCPUs-VMMemoryMB$vRAM-RunAsynchronously$provTask=Get-ProvTask-AdminAddress$adminAddress-TaskId$provTaskId# Track the progress of copying the master imageWrite-Verbose"Tracking progress of provisioning scheme creation task."$totalPercent=0While($provTask.Active-eq$True){Try{$totalPercent=If($provTask.TaskProgress){$provTask.TaskProgress}Else{0}}Catch{}Write-Progress-Activity"Creating Provisioning Scheme (copying and composing master image):"-Status"$totalPercent% Complete:"-percentcomplete$totalPercentSleep15$provTask=Get-ProvTask-AdminAddress$adminAddress-TaskID$provTaskId}# If provisioning task fails, there's no point in continuing further.If($provTask.WorkflowStatus-eq"Completed"){# Apply the provisioning scheme to the machine catalogWrite-Verbose"Binding provisioning scheme to the new machine catalog"$provScheme=Get-ProvScheme|Where-Object{$_.ProvisioningSchemeName-eq$machineCatalogName}Set-BrokerCatalog-AdminAddress$adminAddress-Name$provScheme.ProvisioningSchemeName-ProvisioningSchemeId$provScheme.ProvisioningSchemeUid# Associate a specific set of controllers to the provisioning scheme. This steps appears to be optional.Write-Verbose"Associating controllers $xdControllers to the provisioning scheme."Add-ProvSchemeControllerAddress-AdminAddress$adminAddress-ControllerAddress@($xdControllers)-ProvisioningSchemeName$provScheme.ProvisioningSchemeName# Provisiong the actual machines and map them to AD accounts, track the progress while this is happeningWrite-Verbose"Creating the machine accounts in AD."$adAccounts=New-AcctADAccount-AdminAddress$adminAddress-Count5-IdentityPoolUid$identPool.IdentityPoolUidWrite-Verbose"Creating the virtual machines."$provTaskId=New-ProvVM-AdminAddress$adminAddress-ADAccountName@($adAccounts.SuccessfulAccounts)-ProvisioningSchemeName$provScheme.ProvisioningSchemeName-RunAsynchronously$provTask=Get-ProvTask-AdminAddress$adminAddress-TaskId$provTaskIdWrite-Verbose"Tracking progress of the machine creation task."$totalPercent=0While($provTask.Active-eq$True){Try{$totalPercent=If($provTask.TaskProgress){$provTask.TaskProgress}Else{0}}Catch{}Write-Progress-Activity"Creating Virtual Machines:"-Status"$totalPercent% Complete:"-percentcomplete$totalPercentSleep15$ProvTask=Get-ProvTask-AdminAddress$adminAddress-TaskID$provTaskId}# Assign the newly created virtual machines to the machine catalog$provVMs=Get-ProvVM-AdminAddress$adminAddress-ProvisioningSchemeUid$provScheme.ProvisioningSchemeUidWrite-Verbose"Assigning the virtual machines to the new machine catalog."ForEach($provVMin$provVMs){Write-Verbose"Locking VM $provVM.ADAccountName"Lock-ProvVM-AdminAddress$adminAddress-ProvisioningSchemeName$provScheme.ProvisioningSchemeName-Tag'Brokered'-VMID@($provVM.VMId)Write-Verbose"Adding VM $provVM.ADAccountName"New-BrokerMachine-AdminAddress$adminAddress-CatalogUid$catalogUid.Uid-MachineName$provVM.ADAccountName}Write-Verbose"Machine catalog creation complete."}Else{# If provisioning task fails, provide error# Check that the hypervisor management and storage resources do no have errors. Run 'Test Connection', 'Test Resources' in Citrix StudioWrite-Error"Provisioning task failed with error: [$provTask.TaskState] $provTask.TerminatingError"}}
Comments or feedback on bugs, better ways to do things or additional steps is welcome.
I’ve previously posted about retrieving the UUID from a virtual machine hosted on vSphere. UUIDs are useful if you want to uniquely identify a target machine for OS deployment task sequences and the like (e.g. MDT). Here’s how to obtain the UUID from a virtual machine hosted on Hyper-V.
Just like with vSphere, the UUID isn’t a property of the virtual machine that can be queried directly. We need to go via WMI to query the target virtual machine. Note that in this function, I’m using version 2 of the Root\Virtualization WMI namespace (root\virtualization\v2. This means the function as written, will only work on Windows 8 and Windows Server 2012 (and above). If you want to use this function on earlier versions of Hyper-V, remove the “\v2” from the namespace.
As an example, here’s how to retrieve the UUIDs from a set of VMs on a target Hyper-V host named hv1:
Here’s the full function code listing. Please let me know if you find any bugs:
#---------------------------------------------------------------------------# Author: Aaron Parker# Desc: Function that uses retrieves the UUID from a specified VM and# formats it into the right format for use with MDT/SCCM etc# Date: Aug 18, 2014# Site: http://stealthpuppy.com#---------------------------------------------------------------------------FunctionGet-HypervVMUUID{<#
.SYNOPSIS
Retrieve the UUID from a virtual machine or set of virtual machines.
.DESCRIPTION
This function will retrieve the UUID from from a virtual machine or set of virtual machines from a Hyper-V host.
.PARAMETER ComputerName
Specifies the host from which to query the virtual machine or set of virtual machines.
.PARAMETER VM
Specifies the virtual machine or set of virtual machines (a comma delimited list) from which to obtain the UUID/s.
.EXAMPLE
PS C:\> Get-HypervVMUUID -ComputerName hv1 -VM win71, win72
This command retrieves the UUIDs from the virtual machines win71 and win72 from the host hv1.
.EXAMPLE
PS C:\> Get-HypervVMUUID -VM win71, win72
This command retrieves the UUIDs from the virtual machines win71 and win72 from the local host.
.EXAMPLE
PS C:\> Get-HypervVMUUID
This command retrieves the UUIDs from the all of the virtual machines on the local host.
.NOTES
/retrieving-a-vms-uuid-from-hyperv/ for support information.
.LINK
/retrieving-a-vms-uuid-from-hyperv/
#>[cmdletbinding(SupportsShouldProcess=$True)]param([Parameter(Mandatory=$false,HelpMessage="Specifies one or more Hyper-V hosts from which virtual machine UUIDs are to be retrieved. NetBIOS names, IP addresses, and fully-qualified domain names are allowable. The default is the local computer — use ""localhost"" or a dot (""."") to specify the local computer explicitly.")][string]$ComputerName,[Parameter(Mandatory=$false,Position=0,HelpMessage="Specifies the virtual machine from which to retrieve the UUID.")][string[]]$VM)# If ComputerName parameter is not specified, set value to the local hostIf(!$ComputerName){$ComputerName="."}# If VM parameter is specified, return those VMs, else return all VMsIf($VM){$UUIDs=Get-VM-ComputerName$ComputerName-VM$VM-ErrorActionSilentlyContinue|Select-ObjectName,@{Name="BIOSGUID";Expression={(Get-WmiObject-ComputerName$_.ComputerName-Namespace"root\virtualization\v2"-ClassMsvm_VirtualSystemSettingData-PropertyBIOSGUID-Filter("InstanceID = 'Microsoft:{0}'"-f$_.VMId.Guid)).BIOSGUID}}}Else{$UUIDs=Get-VM-ComputerName$ComputerName-ErrorActionSilentlyContinue|Select-ObjectName,@{Name="BIOSGUID";Expression={(Get-WmiObject-ComputerName$_.ComputerName-Namespace"root\virtualization\v2"-ClassMsvm_VirtualSystemSettingData-PropertyBIOSGUID-Filter("InstanceID = 'Microsoft:{0}'"-f$_.VMId.Guid)).BIOSGUID}}}# Remove curly brackets from the UUIDs and return the arrayForEach($UIDin$UUIDs){$UID.BIOSGUID=$UID.BIOSGUID-replace"}";$UID.BIOSGUID=$UID.BIOSGUID-replace"{"}Return$UUIDs}
Running VMs under ESXi will incur a memory overhead for each virtual machine. You can read about this overhead here: Understanding Memory Overhead. Essentially memory overhead is:
There’s typically not too much that you can do to reduce the size of your master image. You might use application virtualization or layering solutions to reduce the number of master images, but once you work out what needs to go into the core image, that’s going to dictate the size of the image.
In my lab environment, I often want to start a list of virtual machines, but without taxing the system in the process by starting them all at the same time. I could do that manually, but that’s no fun.
Here’s a short function I wrote to sequentially start a list of virtual machines - the script will start a VM and wait for that VM to boot before starting the next VM. You can optionally also wait additional time before starting the next VM to give the first one some time to finish starting it’s services etc.
This version currently supports Hyper-V only. The script does not currently return anything, but has a number of parameters:
ComputerName - the name of the Hyper-V host. Specify “.” for the local machine (without quotes)
VM - specify a comma separated list of VMs
Wait - the number of seconds to wait between starting a VM after the previous VM. Specify the number of VMs as a number (integer) only. This will default to 180 seconds
ShowProgress - Specify whether to show progress while starting the VMs. This is cosmetic only, but does give some indication as to how far through the boot process the script is.
Other standard parameters such as Verbose are supported.
FunctionStart-SequentialVMs{<#
.SYNOPSIS
Starts a list of VMs.
.DESCRIPTION
This function starts a list of VMs sequentially. It will wait until a VM is booted, optionally pause for a number of seconds, before starting the next VM.
.PARAMETER ComputerName
Specifies the Hyper-V host to start the VM on.
.PARAMETER VM
Specifies a list of VMs to start.
.PARAMETER Wait
Specifies a number of seconds to wait after the previous VM has booted successfully. Defaults to 180 seconds.
.PARAMETER ShowProgress
Specified whether to show progress as VMs are started.
.EXAMPLE
Start-SequentialVMs -ComputerName hyperv1 -VMList "sql1", "pvs1", "xd71" -Wait 20
.NOTES.LINK
#>param([Parameter(Mandatory=$true,Position=0,HelpMessage="Hyper-V host.")][string]$ComputerName=$(throw="Please specify a remote Hyper-V host to start VMs on."),[Parameter(Mandatory=$true,Position=1,HelpMessage="List of VMs to start.")][string[]]$VMList=$(throw="Please specifiy a list of VMs to start"),[Parameter(Mandatory=$false)][int]$Wait=180,[Parameter(Mandatory=$false)][bool]$ShowProgress)# Connect to Hyper-V host before attempting to start VMs. Stop script if unable to connectWrite-Verbose"Connecting to VM host."Get-VMHost-ComputerName$ComputerName-Verbose$False-ErrorActionStop# Start progress at 0$Percent=0# Step through list of provided VMsForEach($vmin$VMList){# Convert current location in list of VMs to a percentage$Percent=($VMList.IndexOf($vm)/$VMList.Count)*100# Show progress if specified on the command lineIf($ShowProgress-eq$True){Write-Progress-Activity"Starting VMs."-Status"Starting VM $vm."-PercentComplete$Percent}# Get status for current VMRemove-VariablecurrentVM-ErrorActionSilentlyContinueWrite-Verbose"Getting status for VM $vm..."$currentVM=Get-VM-ComputerName$ComputerName-Name$vm-ErrorActionSilentlyContinue# If the VM exists, then power it on if it is in an Off stateIf($currentVM.Length-gt0){If($currentVM.State-eq"Off"){Start-VM-ComputerName$ComputerName-Name$vm-Verbose# Wait for VM to boot and report a heartbeatWrite-Verbose"Waiting for VM heartbeat."Do{Start-Sleep-milliseconds100}Until((Get-VMIntegrationService$currentVM|?{$_.name-eq"Heartbeat"}).PrimaryStatusDescription-eq"OK")# Wait the specified number of seconds before booting the next VM, unless this is the last VM in the listIf($Wait-gt0-and$VMList.IndexOf($vm)-lt($VMList.Count-1)){Write-Verbose"Waiting for $Wait seconds before starting next VM."Start-Sleep-Seconds$Wait}}Else{Write-Verbose"VM $vm already running."}}Else{Write-Error-Message"Unable to find VM $vm on host $ComputerName."-CategoryObjectNotFound}}Write-Verbose"Started VMs."# Show progress if specified on the command lineIf($ShowProgress-eq$True){Write-Progress-Activity"Starting VMs."-Status"Started all VMs."-PercentComplete100}Start-Sleep-Seconds1}
Save the script as Start-SequentialVMs.ps1 and run it or add the function to your PowerShell profile so that the function is available when starting PowerShell. Use Get-Help to see the full syntax and examples from within a PowerShell window.
Update: August 8, 2015 - Microsoft have discontinued the Microsoft Download Manager as of March 2015, so the below workload will no longer work. If fact there are no workaround now, you’ll need to rely on your browser’s download manager. See this article for more info: Using Subscriber Downloads