Using PowerShell to clean up DHCP and DNS for VDI

When you run a virtual desktop infrastructure that builds and deletes hundreds of virtual machines every day, DHCP and DNS might eventually get out of sync and need some cleanup.

When writing this PowerShell cleanup script, I decided to make vCenter my source of truth because has the virtual machine host names, IP addresses, power state (on/off), and even MAC address. With vCenter as my source of truth, my objectives were as follows:

  • In vCenter, note the virtual machine names, IPs, MACs, and power state
  • For powered off VMs, delete their DHCP, DNS Foward, and DNS Reverse records
  • For powered on VMs, if the hostname does not match the IP address in vCenter, delete their DHCP, DNS Foward, and DNS Reverse records
  • Have all powered on VMs renew their DHCP address (e.g. ipconfig /renew)
  • Provide some basic before and after reporting

I comment my script well enough for many to follow it. Some of the Select-Object makes use of some custom column headings, which you can learn about in this PowerShell tip of the week.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# VDI/DHCP/DNS: Clean up DHCP and DNS, removing unused or incorrect records
 
#########################################
# Import Modules
#########################################
 
# Import Required Modules
Import-Module VMware.VimAutomation.Core
Import-Module ActiveDirectory
Import-Module DhcpServer
Import-Module DnsServer
 
#########################################
# Define user-configured variables (admin should make changes here)
#########################################
 
# Scope: What machine names would you like to target?
# $VMNameLike = "VDI-Example-1"
# $VMNameLike = "VDI-Example*"
$VMNameLike = "VDI-*"
 
# Servers
$ServerVCenter = "vcenterservername"
$ServerDHCP = "dhcpservername"
$ServerDNS = "dnsservername"
 
# DHCP Scope and DNS Zones
$DHCPScopeID = "10.10.10.1"
$DNSForwardZone = "example.local"
$DNSReverseZone = "10.10.in-addr.arpa"
 
#########################################
# Begin Script (no need for admin to make changes below this point)
#########################################
 
# Connect to Desktop vCenter
# Connect-VIServer $ServerVCenter
Connect-VIServer -Server $global:DefaultVIServer -Session $global:DefaultVIServer.SessionId
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix
$GetVSphereAll = Get-VM | Where-Object {$_.Name -like $VMNameLike}
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix and are powered On
$GetVSphereOn = Get-VM | Where-Object {$_.Name -like $VMNameLike -AND $_.PowerState -eq "PoweredOn"} | Select-Object Name, @{N="FQDN";E={@($_.Name+".riverview.local")}}, @{N="IPAddress";E={@($_.Guest.IPAddress)}}, @{N="MACColon";E={@($_.Guest.Nics.MacAddress)}}, @{N="MACHyphen";E={@($_.Guest.Nics.MacAddress -Replace ':','-')}}
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix and are powered Off
$GetVSphereOff = Get-VM | Where-Object {$_.Name -like $VMNameLike -AND $_.PowerState -eq "PoweredOff"} | Select-Object Name, @{N="FQDN";E={@($_.Name+".riverview.local")}}
 
# Get AD machines that begin with the $VMNameLike prefix
$GetAD = Get-ADComputer -Filter {Name -like $VMNameLike}
 
# Get DHCP machines that begin with the $VMNameLike prefix
$GetDHCP = Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.HostName -like $VMNameLike} 
 
# Get DNS A records for machines that begin with the $VMNameLike prefix
$GetDNSA = Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -RRType A | Where-Object {$_.HostName -like $VMNameLike} | Select-Object RecordType, @{N="Name";E={@($_.Hostname)}}, @{N="IPv4Address";E={@($_.RecordData.IPv4Address)}}, HostName, RecordData
 
# Get DNS PTR records for machines that begin with the $VMNameLike prefix
$GetDNSPTR = Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -RRType PTR | Where-Object {$_.RecordData.PtrDomainName -like $VMNameLike} | Select-Object RecordType, @{N="Name";E={@($_.RecordData.PtrDomainName -Replace '.riverview.local.','')}}, @{N="IPv4Address";E={@('172.22.'+$_.HostName)}}, HostName, RecordData
 
# Count Results to quickly compare of vCenter, DHCP, and DNS have the same quantity of records
$CountVSphereOnBefore = $GetVSphereOn | Measure-Object | Select-Object -ExpandProperty Count
$CountVSphereOffBefore = $GetVSphereOff | Measure-Object | Select-Object -ExpandProperty Count
$CountVSphereAllBefore = $GetVSphereAll | Measure-Object | Select-Object -ExpandProperty Count
$CountDNSPTRBefore = $GetDNSPTR | Measure-Object | Select-Object -ExpandProperty Count
$CountDNSABefore = ($GetDNSA | Measure-Object | Select-Object -ExpandProperty Count)/2
$CountDHCPBefore = $GetDHCP | Measure-Object | Select-Object -ExpandProperty Count
$CountADBefore = $GetAD | Measure-Object | Select-Object -ExpandProperty Count
 
