Support for Windows XP ends on April 8, 2014. Some organizations might not be completely off Windows XP and need a good way to determine the status of those workstations. I combined some PowerShell that I wrote a while back to accomplish this:

Pinging a List of Machines in PowerShell

Get Counts of all Operating Systems in a Forest Per Domain

The purpose of this was to find all Windows XP machines in all domains in the forest and then ping each one to determine if it is online. It only looks at XP machines that have active computer accounts in AD and have changed their computer password within the last 31 days (This happens by default every 30 days). This script can also be used to look for any OS, just search the code for “Windows XP” and specify a new filter.

The script doesn’t take any arguments but you must have RSAT and Excel installed on the machine running the script:

Remote Server Administration Tools (RSAT)

  • For desktop OS’s download RSAT
  • For server OS’s use Add-WindowsFeature RSAT-AD-PowerShell. *Note, the script will try to install this feature if it isn’t already installed, but it only works for server OS’s.

The output of the script will display the XP count per domain but the rich data is in the Excel file that gets generated. There is a tab for each Domain in your environment:

image

Fields

  • DNSHostName: DNS Name from Active Directory
  • CN: CN from Active Directory
  • Name: Name from Active Directory
  • DistinguishedName: DN from Active Directory
  • Description: Description from Active Directory
  • OperationSystem: OS from Active Directory
  • OperatingSystemVersion: OS Version from Active Directory
  • NameFromDNS: DNS name from DNS
  • IPAddressFromDNS: IP retrieved from DNS
  • PingSuccessPercentage: It was pinged 4 times, this is how many were successful

Script

