When auditing Windows computers the WMI classes are a goldmine of information.
For example, if I wanted to audit computer processors, I could utilize the WIN32_Processor WMI class
https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-processor
This script in this article allows you to easily add additional WMI classes which are populated during the audit process and saved as a JSON file. This information is readily available to write a report against by just deserializing these JSON files.
These JSON files are just a textual representation of the WMI object which, when deserialized, can be used as the original WMI object.
For more information regarding the JSON format, see https://www.w3schools.com/js/js_json_intro.asp
# Define a computer class to hold the audit information
class Computer
{
[System.Object[]]$WMI
}
# Instantiate a instance of the computer class
$computer = [Computer]::New();
# Load WMI classes into the computer class. Add the ones you need
$computer.WMI += (Get-WMIObject Win32_OperatingSystem);
$computer.WMI += (Get-WMIObject Win32_Volume);
$computer.WMI += (Get-WMIObject Win32_ComputerSystem);
$computer.WMI += (Get-WMIObject Win32_Service);
$computer.WMI += (Get-WMIObject Win32_Processor);
# Serialize the class variable
ConvertTo-Json -Depth 10 $computer
Even though it is a deceptively simple script, it is extremely powerful. It allows you to pull all information, whether you are using it or not, to JSON format. This means that if you later need to generate a report of information that you didn't expect to use, you will already have it if you included the appropriate WMI class in the audit.
Below is an example auditing of all computers in a domain.
$Computers = Get-ADComputer -Filter *;
ForEach ($Computer in $Computers)
{
Invoke-Command -computer $Computer.Name -FilePath .\AuditComputer.ps1 | Out-File -FilePath ".\$($Computer.Name)-$(get-date -f yyyyMMddHHmmss).json"
}
Below is an example of deserializing JSON files back to WMI class objects.
$fullpath = "SomeComputer-20190305182712.json";
$computerAudit = Get-Content $fullpath | ConvertFrom-Json;
$computerSystem = $computerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_ComputerSystem"};
$operatingSystem = $latestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_OperatingSystem"};
$drives = $latestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_Volume" -and $_.DriveType -eq "3" -and $_.SystemVolume -ne "True"} | Sort("Caption");
$Processor = $latestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_Processor"};
$W32TimeService = $latestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_Service" -and $_.Name -eq "W32Time"};
Below is a sample audit script/report
$servicesToCheck = "Netlogon","NTDS","DNS","W32Time";
$subject = "Active Directory Report - Generated on $(Get-Date)";
$smtpsettings = @{
To = "shaun.vermaak@somedomain.com"
From = "ADHealthCheck@somedomain.com"
Subject = $subject
SmtpServer = "smtpserver01"
}
$DomainControllers = Get-ADDomainController -Filter * | Sort("Name");
ForEach ($DomainController in $DomainControllers)
{
Invoke-Command -computer $DomainController.Name -FilePath .\AuditComputer.ps1 | Out-File ".\$($DomainController.Name)-$(get-date -f yyyyMMddHHmmss).json"
}
# Replication Status
$AllReplOut = repadmin /csv /showrepl *;
# $DomainControllers = Get-ADDomainController -Filter *;
$html = "<!DOCTYPE html><html><head><style>";
$html = $html + "table {border: 1px solid black; border-collapse: collapse;}";
$html = $html + "td, th {border: 1px solid black; padding: 15px;}";
$html = $html + "th {background: #DDDDDD}";
$html = $html + "td.good {background:#C6EFCE;color:#2C6153;}";
$html = $html + "td.bad {background:#FFC7CE;color:#AD0055;}";
$html = $html + "td.neutral {background:#FFEB9C;color:#ADA585;}";
$html = $html + "</style></head><body>";
$allServerFlags = $hash = @{};
$allServerFlags.Add("Schema Master","$($(Get-ADForest).SchemaMaster.ToUpper())");
$allServerFlags.Add("Domain Naming Master", "$($(Get-ADForest).SchemaMaster.ToUpper())");
$allServerFlags.Add("RID Master", "$($(Get-ADDomain).RIDMaster.ToUpper())");
$allServerFlags.Add("PDC Emulator", "$($(Get-ADDomain).PDCEmulator.ToUpper())");
$allServerFlags.Add("Infrastructure Master", "$($(Get-ADDomain).InfrastructureMaster.ToUpper())");
$html = $html + "<h2>$($subject)</h2>"
$computerSummary = "<table>";
$serviceTH = "";
foreach ($serviceToCheck in $servicesToCheck)
{
$serviceTH = "$($serviceTH)<th>$($serviceToCheck)</th>"
}
$computerSummary = $computerSummary + "<tr><th>Date</th><th>Name</th><th>Replication</th><th>Operating System</th><th>Build Number</th><th>Number of Logical Processors</th><th>Total Physical Memory</th><th>Drives</th>$($serviceTH)<th>DNS Test</th></tr>";
foreach ($DomainController in $DomainControllers)
{
$fullpath = "$($DomainController.Name)-*.json"
$oldestFile = dir $fullpath | sort lastwritetime | select -First 1
$latestFile = dir $fullpath | sort lastwritetime | select -Last 1
$oldestComputerAudit = Get-Content $oldestFile | ConvertFrom-Json
$latestComputerAudit = Get-Content $latestFile | ConvertFrom-Json
$computerSystem = $latestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_ComputerSystem"};
$operatingSystem = $latestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_OperatingSystem"};
$oldestDrives = $oldestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_Volume" -and $_.DriveType -eq "3" -and $_.SystemVolume -ne "True"} | Sort("Caption");
$latestDrives = $latestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_Volume" -and $_.DriveType -eq "3" -and $_.SystemVolume -ne "True"} | Sort("Caption");
$Processor = $latestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_Processor"};
$serviceTD = "";
foreach ($serviceToCheck in $servicesToCheck)
{
$service = $latestComputerAudit.WMI | Where-Object {$_.__CLASS -eq "Win32_Service" -and $_.Name -eq "$($serviceToCheck)"}
if ($service.State -eq "Running")
{
$serviceTD = "$($serviceTD)<td class='good'>$($service.State)</td>";
}
else
{
$serviceTD = "$($serviceTD)<td class='bad'>$($service.State)</td>";
}
}
$computerFQDN = "$($computerSystem.DNSHostName).$($computerSystem.Domain)".ToUpper();
$serverFlags = $allServerFlags.GetEnumerator() | ? {$_.Value -eq "$($computerFQDN)"};
$serverFlagsJoined = "";
$serverReplication = ConvertFrom-Csv -InputObject $AllReplOut | where {$_.'Destination DSA' -eq $DomainController.Name -and $_.'Number of Failures' -ge 1};
if ($serverReplication -ne $null)
{
$serverReplicationSummary = $serverReplication | select "Source DSA", "Destination DSA", "Naming Context" ,"Number of Failures", "Last Failure Time", "Last Success Time", "Last Failure Status" | ConvertTo-Html;
}
else
{
$serverReplicationSummary = "No replication issues";
}
foreach ($serverFlag in $serverFlags)
{
$serverFlagsJoined = "$($serverFlagsJoined) ($($serverFlag.Name.Trim()))";
}
$dnsTest = (Test-DnsServer -IPAddress $DomainController.IPv4Address).Result;
$dnsTestSummary = "";
if ($dnsTest -eq "Success")
{
$dnsTestSummary = "<td class='good'>Success</td>";
}
else
{
$dnsTestSummary = "<td class='bad'>Failed</td>";
}
# Drive summary table
$driveSummary = "<table>";
$driveSummary = $driveSummary + "<tr><th>Drive</th><th>Capacity</th><th>Free</th><th>Growth</th></tr>";
foreach ($drive in $latestDrives)
{
$oldDrive = $oldestDrives | ? {$_.SerialNumber -eq $drive.SerialNumber};
$driveFreePercentage = $([math]::Round((($drive.FreeSpace/$drive.Capacity)*100),3));
$driveGrowthPercentage = $([math]::Round(((($drive.FreeSpace - $oldDrive.FreeSpace)/$drive.Capacity)*100),3));
$driveSummary = $DriveSummary + "<tr><td>$($drive.Caption)</td>";
$driveSummary = $DriveSummary + "<td>$([math]::Round($drive.Capacity/1GB,3)) GB</td>";
if ($driveFreePercentage -lt 16)
{
$driveSummary = $DriveSummary + "<td class='bad'>$([math]::Round($drive.FreeSpace/1GB,3)) GB ($($driveFreePercentage) %)</td>";
}
else
{
$driveSummary = $DriveSummary + "<td class='good'>$([math]::Round($drive.FreeSpace/1GB,3)) GB ($($driveFreePercentage) %)</td>";
}
if ($driveGrowthPercentage -gt 6)
{
$driveSummary = $DriveSummary + "<td class='bad'>$([math]::Round(($drive.FreeSpace - $oldDrive.FreeSpace)/1GB,3)) GB ($driveGrowthPercentage %)</td></tr>";
}
else
{
$driveSummary = $DriveSummary + "<td class='neutral'>$([math]::Round(($drive.FreeSpace - $oldDrive.FreeSpace)/1GB,3)) GB ($driveGrowthPercentage %)</td></tr>";
}
}
$driveSummary = $DriveSummary + "</table>";
$computerSummary = $computerSummary + "<tr><td>$((Get-Item $latestFile).LastWriteTime)</td><td>$($computerFQDN)$($serverFlagsJoined)</td><td>$($serverReplicationSummary)</td><td>$($operatingSystem.Caption)</td><td>$($operatingSystem.BuildNumber)</td><td>$($Processor.Count)</td><td>$([int]($computerSystem.TotalPhysicalMemory/1GB)) GB</td><td>$($driveSummary)</td>$($serviceTD)$($dnsTestSummary)</tr>";
}
$computerSummary = $computerSummary + "</table>";
$html = $html + $computerSummary;
$html = $html + "</body></html>";
$html;
Send-MailMessage @smtpsettings -Body $html -BodyAsHtml -Encoding ([System.Text.Encoding]::UTF8)
I hope you found this tutorial useful. You are encouraged to ask questions, report any bugs or make any other comments about it below.
Note: If you need further "Support" about this topic, please consider using the Ask a Question feature of Experts Exchange. I monitor questions asked and would be pleased to provide any additional support required in questions asked in this manner, along with other EE experts...
Please do not forget to press the "Thumbs Up" button if you think this article was helpful and valuable for EE members.
It also provides me with positive feedback. Thank you!
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (4)
Commented:
However, when I execute it, I got this error from the AUDITCOMPUTER.PS1 for every Domain Controllers:
Open in new window
Author
Commented:Commented:
Open in new window
is it because some of my Domain Controllers are on Windows Server 2012 R2?
Author
Commented: