Moving VDI Deployments: PowerShell Samples

This post is part of a series of posts showing how you might move an existing VDI deployment to Azure Virtual Desktop on Azure Stack HCI.

This particular post contains the Powershell script examples used as part of the blog series, with two code samples:

  1. ControlWrapper.txt – this is the core engine PowerShell that I cut and pasted into a PowerShell window to actually run the migration
  2. RemoteAVDInstall.PS1 – this script was called by the “ControlWrapper.txt” to install the AVD Agent and Bootloader by executing Invoke-Command -FilePath $ScriptPath -ComputerName $ADComputer.DNSHostName

ControlWrapper.txt

# Remote commands on VMs run by enabling WinRM as detailed here:  https://woshub.com/enable-winrm-management-gpo/
# all required files assumed to be in $WorkDIR
#################################################################################
#Make sure you have the necessary PowerShell modules installed and available:
#################################################################################
#Install-Module AZ
#Install-Module -Name Microsoft.RDInfra.RDPowerShell
#Install-Windowsfeature Hyper-V-PowerShell
#Install-Windowsfeature Hyper-V-Tools		

################################
# RDS and HCI Settings 
################################
$RDSBroker 	= "RDSInfra.gbbgov.us"
$HCI_VM_Share	= "\\fslogix\VMs"
$HCI_VM_path	= "C:\ClusterStorage\Volume1\shares\VMs"
$HCI_Node	= "HCI.gbbgov.us"
$WorkPath	= "\\rdsinfra\installs"
$ScriptPath	= $WorkPath + "\RemoteAVDInstall.PS1"
$WorkDIR 	= $WorkPath + "\*.*"
$BootLoader 	= "Microsoft.RDInfra.RDAgentBootLoader.Installer-x64.msi"
$AVDAgent 	= "Microsoft.RDInfra.RDAgent.Installer-x64-1.0.7255.1400.msi"
$AVDHostPool	= "migration"
$PoolKeyFile 	= "HostpoolRegistrationKey.txt"

$RegistryPath = "HKLM:\Software\Microsoft\RDInfraAgent"
$KeyPath = "HKLM:\Software\Microsoft\RDInfraAgent\RegistrationToken"

################################
# Azure Settings 
################################
$AZResourceGroup 	= "WVD"
$AZSubscriptionID	= "11111111-1111-1111-1111-111111111111"
$AZLocation		= "USGov Virginia"
#added below for working with Azure Gov
#$ISGov			= $false
$ISGov			= $true

################################
# Grab Start date & time	
################################
$StartDate = get-date

##################################################################################
#  Log Into Azure to (later) assign user to their previously assigned VMs in AVD
##################################################################################
if ($ISGov) {
	Connect-AzAccount -EnvironmentName AzureUSGovernment 
} else { 
	Connect-AzAccount 
}
#Select the target subscription for the current session
Select-AzSubscription -SubscriptionId $AZSubscriptionID

##################################################################################
#  Generate / Download New Hostpool Registration Key (have host pool already!)
##################################################################################
#$KeyExpirationTime = $((Get-Date).ToUniversalTime().AddDays(20).ToString('yyyy-MM-ddTHH:mm:ss.fffffffZ'))
#New-AzWvdRegistrationInfo -ExpirationTime $KeyExpirationTime -HostPoolName $AVDHostPool -ResourceGroupName $AZResourceGroup -SubscriptionId $AZSubscriptionID
$PoolKey = Get-AzWvdRegistrationInfo -HostPoolName $AVDHostPool -ResourceGroupName $AZResourceGroup -SubscriptionId $AZSubscriptionID
$PoolKeyPath = $WorkPath + "\" + $PoolKeyFile 
$PoolKey.token | Out-file -FilePath $PoolKeyPath

##################################################################################
#  Collect RDS Deployment information
##################################################################################
$Collection = Get-RDRemoteDesktop -ConnectionBroker $RDSBroker
# get the User / VM mapping
$Details = Get-RDPersonalVirtualDesktopAssignment -CollectionName $Collection.CollectionName -ConnectionBroker $RDSBroker
Write "VMs to be migrated (with assigned users)"
$details

# find the Hyper-V Hosts use for Personal VMs
$Servers = Get-RDServer -Role "RDS-Virtualization" -ConnectionBroker $RDSBroker
#I only have one host... so...
$Server = $Servers[0]