# Show Before Count Results
# #################################################################################
Write-Host "----------------------------------------------------------------------"
Write-Host "BEFORE making changes, here's some data for you to review"
Write-Host "----------------------------------------------------------------------"
Write-Host $CountVSphereOnBefore "powered on machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereOffBefore "powered off machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereAllBefore "all machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountDNSPTRBefore "machines in DNS Reverse lookup zone named" $VMNameLike "BEFORE making changes"
Write-Host $CountDNSABefore "machines in DNS Forward lookup zone named" $VMNameLike "BEFORE making changes"
Write-Host $CountDHCPBefore "machines in DHCP named" $VMNameLike "BEFORE making changes"
Write-Host $CountADBefore "machines in Active Directory named" $VMNameLike "BEFORE making changes"
# #################################################################################
 
 
#########################################
# MAKE CHANGES for Powered OFF VMs
#########################################
 
# Delete DHCP Leases for Powered Off VMs
#########################################
 
# CHANGE: Remove DHCP Lease for each PoweredOff VMs
foreach ($HostName in $GetVSphereOff) {
	Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.HostName -eq $HostName.FQDN} | Remove-DhcpServerv4Lease -ComputerName $ServerDHCP
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
# Delete DNS A records for Powered Off VMs
#########################################
 
