Get All Windows XP Machines in an Active Directory Forest
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:
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