← script library

Audit Windows Server State with a PowerShell Runbook

RunbooksAugust 17, 2026

A three-step runbook that produces one run history entry per server: full device information first — operating system, hardware, serial, disk, BIOS and network — then averaged CPU load, then averaged memory usage. Point it at a server group and it is a fleet inventory with the current load attached, which is a different thing from a monitoring dashboard and often the more useful one.

powershell
# ===== Step 1 of 3: 00-SE-GatherDeviceInformation.ps1 =====

# Gather Useful Computer Information with Reboot Check
#-----------------------------------------------------------

function Write-Log {
    param($Message)
    Write-Host "<WRITE-LOG = `"*$Message*`">"
}

Write-Log "Starting Computer Information Collection"

# OS Information
Write-Log "--- Operating System ---"
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem
Write-Log "OS Name: $($osInfo.Caption)"
Write-Log "OS Version: $($osInfo.Version)"
Write-Log "OS Build: $($osInfo.BuildNumber)"
Write-Log "Architecture: $($osInfo.OSArchitecture)"
Write-Log "Install Date: $($osInfo.InstallDate)"
Write-Log "Last Boot Time: $($osInfo.LastBootUpTime)"
Write-Log "System Directory: $($osInfo.SystemDirectory)"

# System (Computer) Info
Write-Log "--- System ---"
$sysInfo = Get-CimInstance -ClassName Win32_ComputerSystem
Write-Log "Computer Name: $($sysInfo.Name)"
Write-Log "Domain: $($sysInfo.Domain)"
Write-Log "Role: $($sysInfo.Roles -join ', ')"
Write-Log "System Type: $($sysInfo.SystemType)"
Write-Log "Manufacturer: $($sysInfo.Manufacturer)"
Write-Log "Model: $($sysInfo.Model)"

# BIOS Serial Number
Write-Log "--- System Serial ---"
$serial = (Get-CimInstance -ClassName Win32_BIOS).SerialNumber
Write-Log "Serial Number: $serial"

# CPU
Write-Log "--- Processor ---"
$cpu = Get-CimInstance -ClassName Win32_Processor
Write-Log "Name: $($cpu.Name)"
Write-Log "Manufacturer: $($cpu.Manufacturer)"
Write-Log "Cores: $($cpu.NumberOfCores)"
Write-Log "Logical Processors: $($cpu.NumberOfLogicalProcessors)"
Write-Log "Max Clock Speed: $($cpu.MaxClockSpeed) MHz"

# Memory
Write-Log "--- Memory (RAM) ---"
$mem = Get-CimInstance -ClassName Win32_PhysicalMemory
$totalGB = ($mem | Measure-Object -Property Capacity -Sum).Sum / 1GB
Write-Log "Total RAM: $([math]::Round($totalGB, 1)) GB"
Write-Log "Modules Installed: $($mem.Count)"

# Disk (C:)
Write-Log "--- Disk (C:) ---"
$disk = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='C:'"
Write-Log "Drive: C:\"
Write-Log "Size: $([math]::Round($disk.Size / 1GB, 1)) GB"
Write-Log "Free Space: $([math]::Round($disk.FreeSpace / 1GB, 1)) GB"
Write-Log "File System: $($disk.FileSystem)"
Write-Log "Volume Label: $($disk.VolumeName)"

# BIOS Details
Write-Log "--- BIOS ---"
$bios = Get-CimInstance -ClassName Win32_BIOS
Write-Log "Vendor: $($bios.SMBIOSBIOSVendor)"
Write-Log "Version: $($bios.SMBIOSBIOSVersion)"
Write-Log "Release Date: $($bios.ReleaseDate)"

# Network
Write-Log "--- Network (IPv4) ---"
$net = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled } | Select-Object -First 1
if ($net) {
    Write-Log "MAC Address: $($net.MACAddress)"
    Write-Log "IP Address: $(if ($net.IPAddress) { $net.IPAddress -join ', ' } else { 'None' })"
    Write-Log "Default Gateway: $(if ($net.DefaultIPGateway) { $net.DefaultIPGateway -join ', ' } else { 'None' })"
} else {
    Write-Log "No active IPv4 network adapters found"
}

# Reboot Check
Write-Log "--- Reboot Status ---"
$rebootRequired = $false
$registryPaths = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired",
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending",
    "HKLM:\SOFTWARE\Microsoft\ServerManager\CurrentRebootAttempts"
)

foreach ($path in $registryPaths) {
    if (Test-Path $path) { $rebootRequired = $true }
}

# Check for Rename operations
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name "PendingFileRenameOperations" -ErrorAction SilentlyContinue) {
    $rebootRequired = $true
}

Write-Log "Reboot Required: $rebootRequired"

Write-Log "Computer Information Collection Complete"

# ===== Step 2 of 3: 01-CHECK-Usage-CPU.ps1 =====

function Write-Log {
    param($Message)
    Write-Host "<WRITE-LOG = `"*$Message*`">"
}

try {
    $cpuValues = @()

    # Collect CPU usage 5 times, 5 seconds apart
    1..5 | ForEach-Object {
        $cpuSample = Get-Counter '\Processor(_Total)\% Processor Time'
        $cpuValue = $cpuSample.CounterSamples[0].CookedValue
        $cpuValues += $cpuValue
        Start-Sleep -Seconds 5
    }

    # Calculate and output the average
    $avgCpu = [math]::Round(($cpuValues | Measure-Object -Average).Average, 2)
    Write-Log "CPU Average Usage: $avgCpu %"

    # Alert if average CPU exceeds 85%
    if ($avgCpu -gt 85) {
        Write-Error "CPU usage too high: $avgCpu %"
    }

} catch {
    Write-Log "Unable to gather CPU Information: Device not supported."
}

# ===== Step 3 of 3: 01-CHECK-Usage-RAM.ps1 =====

function Write-Log {
    param($Message)
    Write-Host "<WRITE-LOG = `"*$Message*`">"
}

try {
	$ramValues = @()
    # Collect RAM usage 5 times, 5 seconds apart
    1..5 | ForEach-Object {
    	$ram             = Get-WmiObject Win32_OperatingSystem
    	$totalRam        = [math]::Round($ram.TotalVisibleMemorySize / 1MB, 2)
    	$freeRam         = [math]::Round($ram.FreePhysicalMemory  / 1MB, 2)
    	$usedRam         = $totalRam - $freeRam
    	$ramUsagePercent = [math]::Round(($usedRam / $totalRam) * 100, 2)

        $ramValues += $ramUsagePercent
        Start-Sleep -Seconds 5
    }

    # Calculate and output the average
    $avgRam = [math]::Round(($ramValues | Measure-Object -Average).Average, 2)
    Write-Log "RAM Average Usage: $avgRam %"

    # Alert if average RAM exceeds 85%
    if ($avgRam -gt 85) {
        Write-Error "RAM usage too high: $avgRam %"
    }
}
catch {
    Write-Log "Unable to gather RAM Information: Device not supported."
}

Run it across your fleet

This script runs as-is on a single host. Paste it into ServerEngine to schedule it, run it on a whole server group in parallel, and keep the credentials out of the file — see the scripts documentation and the credential store.

Ready when you are.

Try ServerEngine free for 7 days.