# CHANGE: Remove DNA A record for each PoweredOff VM
foreach ($HostName in $GetVSphereOff) {
	Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -RRType A | Where-Object {$_.HostName -eq $HostName.Name} | Remove-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -Force
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
# Delete DNS PTR records for Powered Off VMs
#########################################
 
# CHANGE: Remove DNA PTR record for each PoweredOff VM
foreach ($HostName in $GetVSphereOff) {
	Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -RRType PTR | Where-Object {$_.RecordData.PtrDomainName -like ($HostName.Name + "*")} | Remove-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -Force
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
#########################################
# MAKE CHANGES for Powered ON VMs
#########################################
 
# Delete DHCP Leases for Powered On VMs that have IPs that don't match vCenter
#########################################
 
# CHANGE: Remove DHCP Lease for mismatched PoweredOn VMs
foreach ($HostName in $GetVSphereOn) {
 
	# If the VM name from vCenter matches the HostName in DHCP, but the IP address does not, delete the DHCP lease
	Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.HostName -eq $HostName.FQDN -AND $_.IPADDRESS -ne $HostName.IPAddress} | Remove-DhcpServerv4Lease -ComputerName $ServerDHCP
 
	# If the VM MAC from vCenter matches the HostName in DHCP, but the IP address does not, delete the DHCP lease
	Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.ClientID -eq $HostName.MACHyphen -AND $_.IPADDRESS -ne $HostName.IPAddress} | Remove-DhcpServerv4Lease -ComputerName $ServerDHCP
 
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
# Delete DNS A records for Powered On VMs that have IPs that don't match vCenter
#########################################
 
# CHANGE: Remove DNA A record for mismatched PoweredOn VMs
foreach ($HostName in $GetVSphereOn) {
	Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -RRType A | Where-Object {$_.HostName -eq $HostName.Name -AND $_.RecordData.IPv4Address -ne $HostName.IPAddress} | Remove-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -Force
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
# Delete DNS PTR records for for Powered On VMs that have IPs that don't match vCenter
#########################################
 
# CHANGE: Remove DNA PTR record for mismatched PoweredOn VMs
foreach ($HostName in $GetVSphereOn) {
 
	# Spilt VM IP address into the last two octets to match DNS PTR HostName
	$IPAddress = $HostName.IPAddress
	$IPAddressOctets = $ipAddress.Split('.')
	$IPAddressLastTwoOctets = $IPAddressOctets[3] + "." + $IPAddressOctets[2]
 
	# Find and delete mismatched records
	Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -RRType PTR | Where-Object {$_.RecordData.PtrDomainName -like ($HostName.Name + "*") -AND $_.HostName -notlike $IPAddressLastTwoOctets} | Remove-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -Force
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
#########################################
# RENEW IP Addresses (e.g. attempt to get DHCP and DNS in sync)
#########################################
 
# REFRESH: Have each powered on vCenter VM ask DHCP to renew its IP address. (this will take 5 to 10 seconds per VM)
foreach ($Computer in $GetVSphereOn) {Invoke-Command -ComputerName $Computer.Name { ipconfig /renew }}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
#########################################
# REPORT on what changed by getting new counts
#########################################
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix
$GetVSphereAll = Get-VM | Where-Object {$_.Name -like $VMNameLike}
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix and are powered On
$GetVSphereOn = Get-VM | Where-Object {$_.Name -like $VMNameLike -AND $_.PowerState -eq "PoweredOn"} | Select-Object Name, @{N="FQDN";E={@($_.Name+".riverview.local")}}, @{N="IPAddress";E={@($_.Guest.IPAddress)}}, @{N="MACColon";E={@($_.Guest.Nics.MacAddress)}}, @{N="MACHyphen";E={@($_.Guest.Nics.MacAddress -Replace ':','-')}}
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix and are powered Off
$GetVSphereOff = Get-VM | Where-Object {$_.Name -like $VMNameLike -AND $_.PowerState -eq "PoweredOff"} | Select-Object Name, @{N="FQDN";E={@($_.Name+".riverview.local")}}
 
# Get AD machines that begin with the $VMNameLike prefix
$GetAD = Get-ADComputer -Filter {Name -like $VMNameLike}
 
# Get DHCP machines that begin with the $VMNameLike prefix
$GetDHCP = Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.HostName -like $VMNameLike} 
 
# Get DNS A records for machines that begin with the $VMNameLike prefix
$GetDNSA = Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -RRType A | Where-Object {$_.HostName -like $VMNameLike} | Select-Object RecordType, @{N="Name";E={@($_.Hostname)}}, @{N="IPv4Address";E={@($_.RecordData.IPv4Address)}}, HostName, RecordData
 
# Get DNS PTR records for machines that begin with the $VMNameLike prefix
$GetDNSPTR = Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -RRType PTR | Where-Object {$_.RecordData.PtrDomainName -like $VMNameLike} | Select-Object RecordType, @{N="Name";E={@($_.RecordData.PtrDomainName -Replace '.riverview.local.','')}}, @{N="IPv4Address";E={@('172.22.'+$_.HostName)}}, HostName, RecordData
 
# Count Results to quickly compare of vCenter, DHCP, and DNS have the same quantity of records
$CountVSphereOnAfter = $GetVSphereOn | Measure-Object | Select-Object -ExpandProperty Count
$CountVSphereOffAfter = $GetVSphereOff | Measure-Object | Select-Object -ExpandProperty Count
$CountVSphereAllAfter = $GetVSphereAll | Measure-Object | Select-Object -ExpandProperty Count
$CountDNSPTRAfter = $GetDNSPTR | Measure-Object | Select-Object -ExpandProperty Count
$CountDNSAAfter = ($GetDNSA | Measure-Object | Select-Object -ExpandProperty Count)/2
$CountDHCPAfter = $GetDHCP | Measure-Object | Select-Object -ExpandProperty Count
$CountADAfter = $GetAD | Measure-Object | Select-Object -ExpandProperty Count
 
# Compare Count Results
# #################################################################################
Write-Host "----------------------------------------------------------------------"
Write-Host "COMPARE before and after counts"
Write-Host "----------------------------------------------------------------------"
Write-Host $CountVSphereOnBefore "powered on machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereOnAfter "powered on machines in vCenter named" $VMNameLike "AFTER making changes"
Write-Host $CountVSphereOffBefore "powered off machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereOffAfter "powered off machines in vCenter named" $VMNameLike "AFTER making changes"
Write-Host $CountVSphereAllBefore "machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereAllAfter "machines in vCenter named" $VMNameLike "AFTER making changes"
Write-Host $CountDNSPTRBefore "machines in DNS Reverse lookup zone named" $VMNameLike "BEFORE making changes"
Write-Host $CountDNSPTRAfter "machines in DNS Reverse lookup zone named" $VMNameLike "AFTER making changes"
Write-Host $CountDNSABefore "machines in DNS Forward lookup zone named" $VMNameLike "BEFORE making changes"
Write-Host $CountDNSAAfter "machines in DNS Forward lookup zone named" $VMNameLike "AFTER making changes"
Write-Host $CountDHCPBefore "machines in DHCP named" $VMNameLike "BEFORE making changes"
Write-Host $CountDHCPAfter "machines in DHCP named" $VMNameLike "AFTER making changes"
Write-Host $CountADBefore "machines in Active Directory named" $VMNameLike "BEFORE making changes"
Write-Host $CountADAfter "machines in Active Directory named" $VMNameLike "AFTER making changes"
# #################################################################################