#Functions function ImportADModule { Import-Module ActiveDirectory if (!($?)) { #Only works for Windows Server OS with PS running as admin, download RSAT if using desktop OS Add-WindowsFeature RSAT-AD-PowerShell Import-Module ActiveDirectory } } function GetScriptDirectory { $invocation = (Get-Variable MyInvocation -Scope 1).Value Split-Path $invocation.MyCommand.Path } function GetDN { param($domain) $names = $domain.Split(".") $bFirst = $true foreach ($name in $names) { if ($bFirst) { $dn += "DC=" + $name $bFirst = $false } else { $dn += ",DC=" + $name } } return $dn } function GetDNs { param($domains) $dns = @{} foreach ($domain in $domains) { $dns.Add($domain, (GetDN -domain $domain)) } return $dns } function GetStatusCodeString { param ($code) switch ($code) { $null {$ret = "Ping Command Failed"} 0 {$ret = "Success"} 11001 {$ret = "Buffer Too Small"} 11002 {$ret = "Destination Net Unreachable"} 11003 {$ret = "Destination Host Unreachable"} 11004 {$ret = "Destination Protocol Unreachable"} 11005 {$ret = "Destination Port Unreachable"} 11006 {$ret = "No Resources"} 11007 {$ret = "Bad Option"} 11008 {$ret = "Hardware Error"} 11009 {$ret = "Packet Too Big"} 11010 {$ret = "Request Timed Out"} 11011 {$ret = "Bad Request"} 11012 {$ret = "Bad Route"} 11013 {$ret = "TimeToLive Expired Transit"} 11014 {$ret = "TimeToLive Expired Reassembly"} 11015 {$ret = "Parameter Problem"} 11016 {$ret = "Source Quench"} 11017 {$ret = "Option Too Big"} 11018 {$ret = "Bad Destination"} 11032 {$ret = "Negotiating IPSEC"} 11050 {$ret = "General Error"} default {$ret = "Ping Failed"} } return $ret } function GetPingResultsFromHashTable { param($ht, $maxConcurrent, $count, $timeout) $bDone = $false $i = 0 $totalMachines = 0 $htResults = @{} $dotTime = [System.DateTime]::Now if ($timeout -eq $null) {$timeout = 120} Write-Host ("Sending Ping Command to {0} Machines" -f $ht.Count) -NoNewline foreach ($name in $ht.GetEnumerator()) { while ((Get-Job -State Running).Count -ge $maxConcurrent) { Start-Sleep -Seconds 1 if ($i -ge 50) { Write-Host "*"; $i = 0 } else { Write-Host "*" -NoNewline; $i++ } } $job = Test-Connection -ComputerName $name.Key.ToString() -Count $count -AsJob $job.name = "ping:{0}" -f $name.Key.ToString() if ([System.DateTime]::Now -gt $dotTime) { $dotTime = ([System.DateTime]::Now).AddSeconds(1) if ($i -ge 50) { Write-Host "."; $i = 0 } else { Write-Host "." -NoNewline; $i++ } } } #Start time now, exit in case of timeout $timeout = ([System.DateTime]::Now).AddSeconds($timeout) $dotTime = [System.DateTime]::Now $i = 0 Write-Host Write-Host "Getting Ping Results" -NoNewline while(!($bDone)) { $results = Get-Job -Name 'ping:*' $bRunning = $false foreach ($result in $results) { if ($result.State -ne 'Running') { if ($result.State -eq 'Failed') { #resubmit job if ($i -ge 50) { Write-Host "+"; $i = 0 } else { Write-Host "+" -NoNewline; $i++ } $job = Test-Connection -ComputerName $result.Name.ToString().Split(":")[1] -Count $count -AsJob $job.name = "ping:{0}" -f $result.Name.ToString().Split(":")[1] } else { try { $htResults.Add($result.Name.ToString().Split(":")[1], (Receive-Job $result)) } catch {} $totalMachines++ } if ([System.DateTime]::Now -gt $dotTime) { $dotTime = ([System.DateTime]::Now).AddSeconds(1) if ($i -ge 50) { Write-Host "."; $i = 0 } else { Write-Host "." -NoNewline; $i++ } } try { Remove-Job $result } catch {} } else { $bRunning = $true } } #Check for timeout condition, clean up all jobs if true if ([System.DateTime]::Now -gt $timeout) { $bDone = $true Write-Host "Timeout reached, removing jobs" $results = Get-Job -Name 'ping:*' foreach ($result in $results) { Write-Host "RemoveJob:"$result.Name try { Stop-Job $result try { Remove-Job $result -Force } catch {} } catch {} } } #If the timeout hasn't been reached and jobs are still running, loop again if (!($bRunning)) { $bDone = $true } } Write-Host Write-Host ("Received Ping Results From {0} Machines" -f $totalMachines) return $htResults } function ResolveNamesFromPingResults { param($array, $maxConcurrent, $resolveNames, $timeout) try { if ($resolveNames -ne $null) { [bool]$resolveNames = [System.Convert]::ToBoolean($resolveNames) } } catch {} $htResults = @{} if ($resolveNames) { $dotTime = ([System.DateTime]::Now) if ($timeout -eq $null) {$timeout = 120} $i = 0 $scriptBlock = { param($s) try { $ret = [System.Net.DNS]::GetHostEntry($s) } catch {} return $ret } Write-Host ("Resolving DNS Names for {0} Machines" -f $array.Count) -NoNewline foreach ($name in $array) { while ((Get-Job -State Running).Count -ge $maxConcurrent) { Start-Sleep -Seconds 1 if ($i -ge 50) { Write-Host "*"; $i = 0 } else { Write-Host "*" -NoNewline; $i++ } } $job = Start-Job -ScriptBlock $scriptBlock -ArgumentList $name.NameInList $job.name = "resolve:{0}" -f $name.NameInList if ([System.DateTime]::Now -gt $dotTime) { $dotTime = ([System.DateTime]::Now).AddSeconds(1) if ($i -ge 50) { Write-Host "."; $i = 0 } else { Write-Host "." -NoNewline; $i++ } } } #Start time now, exit in case of timeout $timeout = ([System.DateTime]::Now).AddSeconds($timeout) $dotTime = ([System.DateTime]::Now) $i = 0 $bDone = $false Write-Host Write-Host "Getting DNS Results" -NoNewline while(!($bDone)) { $results = Get-Job -Name 'resolve:*' $bRunning = $false foreach ($result in $results) { if ($result.State -ne 'Running') { if ($result.State -eq 'Failed') { #resubmit job if ($i -ge 50) { Write-Host "+"; $i = 0 } else { Write-Host "+" -NoNewline; $i++ } $job = Start-Job -ScriptBlock $scriptBlock -ArgumentList $result.Name.ToString().Split(":")[1] $job.name = "resolve:{0}" -f $result.Name.ToString().Split(":")[1] } else { try { $htResults.Add($result.Name.ToString().Split(":")[1], (Receive-Job $result)) } catch {continue} } if ([System.DateTime]::Now -gt $dotTime) { $dotTime = ([System.DateTime]::Now).AddSeconds(1) if ($i -ge 50) { Write-Host "."; $i = 0 } else { Write-Host "." -NoNewline; $i++ } } try { Remove-Job $result -Force} catch {} } else { $bRunning = $true } } #Check for timeout condition, clean up all jobs if true if ([System.DateTime]::Now -gt $timeout) { $bDone = $true Write-Host "Timeout reached, removing jobs" $results = Get-Job -Name 'resolve:*' foreach ($result in $results) { Write-Host "RemoveJob:"$result.Name try { Stop-Job $result try { Remove-Job $result -Force } catch {} } catch {} } } #If the timeout hasn't been reached and jobs are still running, loop again if (!($bRunning)) { $bDone = $true } } Write-Host Write-Host ("Received DNS Results From {0} Machines" -f $htResults.Count) } return $htResults } function GetFormattedPingResultsFromHashTable { param($ht) $fResults = New-Object System.Collections.ArrayList $dotTime = ([System.DateTime]::Now) $i = 0 Write-Host "Formatting Ping Results" -NoNewLine foreach ($result in $ht.GetEnumerator()) { #There are multiple pings here if we ping more than once per computer $originalAddress = $result.Key.ToString() $pingCount = 0 $successCount = 0 $status = 'Ping Job Failed' $pingedFrom = 'Ping Job Failed' $successPercentage = 0 try { $pings = $result.Value.Count } catch { $pings = 0 } if ($pings -gt 0) { $status = GetStatusCodeString -code $result.Value[$pings-1].StatusCode $pingedFrom = $result.Value[$pings-1].PSComputerName } foreach ($ping in $result.Value) { $pingCount++ if ($ping.StatusCode -eq 0) { $successCount++ } #If you try to get the IPv4Address or IPv6Address it slows down this loop significantly } #Calculate percentage if ($pingCount -ne 0) { $successPercentage = ($successCount / $pingCount) * 100 } else { $successPercentage = 0 } #Add to array $o = New-Object PSObject -Property @{ NameInList = $originalAddress PingedFrom = $pingedFrom SuccessPercentage = $successPercentage LastPingStatus = $status } [void]$fResults.Add($o) if ([System.DateTime]::Now -gt $dotTime) { $dotTime = ([System.DateTime]::Now).AddSeconds(1) if ($i -ge 50) { Write-Host "."; $i = 0 } else { Write-Host "." -NoNewline; $i++ } } } Write-Host Write-Host ("Formatted Ping Results for {0} Machines" -f $fResults.Count) return $fResults } function GetFormattedPingAndDNSResults { param($pingResults, $dnsResults) if ($dnsResults.Count -ne 0) { Write-Host "Formatting DNS Results" -NoNewLine $dotTime = ([System.DateTime]::Now) $i = 0 foreach ($ping in $pingResults) { $dns = $dnsResults.Get_Item($ping.NameInList) if ($dns -ne $null) { $bFirst = $true foreach ($ip in $dns.AddressList) { if ($bFirst){ $ipList = $ip } else { $ipList += "|" + $ip } } $fqdn = $dns.HostName } else { $ipList = $null $fqdn = 'No DNS Entry Found' } $ping | Add-Member -MemberType NoteProperty -Name NameFromDNS -value $fqdn -Force $ping | Add-Member -MemberType NoteProperty -Name IPAddressListFromDNS -value $ipList -Force if ([System.DateTime]::Now -gt $dotTime) { $dotTime = ([System.DateTime]::Now).AddSeconds(1) if ($i -ge 50) { Write-Host "."; $i = 0 } else { Write-Host "." -NoNewline; $i++ } } } Write-Host Write-Host ("Formatted DNS Results for {0} Machines" -f $pingResults.Count) } return $pingResults } function GetTimeSpanStringInMinutesAndSeconds { param($startTime, $endTime) $time = $startTime.Subtract($endTime) $minutes = $time.ToString().Split(":")[1] $seconds = $time.ToString().Split(":")[2].Split(".")[0] $timeSpan = "{0} Minutes and {1} Seconds" -f $minutes, $seconds return $timeSpan } function GetSuccessPingCount { param($results) $successCount = 0 foreach ($result in $results) { if ($result.SuccessPercentage -gt 0) { $successCount++ } } return $successCount } function GetDNSNamesResolvedCount { param($results) $namesResolved = 0 foreach ($result in $results) { if ($result.IPAddressListFromDNS -ne $null) { $namesResolved++ } } return $namesResolved } function GetPercentageAsString { param($n1, $n2) if ($n1 -ne 0) { $percentage = ($n1 / $n2) * 100 } else { $percentage = 0 } $percentage = ("{0:N0}" -f $percentage) + "%" return $percentage } function GetOSCountsPerDomain { param($dns, $enabled, $daysOld) $osCounts = @{} $cutOffDate = ((Get-Date).Adddays(-($daysOld))).ToFileTime() Write-Host "Getting Data" -NoNewline -ForegroundColor Yellow $filter = "(PwdLastSet -gt {0}) -and (Enabled -eq '{1}') -and (OperatingSystem -like '{2}')" -f $cutOffDate, $enabled, 'Windows XP*' foreach ($domain in $dns.GetEnumerator()) { $domains = @{} $htComputers = @{} Write-Host "." -NoNewline -ForegroundColor Yellow $computers = Get-ADComputer -Filter $filter -SearchBase $domain.Value -Server $domain.Key -Properties CN, Description, DistinguishedName, DNSHostName, Name, OperatingSystem, OperatingSystemVersion foreach ($computer in $computers) { try { $htComputers.Add($computer.DNSHostName, $computer) } catch {} if ($computer.OperatingSystem -eq $null) { $os = 'NULL'} else { $os = $computer.OperatingSystem } if ($computer.OperatingSystemVersion -eq $null) { $osver = 'NULL'} else { $osver = $computer.OperatingSystemVersion } try { $domains.Add(($os + " - " + $osver), 1) } catch { $domains.Set_Item(($os + " - " + $osver), ($domains.Get_Item($os + " - " + $osver))+1) } } $osCounts.Add($domain.Key, $domains) #Ping Computers $results = GetPingResultsFromHashTable -ht $htComputers -maxConcurrent 100 -count 4 -timeout $TimeoutInSeconds 90 #Format ping results into an array of objects $formattedPingResults = GetFormattedPingResultsFromHashTable -ht $results #Resolve DNS Names if specified $dnsResults = ResolveNamesFromPingResults -array $formattedPingResults -maxConcurrent 5 -resolveNames $true -timeout 90 #Format DNS results by adding them to the ping results $formattedPingResults = GetFormattedPingAndDNSResults -pingResults $formattedPingResults -dnsResults $dnsResults #Update master list with ping data $fResults = New-Object System.Collections.ArrayList foreach($formattedPingResult in $formattedPingResults) { $o = $htComputers.Get_Item($formattedPingResult.NameInList) $nO = New-Object PSObject -Property @{ DNSHostName = $o.DNSHostName; CN = $o.CN; Name = $o.Name; DistinguishedName = $o.DistinguishedName; Description = $o.Description; OperatingSystem = $o.OperatingSystem; OperatingSystemVersion = $o.OperatingSystemVersion; NameFromDNS = $formattedPingResult.NameFromDNS; IPAddressFromDNS = $formattedPingResult.IPAddressListFromDNS; PingSuccessPercentage = $formattedPingResult.SuccessPercentage; } [void]$fResults.Add($nO) } #Output data to csv $outputFile = "{0}.csv" -f $domain.Key $path = Join-Path ($Global:CurrentDirectory) $outputFile $Global:CSVs.Add($domain.Key,$path) $fResults | select DNSHostName, CN, Name, DistinguishedName, Description, OperatingSystem, OperatingSystemVersion, NameFromDNS, IPAddressFromDNS, PingSuccessPercentage | sort DNSHostName | Export-Csv $path -NoTypeInformation Write-Host Write-Host "Detailed Output File: $path" } Write-Host return $osCounts } function CreateExcelFile { param ($osCounts) #Create the Excel file $oExcel = New-Object -ComObject Excel.Application $outputFile = "GetXPWorkstations.xlsx" $path = Join-Path ($Global:CurrentDirectory) $outputFile $oExcel.Visible = $true $oWorkbook = $oExcel.Workbooks.Add() #Create a tab for each domain foreach ($csv in $Global:CSVs.GetEnumerator()) { $oCSV = $oExcel.Workbooks.Open($csv.Value) $oCSV.ActiveSheet.Move($oWorkBook.ActiveSheet) } $oWorkbook.SaveAs($path) } function DisplayOutput { param($osCounts) Write-Host foreach ($osCount in $osCounts.GetEnumerator()) { Write-Host $OSCount.Key -ForegroundColor Green $osCount.Value.GetEnumerator() | Sort-Object Value -Descending | Format-Table -AutoSize } } #Main $Global:CurrentDirectory = (GetScriptDirectory) $Global:CSVs = @{} #Import AD Module for PowerShell ImportADModule #Get list of domains from current forest $Domains = (Get-ADForest).domains #Get hash table of domains and distinguished names from current forest $DNs = GetDNs -domains $Domains #Get OS counts per domain (specify age here) $OSCounts = GetOSCountsPerDomain -dns $DNs -enabled $true -daysOld 31 #Convert output to Excel File CreateExcelFile -osCounts $OSCounts #Display Results DisplayOutput -osCounts $OSCounts