Skip to content

Instantly share code, notes, and snippets.

@srMrktng

srMrktng/New PVS Devices.ps1 Secret

Last active Mar 2, 2021
Embed
What would you like to do?
Create new Citrix PVS devices from a VMware vSphere template and optionally add to a Citrix machine catalogue and delivery group and optionally add to a published desktop.
#requires -version 3
## TODO - Create new VM and copy a specified disk or just clone an existing VM, possibly with changed resources
<#
.SYNOPSIS
Create new Citrix PVS devices from a VMware vSphere template and optionally add to a Citrix machine catalogue and delivery group and optionally add to a published desktop.
.DESCRIPTION
.PARAMETER vmName
Name of the new VM. Use # where more than one is being created (via -count) where # will be replaced by digits.
Use the number of hashes for the number of digits, eg GLPVS### for a machine like GLPVS042
.PARAMETER templateName
Name/pattern of unique vSphere template VM which will be used to create the new PVS VMs
.PARAMETER diskName
Name of PVS disk to assign
.PARAMETER ExistingPVSDevice
Name of existing PVS device to use as template such as the site, collection, OU and disk
.PARAMETER OU
The OU to place the new VMs in, in the a slash delimited format, eg "Sites/Wakefield/Computers/RDS/PVS" from "OU=PVS,OU=RDS,OU=Computers,OU=Wakefield,OU=Sites,DC=guyrleech,DC=local"
.PARAMETER count
Number of new VMs to create
.PARAMETER create
Create the named machine catalog, delivery group, site or collection if they do not exist otherwise an error will be thrown on non-existence
.PARAMETER retrySeconds
Retry period in seconds between attempts to check for the properties of a new VM in the hypervisor PS drive
.PARAMETER retries
Number of retries to check for the properties of a new VM in the hypervisor PS drive
.PARAMETER nopad
Do not pad the digits in the new VM name to match the number of # characters specified
.PARAMETER tagName
Name of optional Citrix tag to assign to new VMs. Will be created if non-existent and -create specified
.PARAMETER tagDescription
Description of optional Citrix tag to assign to new VMs, if the tag does not exist and -create is specified
.PARAMETER startNumber
The starting number for the new VM names. If not specified, the script will figure it out by checking AD, PVS, Studio and VMware
.PARAMETER pvsServer
The PVS server to use, otherwise the local machine will be used. If credentials are needed as opposed to using those for the user running the script, use -pvscredential
.PARAMETER ddc
The Delivery Controller to use
.PARAMETER viServers
The VMware vCenter server(s) to connect to
.PARAMETER hypervisorConnectionName
The name of the hypervisor connection in Studio. If not specified, the script will figure it out if -ExistingPVSDevice is specified
.PARAMETER description
Description text for the new VM and PVS device. Environment variables denoted with % will be expanded.
.PARAMETER collectionName
The name of the PVS collection to use, or create if -create specified if -ExistingPVSDevice is not specified
.PARAMETER folderName
The name of the VMware folder to place the VM in
.PARAMETER resourcePoolName
The name of the VMware resource pool to place the VM in
.PARAMETER siteName
The name of the PVS site to use, or create if -create specified if -ExistingPVSDevice is not specified
.PARAMETER catalog
The name of the Studio machine catalog to use, or create if -create specified if -ExistingPVSDevice is not specified
.PARAMETER deliveryGroup
The name of the Studio delivery group to use, or create if -create specified if -ExistingPVSDevice is not specified
.PARAMETER domain
The name of the AD domain to use if -ExistingPVSDevice is not specified
.PARAMETER networkName
The name of the VMware network to use the NIC attached to. Use if the VM template has more than one NIC, on different networks
.PARAMETER noNewVM
Do not create new VMs. They must already exist if this option is specified.
.PARAMETER maintenanceMode
Add the new machines to Studio with maintenance mode enabled
.PARAMETER desktopName
The name of a published desktop to add the new VMs to, or create if -create is specified
.PARAMETER desktopKind
The type of desktops served from a delivery group if -create is specified and it does not exist
.PARAMETER deliveryType
The type of desktops served from a delivery group if -create is specified and it does not exist
.PARAMETER sessionSupport
The session support to set for a delivery group and/or machine catalog if -create is specified and they do not exist
.PARAMETER allocationType
The allcoation type for machines in the machine catalog if -create is specified and it does not exist
.PARAMETER PersistUserChanges
Whether to persist user changes for the machine catalog specified if -create is specified and it does not exist
.PARAMETER vmwarecredential
Credentials to be used for the VMware vCenter connection otherwise the user running the script is used
.PARAMETER pvscredential
Credentials to be used for the PVS connection otherwise the user running the script is used
.EXAMPLE
& '.\New PVS Devices.ps1' -vmName GLXA19PVS## -ExistingPVSDevice GLXA19PVS39 -templateName 'PVS Server 2019' -pvsServer GRL-PVS02 -ddc GRL-XADDC02
Create 1 new VM from the 'PVS Server 2019' vSphere template, naming it with the first available non-existent name matching GLXA19PVS* using the same PVS properties as the existing device GLXA19PVS39 on PVS server GRL-PVS02 such as the collection and PVS disk assigned.
Once created, add it to the same machine catalog and delivery group as existing device GLXA19PVS39 via the delivery controller GRL-XADDC02
.EXAMPLE
& '.\New PVS Devices.ps1' -vmName GLXA19PVS## -templateName 'PVS Server 2019' -pvsServer GRL-PVS02 -startNumber 20 -count 5 -ddc GRL-XADDC02 -diskName xaserver2019 -OU "Sites/Wakefield/Computers/RDS/PVS" -viServers grl-vcenter04 -deliveryGroup Automated -siteName Primary -collectionName Automated -catalog Automated -description "Created by @guyrleech" -tagName "Machines created by script" -folderName "Citrix\PVS\NonProd Servers"
Create 5 new VMs from the 'PVS Server 2019' vSphere template on vCenter grl-vcenter04, naming them with the first available non-existent name from GLXA19PVS20 onwards. Create the corresponding PVS target devices in the PVS site "Primary" and collection "Automated" on PVS server GRL-PVS02 and assign the PVS disk "xaserver2019".
Once created, add it to the "Automated" machine catalog and "Automated" delivery group via the delivery controller GRL-XADDC02 and tag the machines with the tag ""Machines created by script"
.NOTES
Modification History:
08/02/2021 @guyrleech First version
01/03/2021 @guyrleech VMware folder error checking and finding folder if path contains \. Creates folders if -create specified. Fix for not getting hypervisor connection for non-FQDN
#>
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
Param
(
[Parameter(Mandatory=$true,HelpMessage="Name of new VM")]
[string]$vmName ,
[Parameter(Mandatory=$true,HelpMessage="Name/pattern of unique vSphere template VM")] ## TODO could add ability to create new VM either of specified CPU, RAM & a copy of a peristent disk or get this from a "template" but not using vSphere templating to create
[string]$templateName ,
[Parameter(Mandatory=$true,HelpMessage="Name of PVS disk to assign",ParameterSetName='Disk')]
[string]$diskName ,
[Parameter(Mandatory=$true,HelpMessage="Name of existing PVS device to use as template",ParameterSetName='ExistingPVSDevice')]
[string]$ExistingPVSDevice ,
[string]$OU ,
[int]$count = 1 ,
[switch]$create ,
[double]$retrySeconds = 10 ,
[int]$retries = 9 ,
[switch]$nopad ,
[string]$tagName ,
[string]$tagDescription ,
[int]$startNumber ,
[string]$pvsServer ,
[string]$ddc ,
[string[]]$viServers ,
[string]$hypervisorConnectionName ,
[string]$description = "Citrix PVS machine",
[string]$collectionName ,
[string]$folderName ,
[string]$resourcePoolName,
[string]$siteName ,
[string]$catalog ,
[string]$deliveryGroup ,
[string]$domain ,
[System.Management.Automation.PSCredential]$vmwarecredential ,
[System.Management.Automation.PSCredential]$pvscredential ,
[string]$networkName ,
[switch]$maintenanceMode ,
[string]$desktopName ,
[ValidateSet('Shared','Private')]
[string]$desktopKind = 'Shared' ,
[ValidateSet('AppsOnly','DesktopsOnly','DesktopsAndApps')]
[string]$deliveryType = 'DesktopsOnly' ,
[ValidateSet('SingleSession','MultiSession')]
[string]$sessionSupport = 'MultiSession' ,
[ValidateSet('Random','Permanent','Static')]
[string]$allocationType = 'Random' ,
[ValidateSet('Discard','OnLocal','OnPvd')]
[string]$PersistUserChanges = 'Discard' ,
[switch]$noNewVM
)
[string]$pvsInstallFolder = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Citrix\ProvisioningServices' -Name ConsoleTargetDir -ErrorAction SilentlyContinue)|Select-Object -ExpandProperty ConsoleTargetDir -ErrorAction SilentlyContinue
if( [string]::IsNullOrEmpty( $pvsInstallFolder ) )
{
Write-Warning -Message "Failed to find PVS console install folder in the registry"
$pvsInstallFolder = "$env:ProgramFiles\Citrix\Provisioning Services Console"
}
elseif( ! ( Test-Path -Path $pvsInstallFolder -PathType Container -ErrorAction SilentlyContinue ) )
{
Write-Warning -Message "PVS console install folder `"$pvsInstallFolder`" not found"
$pvsInstallFolder = "$env:ProgramFiles\Citrix\Provisioning Services Console"
}
[string]$pvsModule = Join-Path -Path $pvsInstallFolder -ChildPath 'Citrix.PVS.SnapIn.dll'
$PVSSession = $null
if( ( ! ( Test-Path -Path $pvsModule -PathType Leaf -ErrorAction SilentlyContinue ) -or ! ( Import-Module -Name $pvsModule -Verbose:$false -PassThru ) -or ! ( Get-Command -Name Set-PvsConnection -ErrorAction SilentlyContinue ) ) -and $PSBoundParameters[ 'pvsServer' ] -and $pvsServer -ne $env:COMPUTERNAME )
{
## if we don't have the PVS cmdlets we need, see if we can import from PVS server
if( $PVSSession = New-PSSession -ComputerName $pvsServer )
{
Invoke-Command -Session $PVSSession -ScriptBlock { Import-Module $using:pvsModule }
if( ! ( Import-PSSession -Session $PVSSession -Module 'Citrix.PVS.SnapIn' ) )
{
Throw "Failed to import PVS module from $pvsServer"
}
}
else
{
Throw "Unable to create remote session to $pvsServer to import PVS cmdlets"
}
}
[string[]]$citrixModules = @( 'Citrix.Broker.*' , 'Citrix.Host.*' )
ForEach( $citrixModule in $citrixModules )
{
if( ! ( Import-Module -Name $citrixModule -ErrorAction SilentlyContinue -PassThru -Verbose:$false) `
-and ! ( Add-PSSnapin -Name $citrixModule -ErrorAction SilentlyContinue -PassThru -Verbose:$false) )
{
## No point doing implicit remoting since wouldn't get the XDHyp: PSDrive which we need
Throw "Failed to load Citrix PowerShell cmdlets from $citrixModule - is this a Delivery Controller or have Studio or the PowerShell SDK installed ?"
}
}
Import-Module -Name VMware.VimAutomation.Core -Verbose:$false
if( ! ( Get-Command -Name Get-Template ) )
{
Throw 'Unable to find VMware PowerCLI cmdlets'
}
# may need this later if we have to discover the vCenter from the hypervisor connection
[hashtable]$vmwareParameters = @{ Server = $null ; Verbose = $false }
if( $PSBoundParameters[ 'vmwarecredential' ] )
{
$vmwareParameters += @{ User = ($vmwarecredential.UserName -split '\\')[-1] ; Domain = ($vmwarecredential.UserName -split '\\')[0] ; Password = $vmwarecredential.GetNetworkCredential().Password }
}
if( $PSBoundParameters[ 'viServers' ] )
{
$vmwareParameters.Server = $viServers
if( ! ( $vmwareConnection = Connect-VIServer @vmwareParameters ) )
{
Throw "Failed to connect to VMware $viServers"
}
}
if( $PSBoundParameters[ 'pvsserver' ] )
{
[hashtable]$pvsParameters = @{ Server = $pvsServer ; Verbose = $false }
if( $PSBoundParameters[ 'pvscredential' ] )
{
$pvsParameters += @{ User = ($pvscredential.UserName -split '\\')[-1] ; Domain = ($pvscredential.UserName -split '\\')[0] ; Password = $pvscredential.GetNetworkCredential().Password }
}
Set-PvsConnection @pvsParameters
if( ! $? )
{
Exit 1
}
}
[hashtable]$pvsdeviceParameters = @{ }
[hashtable]$ddcParams = @{ }
if( $PSBoundParameters[ 'ddc' ] )
{
$ddcParams.Add( 'Adminaddress' , $ddc )
}
$existingBrokerMachine = $null
$existingHostedMachine = $null
$existingVM = $null
[GUID]$HypervisorConnectionUid = [GUID]::Empty
## if we have a template device then get its properties so we can use them for the new VM
if( $PsCmdlet.ParameterSetName -eq 'ExistingPVSDevice' )
{
$searcher = [adsisearcher]"(&(objectCategory=Computer)(name=$ExistingPVSDevice))"
if( ! ( $machine = $searcher.FindAll() ) )
{
Throw "Unable to find machine $ExistingPVSDevice in AD"
}
elseif( $machine -is [array] )
{
Throw "Found $($machine.Count) AD computers matching $ExistingPVSDevice"
}
if( ! ( $pvsDevice = Get-PvsDevice -DeviceName $ExistingPVSDevice ) )
{
Throw "Unable to find machine $ExistingPVSDevice in PVS"
}
elseif( $pvsdevice -is [array] )
{
Throw "Found $($pvsdeviceParameters.Count) PVS devices matching $ExistingPVSDevice"
}
if( ! ( $pvsDisk = Get-PvsDiskInfo -DeviceName $ExistingPVSDevice ) )
{
Throw "Unable to find PVS disk for target device $ExistingPVSDevice"
}
$pvsdeviceParameters.Add( 'SiteName' , $pvsDisk.siteName )
$pvsdeviceParameters.Add( 'CollectionName' , $pvsDevice.CollectionName )
$storeName = $pvsDisk.StoreName
$diskName = $pvsDisk.DiskLocatorName
$siteName = $pvsDisk.SiteName
if( ! $PSBoundParameters[ 'OU' ] )
{
## LDAP://CN=GLXA19PVS01,OU=PVS,OU=RDS,OU=Computers,OU=Wakefield,OU=Sites,DC=guyrleech,DC=local
## need to turn into Sites/Wakefield/Computers/RDS/PVS for Add-PvsDeviceToDomain
[string[]]$fields = $machine[0].Path -split ','
$OU = $( For( [int]$index = $fields.Count - 1 ; $index -ge 0 ; $index-- )
{
if( $fields[ $index ] -match '^OU=(.*)$' )
{
$Matches[1]
}
}) -join '/'
}
if( ! ( $existingBrokerMachine = Get-BrokerMachine @ddcParams -HostedMachineName $ExistingPVSDevice -ProvisioningType PVS -ErrorAction SilentlyContinue ) )
{
Throw "Failed to find existing broker machine $ExistingPVSDevice"
}
if( ! $PSBoundParameters[ 'viServer' ] )
{
## need to get the vcenters for the hosting connection
if( ! ( $hypervisorConnection = Get-BrokerHypervisorConnection -Uid $existingBrokerMachine.HypervisorConnectionUid ) `
-or ! ( $connectionProperties = Get-ChildItem -Path 'XDHyp:\Connections' -Verbose:$false @ddcParams | Where-Object PSChildName -eq $existingBrokerMachine.HypervisorConnectionName ) )
{
Throw "Failed to connection details for hypervisor connection `"$($existingBrokerMachine.HypervisorConnectionName)`" via PS drive"
}
$vmwareParameters.Server = $connectionProperties.hypervisorAddress -replace '^https?://([^/]+)/(.*)$' , '$1'
if( ! ( $vmwareConnection = Connect-VIServer @vmwareParameters ) )
{
Throw "Failed to connect to VMware $viServers via hypervisor connection `"$($existingBrokerMachine.HypervisorConnectionName)`""
}
}
$HypervisorConnectionUid = $existingBrokerMachine.HypHypervisorConnectionUid.Guid
if( ! ( $existingVM = Get-VM -Name $ExistingPVSDevice ) )
{
Throw "Unable to find VM $ExistingPVSDevice"
}
}
else
{
if( $PSBoundParameters[ 'Domain' ] )
{
$pvsdeviceParameters.Add( 'Domain' , $domain )
}
[array]$allPVSSites = @( Get-PvsSite )
if( ! $allPVSSites -or ! $allPVSSites.Count )
{
Throw "No PVS sites found"
}
if( $PSBoundParameters[ 'siteName' ] )
{
[array]$matchingSites = @( $allPVSSites.Where( { $_.Name -eq $siteName } ) )
if( ! $matchingSites -or ! $matchingSites.Count )
{
Throw "No PVS site `"$siteName`" found"
}
elseif( $matchingSites.Count -ne 1 )
{
Throw "$($matchingSites.Count) PVS sites found matching `"$siteName`" found - must be unique"
}
}
elseif( $allPVSSites.Count -eq 1 )
{
$siteName = $allPVSSites[0].Name
}
else
{
Throw "$($allPVSSites.Count) PVS sites found - must specify unique site name via -sitename"
}
$pvsdeviceParameters.Add( 'SiteName' , $siteName )
[array]$allPVSCollections = @( Get-PvsCollection )
if( ! $allPVSCollections -or ! $allPVSCollections.Count )
{
Throw "No PVS Collections found"
}
if( $PSBoundParameters[ 'CollectionName' ] )
{
[array]$matchingCollections = @( $allPVSCollections.Where( { $_.Name -eq $CollectionName } ) )
if( ! $matchingCollections -or ! $matchingCollections.Count )
{
if( $create -and $siteName )
{
if( ! ( $newCollection = New-PvsCollection -CollectionName $collectionName -SiteName $siteName -Description "Created by script $(Get-Date -Format G)" ) )
{
Throw "Failed to create PVS collection `"$collectionName`" in site `"$siteName`""
}
}
else
{
Throw "No PVS Collection `"$CollectionName`" found"
}
}
elseif( $matchingCollections.Count -ne 1 )
{
Throw "$($matchingCollections.Count) PVS Collections found matching `"$CollectionName`" found - must be unique"
}
}
elseif( $allPVSCollections.Count -eq 1 )
{
$CollectionName = $allPVSCollections[0].Name
}
else
{
Throw "$($allPVSCollections.Count) PVS Collections found - must specify unique Collection name via -Collectionname"
}
$pvsdeviceParameters.Add( 'CollectionName' , $CollectionName )
[array]$disks = @( Get-PvsDiskLocator -SiteName $siteName )
if( ! $disks -or ! $disks.Count )
{
Throw "No PVS disks found in site $siteName"
}
[array]$matchingDisks = @( $disks.Where( { $_.Name -eq $diskName } ) )
[string]$storeName = $null
if( ! $matchingDisks -or ! $matchingDisks.Count )
{
Throw "Found no disk in site $siteName for $diskName"
}
elseif( $matchingDisks.Count -ne 1 )
{
Throw "Found $($matchingDisks.Count) disks in site $siteName for $diskName"
}
else
{
$storeName = $matchingDisks[0].StoreName
}
## if no OU specified see if we have enough info to select a unique one otherwise error
if( ! $PSBoundParameters[ 'OU' ] )
{
$OUs = New-Object -TypeName System.Collections.Generic.List[string]
Get-PvsDevice -DiskLocatorName $matchingdisks[0].DiskLocatorName -SiteName $sitename -StoreName $storename | Where-Object { $_.DomainName -ne $null -and ( ! $domain -or $_.DomainName -eq $domain ) } | . { Process `
{
$device = $_
$searcher = [adsisearcher]"(&(objectCategory=Computer)(name=$($device.DeviceName)))"
Try
{
if( $machine = $searcher.FindOne() )
{
$OUs.Add( ($machine.Path -split ',' , 2)[-1] )
}
}
Catch
{
Throw $_
}
}}
if( ! $OUs -or ! $OUs.Count )
{
Throw "Unable to find any OUs from existing devices - use -OU"
}
$groups = Group-Object -InputObject $OUs
if( $groups.Count -ne 1 )
{
Throw "Failed to find unique OU, found $($groups.Count) ($($groups | Select-Object -ExpandProperty Name))"
}
[string[]]$fields = $OUs[0] -split ','
$OU = $( For( [int]$index = $fields.Count - 1 ; $index -ge 0 ; $index-- )
{
if( $fields[ $index ] -match '^OU=(.*)$' )
{
$Matches[1]
}
}) -join '/'
}
## need to figure out the vCenter connection uid if not specified
$hypervisor = $null
if( ! $PSBoundParameters[ 'hypervisorConnectionName' ] )
{
## connection may not use fqdn so need to match either
[string[]]$nonFQDNviServers = $viServers | ForEach-Object { ( $_ -split '\.' )[0] }
ForEach( $connection in (Get-ChildItem -Path 'XDHyp:\Connections' @ddcParams))
{
ForEach( $hypervisorAddress in $connection.hypervisorAddress )
{
if( $hypervisorAddress -match '^https?://([^/]+)/' )
{
if( $Matches[1] -in $viServers -or $Matches[1] -in $nonFQDNviServers )
{
if( $hypervisor )
{
Write-Warning -Message "Already found $viServers in hypervisor connection `"$($hypervisor.Name)`" and now in `"$($connection.Name)`""
}
$hypervisor = $connection
$HypervisorConnectionUid = $hypervisor.HypervisorConnectionUid
}
}
}
}
if( $HypervisorConnectionUid -eq [GUID]::Empty )
{
Throw "Failed to find hypervisor connect for $($viServers -join ' , ')"
}
}
else
{
if( ! ( $hypervisor = Get-BrokerHypervisorConnection -Name $hypervisorConnectionName @ddcParams | Where-Object HypHypervisorType -eq 'vcenter' ) )
{
Throw "Failed to find hypervisor connection `"$hypervisorConnectionName`""
}
elseif( $hypervisor -is [array] -and $hypervisor.Count -gt 1 )
{
Throw "Found $($hypervisor.Count) hypervisor connections for `"$hypervisorConnectionName`""
}
if( ! $hypervisor -or ! ( $HypervisorConnectionUid = $hypervisor.HypervisorConnectionUid.Guid ) )
{
Throw "Failed to get hypervisor connection uid"
}
}
}
## if we are creating more than 1 item then we need a pattern for the name that we can figure out what are the next machines to create (doesn't exist in PVS, AD, Citrix or VMware)
$newMachines = New-Object -TypeName System.Collections.Generic.List[string]
if( $count -gt 1 -or $vmName -match '#' )
{
if( $vmName -notmatch '^([^#]*)(#+)([^#]*)$' )
{
Throw "Must have # characters in machine name when creating more than one"
}
[string]$padding = '0' * $Matches[2].Length
[string]$prefix = $Matches[1]
[string]$suffix = $Matches[3]
[string]$machinePattern = "$prefix*"
if( $suffix.Length )
{
$machinePattern += "$suffix*"
}
[hashtable]$ADMachines = @{}
[hashtable]$pvsDevices = @{}
[hashtable]$brokerMachines = @{}
[hashtable]$VMs = @{}
$searcher = [adsisearcher]"(&(objectCategory=Computer)(name=$machinePattern))"
$searcher.FindAll() | . { Process `
{
$ADMachines.Add( [string]$_.properties.name , $_.properties )
}}
Write-Verbose -Message "Got $($ADMachines.Count) AD computers matching $machinePattern"
Get-VM -Name $machinePattern -ErrorAction SilentlyContinue | . { Process `
{
$VMs.Add( $_.Name , $_ )
}}
Write-Verbose -Message "Got $($VMs.Count) VMs matching $machinePattern"
Get-BrokerMachine -HostedMachineName $machinePattern @ddcParams | . { Process `
{
$brokerMachines.Add( $_.HostedMachineName , $_ )
}}
Write-Verbose -Message "Got $($brokerMachines.Count) broker machines matching $machinePattern"
## can't pattern match with Get-PVsDevice
[hashtable]$existingPVSdeviceParameters = $pvsdeviceParameters.Clone()
$existingPVSdeviceParameters.Remove( 'Description' )
Get-PvsDevice @existingPVSdeviceParameters | . { Process `
{
if( $_.DeviceName -like $machinePattern )
{
$pvsDevices.Add( $_.DeviceName , $_ )
}
}}
Write-Verbose -Message "Got $($pvsDevices.Count) PVS devices matching $machinePattern"
## Find first machine available if no $startNumber specified
[string]$lastMachine = $null
[int]$machineNumber = $(if( $PSBoundParameters[ 'startNumber' ] ) { $startNumber } else { 1 })
do
{
[string]$thisMachine = "{0}{1:$padding}{2}" -f $prefix , $machineNumber , $suffix
if( $nopad -or $thisMachine.Length -eq $vmname.Length )
{
Write-Verbose -Message "Trying machine $thisMachine - got $($newMachines.Count) so far"
if( ! $ADMachines[ $thisMachine ] -and ! $VMs[ $thisMachine ] -and ! $pvsDevices[ $thisMachine ] -and ! $brokerMachines[ $thisMachine ] )
{
Write-Verbose -Message "$thisMachine not found so adding"
$newMachines.Add( $thisMachine )
$lastMachine = $thisMachine
}
$machineNumber++
}
else
{
Throw "Unable to find available machine names - got $($newMachines.Count) out of $count but now got $thisMachine which doesn't match pattern $vmname, last was $lastMachine"
}
} while( $newMachines.Count -lt $count ) ## TODO how do we detect machine exhaustion? String length?
}
else
{
$newMachines.Add( $vmName )
}
Write-Verbose -Message "Got list of $($newMachines.Count) machines:"
$newMachines | Write-Verbose
[int]$newBrokerMachineCount = 0
$tag = $null
$newCatalog = $null
## TODO alternative to using vSphere templates
if( ! ( $template = Get-Template -Name $templateName ) )
{
Throw "Unable to find template $templateName"
}
if( $template -is [array] )
{
Throw "$($template.Count) templates matched $templateName"
}
$results = New-Object -TypeName System.Collections.Generic.List[psobject]
ForEach( $newMachine in $newMachines )
{
$result = $null
if( $noNewVM -or ! $PSCmdlet.ShouldProcess( $newMachine , 'Create VM' ) )
{
if( ! ( $newVM = Get-VM -Name $newMachine ) )
{
Throw "Unable to find VM $newMachine"
}
}
else
{
$resourcePool = $null
if( $PSBoundParameters[ 'resourcePoolName' ] )
{
$resourcePool = Get-ResourcePool -Name $resourcePoolName
}
else
{
if( $existingVM )
{
$resourcePool = $existingVM.ResourcePool
}
else
{
$resourcePool = (Get-ResourcePool|Select-Object -First 1)
}
}
$folder = $null
if( $PSBoundParameters[ 'folderName' ] )
{
[string]$parent = 'vm' ## root folder
$folder = Get-Folder -Name $parent -ErrorAction Stop
ForEach( $folderElement in $folderName.Split( '\' ) )
{
if( ! [string]::IsNullOrEmpty( $folderElement ) )
{
if( ! ($folder = Get-Folder -Name $folderElement -Location $parent -ErrorAction SilentlyContinue ))
{
if( $create )
{
if( ! ( $folder = New-Folder -Name $folderElement -Location $parent ) )
{
Throw "Failed to create sub folder `"$folderElement`" of parent `"$parent`" of folder path `"$folderName`""
}
}
else
{
Throw "Unable to find sub folder `"$folderElement`" of parent `"$parent`" of folder path `"$folderName`""
}
}
$parent = $folderElement
}
}
if( $folder -is [array] -and $folder.Count -gt 1 )
{
Throw "Found $($folder.Count) folders `"$folderName`" - `"$($folders.Name -join '" , "' )`""
}
}
else
{
if( $existingVM )
{
$folder = $existingVM.folder
}
}
if( ! $PSBoundParameters[ 'description' ] )
{
if( $existingVM -and ! [string]::IsNullOrEmpty( $existingVM.Notes ) )
{
$description = $existingVM.Notes
}
}
elseif( $description.IndexOf( '%' ) -ne $description.LastIndexOf( '%' ) )
{
$description = [Environment]::ExpandEnvironmentVariables( $description ) -replace '%time%' , (Get-Date -Form T) -replace '%date%' , (Get-Date -Form d)
}
## TODO make async so we can create several in parallel
try
{
if( ! ( $newVM = New-VM -Template $template -Name $newMachine -Description $description -ResourcePool $resourcePool -Location $folder ) )
{
Throw "Failed to create $newMachine from $($template.Name)"
}
}
catch
{
Throw "Failed to create $newMachine from $($template.Name): $_"
}
}
if( ! ( $NICs = Get-NetworkAdapter -VM $newVM ) )
{
Throw "Failed to get any NICs for VM $($newVM.Name)"
}
if( $NICs -is [array] -and $NICs.Count -gt 1 )
{
if( $theNIC = $NICs | Where-Object NetworkName -match $networkName )
{
if( $theNIC -is [array] )
{
Throw "Found $($theNIC.Count) NICs out of $($NICs.Count) matching $networkName so can't figure which one to set in PVS"
}
else
{
$NICs = $theNIC
}
}
else
{
Throw "Found no NICs out of $($NICs.Count) matching $networkName so can't set MAC address in PVS"
}
}
$pvsdeviceParameters.DeviceMac = $NICs.MacAddress -replace ':' , '-'
$result = $newVM
$newPVSDevice = $null
if( $PsCmdlet.ShouldProcess( $newMachine , 'Create new PVS Device' ) )
{
try
{
$pvsdeviceParameters.Description = $description
$pvsdeviceParameters.DeviceName = $newMachine
$newPVSDevice = New-PvsDevice @pvsdeviceParameters
}
catch
{
Throw "Failed to create new PVS device $($pvsdeviceParameters.DeviceName): $_"
}
if( ! $newPVSDevice )
{
Throw "Failed to create new PVS device $($pvsdeviceParameters.DeviceName)"
}
try
{
Add-PvsDiskLocatorToDevice -DiskLocatorName $diskName -DeviceId $newPVSDevice.DeviceId -SiteName $siteName -StoreName $storeName
}
catch
{
Throw "Failed to add disk $diskname to $($newPVSDevice.DeviceName): $_"
}
$newPVSDevice | Select-Object -Property *name , DeviceMac | ForEach-Object { $_.PSObject.Properties | ForEach-Object { Add-Member -InputObject $result -MemberType NoteProperty -Name $_.Name -Value $_.Value -Force } }
[hashtable]$ADParameters = @{ DeviceName = $newPVSDevice.DeviceName }
if( ! [string]::IsNullOrEmpty( $OU ) )
{
$ADParameters.Add( 'OrganizationUnit' , $OU )
}
if( ! [string]::IsNullOrEmpty( $domain ) )
{
$ADParameters.Add( 'Domain' , $domain )
}
Add-Member -InputObject $result -NotePropertyMembers $ADParameters -Force
try
{
Add-PvsDeviceToDomain @ADParameters
}
catch
{
Throw "Failed to add $($newPVSDevice.DeviceName) to AD: $_"
}
}
if( $PSBoundParameters[ 'catalog' ] )
{
## if catalogue doesn't exist, create if reqested and not already done
if( ! $newCatalog -and ! ( Get-BrokerCatalog -Name $catalog -ErrorAction SilentlyContinue @ddcParams | Where-Object Name -eq $catalog ) ) ## ensure no name matching giving multiple catalogues
{
if( ! $domain )
{
$searcher = [adsisearcher]"(&(objectCategory=Computer)(name=$($newpvsdevice.DeviceName)))"
if( ! ( $ADmachine = $searcher.FindOne() ) )
{
Write-Warning -Message "Failed to find new machine $($newPVSDevice.DeviceName) in AD"
}
else
{
$domain = ($ADmachine.properties.dnshostname -split '\.' , 2)[-1]
}
}
if( $PsCmdlet.ShouldProcess( $catalog , 'Create Machine Catalog' ) )
{
if( ! ( $newCatalog = New-BrokerCatalog -PersistUserChanges $PersistUserChanges -ProvisioningType PVS -MachinesArePhysical $false -Description "Created by script $(Get-Date -Format G)" -SessionSupport $sessionSupport -Name $catalog -AllocationType $allocationType -IsRemotePC $false -PvsAddress $pvsServer -PvsDomain $domain @ddcParams ) )
{
Write-Warning -Message "Failed to create new catalog `"$catalog`""
}
}
}
}
else
{
if( $existingBrokerMachine )
{
$catalog = $existingBrokerMachine.CatalogName
}
}
if( $newPVSDevice -and ! [string]::IsNullOrEmpty( $catalog ) )
{
if( $newCatalog )
{
$brokerCatalog = $newCatalog
}
elseif( ! ( $brokerCatalog = Get-BrokerCatalog -Name $catalog @ddcParams ) )
{
Throw "Failed to get machine catalog `"$catalog`""
}
if( $hypervisorConnection = Get-BrokerHypervisorConnection -HypHypervisorConnectionUid $HypervisorConnectionUid )
{
$hostedMachine = $null
[int]$retry = 0
## had a problem whereby new machines don't appear immediately via PS drive so retry if not seen. Sometimes about a minute before they "appear"
while( ! $hostedMachine -and $retry -lt $retries )
{
if ( $hostedMachine = Get-ChildItem -Path (Join-Path -Path 'XDHyp:\Connections' -ChildPath $hypervisorConnection.Name -Verbose:$false) -Force -Recurse -Include "$($newPVSDevice.DeviceName).vm" -Verbose:$false @ddcParams )
{
if( ! ($newBrokerMachine = New-BrokerMachine -CatalogUid $brokerCatalog.Uid -MachineName $newPVSDevice.DeviceName -InMaintenanceMode $maintenanceMode.IsPresent -HypervisorConnectionUid $HypervisorConnection.Uid -HostedMachineId $hostedMachine.Id @ddcParams ) )
{
Throw "Failed to add $($newPVSDevice.DeviceName) to machine catalog `"$catalog`""
}
else
{
Add-Member -InputObject $result -MemberType NoteProperty -Name 'Machine Catalog' -Value $brokerCatalog.Name
$newBrokerMachineCount++
if( $PSBoundParameters[ 'tagName' ] )
{
if( ! $tag -and ! ( $tag = Get-BrokerTag -Name $tagName -ErrorAction SilentlyContinue @ddcParams ) -and $PsCmdlet.ShouldProcess( $tagName , "Create Tag" ) )
{
[hashtable]$tagParameters = @{ 'Name' = $tagName }
if( $PSBoundParameters[ 'tagDescription' ] )
{
$tagParameters.Add( 'Description' , $tagDescription )
}
$tagParameters += $ddcParams
if( ! ( $tag = New-BrokerTag @tagParameters ) )
{
Write-Warning -Message "Failed to create tag `"$tagName`""
}
}
if( $tag )
{
Add-BrokerTag -InputObject $tag -Machine $newBrokerMachine
if( ! $? )
{
Write-Warning -Message "Failed to add tag `"$($tag.Name)`" to machine $($newbrokermachine.MachineName)"
}
else
{
Add-Member -InputObject $result -MemberType NoteProperty -Name 'Tag' -Value $tagName
}
}
}
}
}
else
{
$retry++
Write-Warning -Message "$(Get-Date -Format G) failed to find hypervisor connection for $($newPVSDevice.DeviceName) - retry $retry in $retrySeconds seconds"
Start-Sleep -Milliseconds ($retrySeconds * 1000)
}
}
if( ! $hostedMachine )
{
Write-Warning -Message "$(Get-Date -Format G) failed to find VM via hypervisor connection for $($newPVSDevice.DeviceName) so unable to add to catalog `"$($brokerCatalog.Name)`""
}
}
else
{
Throw "Failed to get hypervisor connection for uid $HypervisorConnectionUid"
}
}
if( ! $PSBoundParameters[ 'deliveryGroup' ] -and $existingBrokerMachine )
{
$deliveryGroup = $existingBrokerMachine.DesktopGroupName
}
$results.Add( $result )
}
if( ! [string]::IsNullOrEmpty( $deliveryGroup ) )
{
if( ! ( $existingDeliveryGroup = Get-BrokerDesktopGroup -Name $deliveryGroup @ddcParams -ErrorAction SilentlyContinue ) )
{
if( $create )
{
if( $PsCmdlet.ShouldProcess( $deliveryGroup , "Create Delivery Group" ) )
{
if( ! ( $newDeliveryGroup = New-BrokerDesktopGroup -Name $deliveryGroup -Description "Created by $env:username via script $(Get-Date -Format G)" -InMaintenanceMode $maintenanceMode.IsPresent -SessionSupport $sessionsupport -DesktopKind $desktopKind -DeliveryType $deliveryType @ddcParams ) )
{
Write-Warning -Message "Failed to create delivery group `"$deliveryGroup`""
}
else
{
## need to create default access policies otherwise get "The User configuration has been manually modified and cannot be changed by Studio" in Studio and doesn't show to users
## TODO Should look at supporting custom settings but for now set in maintenance mode so not visible to users
if( ! ( $viaAG = New-BrokerAccessPolicyRule -AllowedConnections ViaAG -Name "$($deliveryGroup)_AG" -DesktopGroupUid $newDeliveryGroup.Uid -AllowedProtocols HDX,RDP -AllowedUsers AnyAuthenticated -AllowRestart $true -IncludedSmartAccessFilterEnabled $true -IncludedUserFilterEnabled $true ) )
{
Write-Warning -Message "Failed to create AG policy rule for new desktop group `"$deliveryGroup`""
}
if( ! ( $direct = New-BrokerAccessPolicyRule -AllowedConnections NotViaAG -Name "$($deliveryGroup)_Direct" -DesktopGroupUid $newDeliveryGroup.Uid -AllowedProtocols HDX,RDP -AllowedUsers AnyAuthenticated -AllowRestart $true -IncludedSmartAccessFilterEnabled $true -IncludedUserFilterEnabled $true ) )
{
Write-Warning -Message "Failed to create Non AG policy rule for new desktop group `"$deliveryGroup`""
}
$existingDeliveryGroup = $newDeliveryGroup
}
}
}
else
{
Write-Warning -Message "Failed to find delivery group `"$deliveryGroup`""
}
}
if( $PsCmdlet.ShouldProcess( "Delivery group `"$deliveryGroup`"" , "Add $newBrokerMachineCount machines" ) )
{
Write-Verbose -Message "Adding $newBrokerMachineCount to delivery group `"$deliveryGroup`""
[int]$added = Add-BrokerMachinesToDesktopGroup -DesktopGroup $deliveryGroup -Catalog $catalog -Count $newBrokerMachineCount
if( $added -ne $count )
{
Write-Warning -Message "Added $added machines to delivery group `"$deliveryGroup`" not $count"
}
[hashtable]$deliveryGroupMembers = @{}
ForEach( $deliveryGroupMember in (Get-BrokerMachine -DesktopGroupName $deliveryGroup))
{
$deliveryGroupMembers.Add( ($deliveryGroupMember.MachineName -split '\\')[-1] , $deliveryGroupMember.DesktopGroupName )
}
Write-Verbose -Message "Got $($deliveryGroupMembers.Count) machines in delivery group `"$deliveryGroup`""
ForEach( $item in $results )
{
Add-Member -InputObject $item -MemberType NoteProperty -Name 'Delivery Group' -Value $deliveryGroupMembers[ $item.DeviceName ]
}
if( $PSBoundParameters[ 'desktopName' ] )
{
if( ! ( Get-BrokerEntitlementPolicyRule -DesktopGroupUid $existingDeliveryGroup.Uid -Name $desktopName -ErrorAction SilentlyContinue ) )
{
if( $create )
{
if( $PsCmdlet.ShouldProcess( $desktopName , 'Create Published Desktop' ) )
{
## publish a desktop
[hashtable]$desktopParameters = $ddcParams.Clone()
if( $PSBoundParameters[ 'TagName' ] )
{
$desktopParameters.Add( 'Tag' , $tagName )
}
## TODO could have a parameter to restrict user access via -IncludedUsers
if( ! ( $newPublishedDesktop = New-BrokerEntitlementPolicyRule -Description "Created by $env:username by script $(Get-Date -Format G)" -Name $desktopName -PublishedName $desktopName -DesktopGroupUid $existingDeliveryGroup.Uid -Enabled $true -IncludedUserFilterEnabled $false -IncludedUsers @() @desktopParameters ) )
{
Write-Warning -Message "Failed to create published desktop `"$desktopName`""
}
}
}
else
{
Write-Warning -Message "Desktop `"$desktopName`" does not exist"
}
}
else
{
Write-Warning -Message "Desktop `"$desktopName`" already exists"
}
}
}
}
if( $PVSSession )
{
Remove-PSSession -Session $PVSSession
$PVSSession = $null
}
## because based off a VMware object, it would only output the default properties for that so force it to return all properties
$results | Select-Object -Property *
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment