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" # ################################################################################# |