Computer Audit with Powershell

Published on
5,775 Points
4 Endorsements
Last Modified:
Shaun Vermaak
My name is Shaun Vermaak and I have always been fascinated with technology and how we use it to enhance our lives and business.
This article details my method of auditing computers by querying WMI class, serializing it to JSON and saving it is a central location, ready to be deserialized again and pulled into a report


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


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

The Script (AuditComputer.ps1)

# Define a computer class to hold the audit information
class Computer

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

Sample Report

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>";
$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;
$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>";
$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>";
$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>";
$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>";

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!

Ask questions about what you read
If you have a question about something within an article, you can receive help directly from the article author. Experts Exchange article authors are available to answer questions and further the discussion.
Get 7 days free