Windows Virtual Desktop - Cleanup
Cleanup time
Recently we’de deployed and set up basic monitoring with Azure App Insight and Log Analytics.
As production environment is up and running - time to destroy the PoC environment.
It’s always easy to say - clean it up
. I prefer to PLAN and write down (in a form of a checklist) all steps BEFORE I dive into. That helps in case you’re interrupted, but also serves as a basic documentation
.
Together with deployment steps
it’s a good start .
So let’s get it started. What needs to be done?
- All Sessions that are currently open will be shutdown.
- RDS application group removed.
- All hosts removed from Host Pool.
- Host Pool removed.
- RDS Tenant removed.
- Azure AD application account removed.
- Resource group with all resources - removed as well.
- AD computer objects with DNS and DHCP entries - removed.
- Entries in monitoring system (in our case Zabbix) also cleaned up.
I’m not removeing assigned licenses, as we use Windows E5 not only for WVD.
Automate
I’ll be destroying a PoC environment, but in some time - probably production WVD will go down. That is why I prefer to automate it a bit.
WVD tenant
Let’s first take down the WVD tenant. I’m using Out-GridView -PassThru
as interactive interface for scripts. I want to remove PoC environment, not the production one
Install-module microsoft.RDInfra.RDPowerShell
Import-Module Microsoft.RDInfra.RDPowerShell
Add-RDSAccount -DeploymentUrl https://rdbroker.wvd.microsoft.com
#Gather information about our current RDS tenant
$currentRDSTenant = Get-RDSTenant | Out-GridView -PassThru
$CurrentRDSHostPool = Get-RDSHostPool -TenantName $currentRDSTenant.TenantName
$CurrentRDSSessionHost = Get-RdsSessionHost -TenantName $currentRDSTenant.TenantName -HostPoolName $CurrentRDSHostPool.HostPoolName
$CurrentRDSAppGroup = Get-RdsAppGroup -TenantName $currentRDSTenant.TenantName -HostPoolName $CurrentRDSHostPool.HostPoolName
#Remove any RD sessions users may still have
$CurrentRDSSessions = Get-RdsUserSession -TenantName $currentRDSTenant.TenantName -HostPoolName $CurrentRDSHostPool.HostPoolName
$CurrentRDSSessions | ForEach-Object {
$RdsUserSessionLogoffProperties = @{
TenantName = $currentRDSTenant.TenantName
HostPoolName = $CurrentRDSHostPool.HostPoolName
SessionHostName = $PSItem.SessionHostName
SessionId = $PSItem.SessionId
NoUserPrompt = $true
}
Invoke-RdsUserSessionLogoff @RdsUserSessionLogoffProperties
}
#Remove RDS Application Group, Session Hosts, Pool and finally tenant
$CurrentRDSAppGroup | Remove-RdsAppGroup
$CurrentRDSSessionHost | Remove-RdsSessionHost -Force
$CurrentRDSHostPool | Remove-RdsHostPool
$currentRDSTenant | Remove-RDSTenant
Azure AD Application
Now, delete the Azure AD Application we used to configure WVD:
Import-Module AzureAD
$aadContext = Connect-AzureAD
$DisplayName = "Windows Virtual Desktop Svc Principal"
$currentAzureADApplication = Get-AzureADApplication -All $true | Where-Object { $_.DisplayName -eq $DisplayName }
$currentAzureADApplication | Remove-AzureADApplication
Azure Resource Group
After this, Resource Group will not exist anymore . Bear in mind this may take a while to complete:
#region Remove Resource Group
Connect-AzureRmAccount
$Subscription = Get-AzureRmSubscription | Out-GridView -PassThru | Select-AzureRmSubscription
$resourceGroup = Get-AzureRmResourceGroup | Out-GridView -PassThru
Remove-AzureRmResourceGroup -Name $ResourceGroup.ResourceGroupName
#endregion
Active Directory
Now, some finishing touches in Active Directory. Thare are still some computer objects, DNS and DHCP entries to take care of. Again, I’ll be using Out-GridView, so that Operator can confirm what to delete.
Your environment may vary and you may want to include other steps in here or remove some of the existing.
This is a
quick-and-dirty
solution to simplify the removal in OUR environment.
We create ‘{ComputerName}-Admins’ and a few other AD groups for each AD Computer to better manage ACLs. I’ll hunt every group that looks like ‘Computer-*’, Out-GridView it for the Operator to select which ones should be removed.
In the end - we’ll connect to Zabbix (if selected so in the variables section) and hunt for any entries.
#region Variables section
$WVDVMsNamePrefix = 'WVD1'
$FilterString = 'Name -like "{0}*"' -f $WVDVMsNamePrefix
$VMsToRemove = Get-ADComputer -filter $FilterString | Out-GridView -PassThru
$DNSServer = Get-ADDomainController | Select-Object -ExpandProperty HostName # Or provide other DNS
$DHCPServers = Get-DhcpServerInDC | select-Object -ExpandProperty DNSName | Out-GridView -PassThru # Or provide other DHCP
$IncludeZabbix = $true # $true, $false
$AdditionalDNSRecords = $null # @('somename') #if there are any additional dns records to hunt and delete
$Domain = 'contoso.com'
$ZabbixURI = 'https://zabbix.{0}/zabbix/api_jsonrpc.php' -f $Domain
$serverprops = @{
ComputerName = $DNSServer
ZoneName = $Domain
}
#endregion
#region RunTime section
foreach ($ComputerName in $VMsToRemove.Name) {
#region DNS Entries
$DNSrecords = Get-DnsServerResourceRecord @serverprops | Where-Object { $_.HostName -match $ComputerName } | Out-GridView -PassThru
if ($DNSrecords) {
$DNSrecords | ForEach-Object {
Write-Host "Removing DNS entry {$($PSItem.HostName)} - IP {$($PSItem.RecordData.IPv4Address.ToString())} - record type {$($PSItem.RecordType)}"
Remove-DnsServerResourceRecord @serverprops -Name $PSItem.HostName -RRType $PSItem.RecordType -confirm:$false
}
}
#endregion
#region DNS delete additional records matching criteria
if ($null -ne $AdditionalDNSRecords) {
$AdditionalDNS = foreach ($dnsRecord in $AdditionalDNSRecords) {
Get-DnsServerResourceRecord @serverprops | where { $PSItem.hostName -match $AdditionalDNSRecord } | Out-GridView -PassThru
}
if ($AdditionalDNS) {
$AdditionalDNS | ForEach-Object {
Write-Host "Removing DNS entry {$($PSItem.HostName)} - IP {$($PSItem.RecordData)} - record type {$($PSItem.RecordType)}"
Remove-DnsServerResourceRecord @serverprops -Name $PSItem.HostName -RRType $PSItem.RecordType -confirm:$false
}
}
}
#endregion
#region DHCP entries
foreach ($dhcpComputer in $DHCPServers) {
$dhcpScopes = Get-DhcpServerv4Scope -computername $dhcpComputer
$DHCPLeases = $dhcpScopes | ForEach-Object { Get-DhcpServerv4Lease -ComputerName $dhcpComputer -ScopeId $PSItem.ScopeID | Where-Object { $PSItem.HostName -match $ComputerName } } | Out-GridView -PassThru
if ($DHCPLeases) {
foreach ($dhcplease in $DHCPLeases) {
if ($dhcplease.AddressState -match 'Reservation') {
Write-Host "Removing DHCP Reservation {$($dhcplease.HostName)} - IP {$($dhcplease.IPAddress)} - address state {$($dhcplease.AddressState)}"
Remove-DhcpServerv4Reservation -ComputerName $dhcpComputer -ScopeId $dhcplease.scopeid -ClientId $dhcplease.ClientID -Confirm:$false -PassThru
}
else {
Write-Host "Removing DHCP Lease {$($dhcplease.HostName)} - IP {$($dhcplease.IPAddress)} - address state {$($dhcplease.AddressState)}"
Remove-DhcpServerv4Lease -ComputerName $dhcpComputer -ScopeId $dhcplease.scopeid -ClientId $dhcplease.ClientID -Confirm:$false -PassThru
}
}
}
}
#endregion
#region Delete Computer Object
$ComputerObject = Get-ADComputer -filter { Name -like $ComputerName } | Out-GridView -PassThru
if ($ComputerObject) {
Write-Host "Found AD Object for Computer {$computerName} - {$($ComputerObject.Name)} with state {$($ComputerObject.Enabled)}"
$ComputerObject | foreach-object {
Write-Host " Removing Computer Object {$($PSItem.Name)}"
$PSItem | Get-ADObject | ForEach-Object {
Remove-ADObject -Recursive -Identity $PSItem
}
}
}
else {
Write-Host "No Computer Objects found for Computer {$ComputerName}"
}
#endregion
#region Remove Groups
Write-Host "Enumerating groups for computer [$ComputerName]"
$FilterString = 'Name -like "{0}-*"' -f $ComputerName
$ComputerGroups = Get-ADGroup -filter $FilterString | Out-GridView -Title 'Found Group Records' -PassThru
if ($ComputerGroups) {
$ComputerGroups | ForEach-Object {
Write-Host "Removing ADGroup {$($PSItem.Name)} with DN {$($PSItem.DistinguishedName)}"
$PSItem | Remove-ADGroup -Confirm:$false
}
}
else {
Write-Host "No Groups found for Computer {$ComputerName}"
}
#endregion
#region remove from Zabbix
if ($IncludeZabbix) {
Write-Host "Processing Zabbix for computer {$ComputerName}"
if (-not ($zabbixSession)) {
Import-Module PSZabbix
$zabbixSession = New-ZbxApiSession $ZabbixURI (Get-Credential $env:Username -Message "Provide Zabbix credentials")
}
#region cleanup zabbix
$zbxHost = Get-ZbxHost -Session $zabbixSession | Where-Object { $PSItem.Name -match "$computername" } | Out-GridView -PassThru
if ($zbxHost) {
Write-Host " Removing host {$ComputerName} from zabbix - hostID {$($zbxHost.HostId)}"
Remove-ZbxHost -Session $zabbixSession -HostId $zbxHost.hostid
}
else {
Write-Host " No Zabbix host found for Computer {$ComputerName}"
}
#endregion
}
#endregion
#endregion
}
Summary
After this, we should have all the objects deleted. If I forgot about something - please let me know!
Leave a comment