##################################################################################
#  Migrate ASSIGNED VMs from RDS Virtualization Host to HCI
##################################################################################
foreach ($Item in $Details)
{
	$VM = get-VM -Name $Item.VirtualDesktopName -Computername $Server.Server
	#$VM = get-VM -Name "Per-10" -Computername $Server.Server
	$VMName = $VM.name
	$ADComputer = get-ADComputer $VM.name
	write "#######################################"
	write "# Processing:   $VMname"
	write "#######################################"

	write "#######################################"
	write "#Start VM if it is Saved 
	write "#######################################"
	if($VM.State -eq "Saved")
	{
		Start-VM -Name $VMName -Computername $Server.Server 
		Start-Sleep -seconds 30  #let VM wake up	
	}

	write "#######################################"
	write "#Stop VM if it is Running
	write "#######################################"
	if($VM.State -eq "Running")
	{
		Stop-VM -Name $VMName -Computername $Server.Server -Force
	}
	
	write "#######################################"
	write "#Export VM from RDS Virt Host to HCI Cluster"
	write "#######################################"
	Export-VM -Name $VMName -Computername $Server.Server -Path $HCI_VM_Share
	$VM_find_location = $HCI_VM_Share + "\" + $VMName + "\*.vmcx"
	$File = get-childitem -path $VM_find_location -Recurse
	$FileLocation = $HCI_VM_Path + "\" + $VMName + "\" + $File.name

	write "#######################################"
	write "#Import VM $VMname in HCI Cluster $HCI_Node"
	write "#######################################"
	Invoke-Command -ComputerName $HCI_Node -ScriptBlock {
		Get-ChildItem -Path $Using:FileLocation -Recurse | Import-VM -Register | Get-VM | Add-ClusterVirtualMachineRole
	}
	
	write "#######################################"
	write "#Start VM $VMName on HCI Cluster $HCI_Node"
	write "#######################################"
	Start-VM -Name $VMName -Computername $HCI_Node 
	Start-Sleep -seconds 60  #let VM wake up

	write "#######################################"
	write "#Copy install files to local VM $VMname"
	write "#######################################"
Pause
	$DestinationPath = -Join("\\", $ADComputer.DNSHostName, "\C$\Installs")
	write "Destination path:  $Destinationpath"

	If (!(test-path $DestinationPath)) { md $DestinationPath }
	Copy-Item $WorkDIR -Destination $DestinationPath

	write "#######################################"
	write "#  Call Local Agent Install Script on $VMname"
	write "#######################################"
	Invoke-Command -FilePath $ScriptPath -ComputerName $ADComputer.DNSHostName

	write "#######################################"
	write "#  Assign User to AVD VM $VMname"
	write "#######################################"
	$User = $item.user -split "\\"
	#$User = $item[0].user -split "\\"
	$ADUser = get-ADuser -Identity $user[1]

	###################################################################
	Start-Sleep -seconds 60  #Wait for VM to be registered in AVD host pool (may need longer)
	###################################################################
	Update-AzWvdSessionHost -HostPoolName $AVDHostPool -ResourceGroupName $AZResourceGroup -SubscriptionId $AZSubscriptionID -Name $ADComputer.DNSHostName -AssignedUser $ADUser.UserPrincipalName

	write "#######################################"
	write "# Remove VM $VMname from RDS deployment"
	write "#######################################"
	Remove-RDVirtualDesktopFromCollection -CollectionName $Collection.CollectionName -VirtualDesktopName @($VMName) -ConnectionBroker $RDSBroker -Force
}

$EndDate = get-date
$executionminutes =  ($EndDate - $Startdate).minutes
write "#######################################"
Write "Start time:	$startdate"
Write "Elapsed minutes:	$executionminutes"
write "#######################################"

RemoteAVDInstall.PS1

# Remote commands on VMs run by enabling WinRM as detailed here:  https://woshub.com/enable-winrm-management-gpo/
# all required files assumed to be in $WorkDIR

$WorkDIR 	= "C:\installs\"
$PoolKeyFile 	= "HostpoolRegistrationKey.txt"

#set paths for files
$PoolKeyFile 	= -join($WorkDIR, $PoolKeyFile)
$RegistryPath = "HKLM:\Software\Microsoft\RDInfraAgent"
$KeyPath = "HKLM:\Software\Microsoft\RDInfraAgent\RegistrationToken"

#Get the Host Poll Registration key from the downloaded file
$Key = Get-Content -Path $PoolKeyFile -Raw

#  AVD Agent install process
#  https://learn.microsoft.com/en-us/azure/virtual-desktop/troubleshoot-agent#step-4-reinstall-the-agent-and-boot-loader

# Jam Host Pool Key into registry
If (-NOT (Test-Path $RegistryPath)) { New-Item -Path $RegistryPath }  

# Install required agents and restart bootloader
Invoke-Command -ScriptBlock {&cmd.exe /c MSIEXEC /I C:\Installs\AVDAgent.msi /qn}
Invoke-Command -ScriptBlock {&cmd.exe /c MSIEXEC /I C:\Installs\Bootloader.msi /qn}
Set-ItemProperty -Path $RegistryPath -Name "RegistrationToken" -Value $Key
restart-service -Name RDAgentBootLoader