Skip to content

Add server lifecycle audit PowerShell script#47

Open
AlrightLad wants to merge 1 commit intoDTC-Inc:mainfrom
AlrightLad:patch-10
Open

Add server lifecycle audit PowerShell script#47
AlrightLad wants to merge 1 commit intoDTC-Inc:mainfrom
AlrightLad:patch-10

Conversation

@AlrightLad
Copy link

@AlrightLad AlrightLad commented Mar 24, 2026

This script performs a comprehensive audit of server lifecycle aspects including OS details, domain roles, CPU, memory, disk layout, server roles, SQL instances, installed software, running services, file shares, printers, network configuration, DNS settings, DHCP scopes, RDS licensing, scheduled tasks, and Hyper-V VMs.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a comprehensive Windows VM lifecycle audit script that generates structured reports on system configuration, including host details, hardware metrics, storage layout, installed server roles, services, software inventory, network adapters, shared resources, DNS/DHCP configuration, RDS licensing, scheduled tasks, and virtual machines.

This script performs a comprehensive audit of server lifecycle aspects including OS details, domain roles, CPU, memory, disk layout, server roles, SQL instances, installed software, running services, file shares, printers, network configuration, DNS settings, DHCP scopes, RDS licensing, scheduled tasks, and Hyper-V VMs.
@AlrightLad AlrightLad requested a review from Gumbees as a code owner March 24, 2026 07:04
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

A new PowerShell audit script has been added that inventories and reports system and infrastructure details including host configuration, domain roles, hardware metrics, installed roles and software, network configuration, and virtualization state via structured queries.

Changes

Cohort / File(s) Summary
PowerShell Audit Script
msft-windows/msft-windows-vm-lifecycle-audit/audit-script.ps1 or similar
New comprehensive Windows host audit script that collects host/OS information, domain metadata, hardware metrics, installed roles, software inventory (filtered), auto-start services, SMB shares, network configuration, and conditionally queries DNS/DHCP/RDS/Hyper-V with error handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hippity-hop through system logs so deep,
This script collects the secrets Windows keeps,
From CPU cores to virtual machines,
Every detail audited with PowerShell sheen!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add server lifecycle audit PowerShell script' directly and clearly describes the main change—adding a new PowerShell script for server lifecycle auditing, which aligns with the PR's primary objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
msft-windows/msft-windows-vm-lifecycle-audit (1)

1-5: Add transcript logging and an explicit RMM simulation path.

The script currently has no transcript lifecycle, which makes audit traceability harder during field execution and validation.

Proposed logging/testability addition
+# Transcript path under %WINDIR%\logs
+$logDir = Join-Path $env:WINDIR "logs"
+$logFile = Join-Path $logDir ("vm-lifecycle-audit-{0:yyyyMMdd-HHmmss}.log" -f (Get-Date))
+if (-not (Test-Path $logDir)) { New-Item -Path $logDir -ItemType Directory -Force | Out-Null }
+Start-Transcript -Path $logFile -Force
+
+# Optional RMM simulation switch
+$RMM = 0
+# Set $RMM = 1 and predefine required variables during testing
+
 try {
   # existing audit logic...
 }
 finally {
+  Stop-Transcript | Out-Null
 }

Based on learnings: “Verify transcripts/logs in %WINDIR%\logs after execution; support RMM simulation by setting $RMM=1 and predefining required variables during testing”.

Also applies to: 65-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@msft-windows/msft-windows-vm-lifecycle-audit` around lines 1 - 5, Add
PowerShell transcript start/stop around the main script execution by invoking
Start-Transcript and Stop-Transcript and write the transcript to
$env:windir\Logs with a timestamped filename so audit traces land in
%WINDIR%\Logs; also add explicit test-mode/RMM simulation support by declaring
and checking a $RMM variable (e.g., default $RMM = 0) and, when $RMM -eq 1,
predefine or mock required input variables and skip any destructive operations
to allow running under RMM testing; ensure Start-Transcript is invoked early
(before major actions) and Stop-Transcript in a finally/cleanup path so
transcripts are always closed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@msft-windows/msft-windows-vm-lifecycle-audit`:
- Around line 1-65: The script lacks the required three-part PowerShell template
(RMM variable declaration, input handling, script logic) so preserve backward
compatibility by adding the RMM variable declaration block and input parsing
before any immediate execution (before the first Write-Host), implement the
input handling/parameter mapping that mirrors script-template-powershell.ps1
(declare expected RMM variables and parse parameters/env), and then move the
existing audit commands into a clearly delimited "script logic" section;
reference the template name script-template-powershell.ps1 and the existing
top-level execution points (the initial Write-Host and subsequent audit command
blocks) to locate where to insert the RMM variables and input handling.
- Around line 12-14: The netdom call in the FSMO block (netdom query fsmo)
currently relies on try/catch which won't catch non-zero exit codes from the
native executable; update the block that calls netdom to capture its output,
examine $LASTEXITCODE immediately after the call, and handle non-zero values by
logging a clear message via Write-Host (including the captured output/error)
instead of silently relying on the catch. Ensure the modified logic still
handles the non-DC case and unavailability of netdom by checking for
$LASTEXITCODE and/or specific error text and emitting a concise failure message.

---

Nitpick comments:
In `@msft-windows/msft-windows-vm-lifecycle-audit`:
- Around line 1-5: Add PowerShell transcript start/stop around the main script
execution by invoking Start-Transcript and Stop-Transcript and write the
transcript to $env:windir\Logs with a timestamped filename so audit traces land
in %WINDIR%\Logs; also add explicit test-mode/RMM simulation support by
declaring and checking a $RMM variable (e.g., default $RMM = 0) and, when $RMM
-eq 1, predefine or mock required input variables and skip any destructive
operations to allow running under RMM testing; ensure Start-Transcript is
invoked early (before major actions) and Stop-Transcript in a finally/cleanup
path so transcripts are always closed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aec60540-d5ba-453e-a146-58ab3c2fd743

📥 Commits

Reviewing files that changed from the base of the PR and between 1ac5146 and 274fca9.

📒 Files selected for processing (1)
  • msft-windows/msft-windows-vm-lifecycle-audit

Comment on lines +1 to +65
# ===== SERVER REPLACEMENT AUDIT =====
# Server Lifecycle - Quote Validation Audit SOP (BookStack Page 1787)
# Run on EACH server/VM being evaluated for replacement.
# Paste into elevated PowerShell session or push via NinjaOne.

Write-Host "===== HOSTNAME & OS =====" -ForegroundColor Cyan
Get-CimInstance Win32_OperatingSystem | Select-Object CSName, Caption, Version, InstallDate | Format-List

Write-Host "===== DOMAIN ROLE =====" -ForegroundColor Cyan
Get-CimInstance Win32_ComputerSystem | Select-Object Name, Domain, DomainRole, PartOfDomain | Format-List

Write-Host "===== AD FSMO ROLES (if DC) =====" -ForegroundColor Cyan
try { netdom query fsmo 2>&1 } catch { Write-Host "Not a DC or netdom unavailable" }

Write-Host "===== CPU =====" -ForegroundColor Cyan
Get-CimInstance Win32_Processor | Select-Object Name, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed | Format-List

Write-Host "===== MEMORY =====" -ForegroundColor Cyan
$os = Get-CimInstance Win32_OperatingSystem
[PSCustomObject]@{TotalGB=[math]::Round($os.TotalVisibleMemorySize/1MB,1);FreeGB=[math]::Round($os.FreePhysicalMemory/1MB,1);UsedGB=[math]::Round(($os.TotalVisibleMemorySize-$os.FreePhysicalMemory)/1MB,1);CommittedGB=[math]::Round(($os.TotalVirtualMemorySize-$os.FreeVirtualMemory)/1MB,1)} | Format-List

Write-Host "===== DISK LAYOUT =====" -ForegroundColor Cyan
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | Select-Object DeviceID, @{N='SizeGB';E={[math]::Round($_.Size/1GB,1)}}, @{N='FreeGB';E={[math]::Round($_.FreeSpace/1GB,1)}}, @{N='UsedGB';E={[math]::Round(($_.Size-$_.FreeSpace)/1GB,1)}} | Format-Table -AutoSize

Write-Host "===== PHYSICAL DISK CONTROLLER =====" -ForegroundColor Cyan
Get-CimInstance Win32_SCSIController | Select-Object Name, DriverName | Format-Table -AutoSize

Write-Host "===== SERVER ROLES =====" -ForegroundColor Cyan
try { Get-WindowsFeature | Where-Object { $_.Installed -eq $true -and $_.FeatureType -eq 'Role' } | Select-Object Name, DisplayName | Format-Table -AutoSize } catch { Write-Host "Get-WindowsFeature not available" }

Write-Host "===== SQL SERVER INSTANCES =====" -ForegroundColor Cyan
Get-Service | Where-Object { $_.Name -match 'MSSQL' -or $_.Name -match 'SQL' } | Select-Object Name, DisplayName, Status, StartType | Format-Table -AutoSize

Write-Host "===== INSTALLED SOFTWARE (filtered) =====" -ForegroundColor Cyan
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*, HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -and $_.DisplayName -notmatch 'Update|Hotfix|KB\d|Visual C\+\+|\.NET' } | Select-Object DisplayName, DisplayVersion, Publisher | Sort-Object DisplayName | Format-Table -AutoSize

Write-Host "===== RUNNING SERVICES (non-Microsoft, Auto-start) =====" -ForegroundColor Cyan
Get-CimInstance Win32_Service | Where-Object { $_.StartMode -eq 'Auto' -and $_.PathName -notmatch 'Windows|Microsoft|svchost' } | Select-Object Name, DisplayName, State, PathName | Format-Table -AutoSize

Write-Host "===== FILE SHARES =====" -ForegroundColor Cyan
Get-SmbShare | Where-Object { $_.Name -notmatch '^\$|^ADMIN\$|^IPC\$|^print\$' } | Select-Object Name, Path, Description | Format-Table -AutoSize

Write-Host "===== SHARED PRINTERS =====" -ForegroundColor Cyan
Get-Printer -ErrorAction SilentlyContinue | Where-Object { $_.Shared -eq $true } | Select-Object Name, DriverName, PortName, Shared | Format-Table -AutoSize

Write-Host "===== NETWORK CONFIG =====" -ForegroundColor Cyan
Get-NetAdapter | Select-Object Name, InterfaceDescription, Status, LinkSpeed | Format-Table -AutoSize
Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.IPAddress -notmatch '^127' } | Select-Object InterfaceAlias, IPAddress, PrefixLength | Format-Table -AutoSize

Write-Host "===== DNS SERVER SETTINGS (if DC) =====" -ForegroundColor Cyan
try { Get-DnsServerZone -ErrorAction Stop | Select-Object ZoneName, ZoneType, IsReverseLookupZone | Format-Table -AutoSize } catch { Write-Host "DNS Server role not installed" }

Write-Host "===== DHCP SCOPES (if DHCP server) =====" -ForegroundColor Cyan
try { Get-DhcpServerv4Scope -ErrorAction Stop | Select-Object ScopeId, Name, StartRange, EndRange, SubnetMask, State | Format-Table -AutoSize } catch { Write-Host "DHCP Server role not installed" }

Write-Host "===== RDS LICENSING =====" -ForegroundColor Cyan
try { Get-RDLicenseConfiguration -ErrorAction Stop | Format-List } catch { Write-Host "RDS Licensing not configured" }

Write-Host "===== SCHEDULED TASKS (non-Microsoft) =====" -ForegroundColor Cyan
Get-ScheduledTask | Where-Object { $_.TaskPath -notmatch '\\Microsoft\\' -and $_.State -ne 'Disabled' } | Select-Object TaskName, TaskPath, State | Format-Table -AutoSize

Write-Host "===== HYPER-V VMs (if any) =====" -ForegroundColor Cyan
try { Get-VM -ErrorAction Stop | Select-Object Name, State, MemoryAssigned, ProcessorCount | Format-Table -AutoSize } catch { Write-Host "Hyper-V not installed" }

Write-Host "===== AUDIT COMPLETE =====" -ForegroundColor Green
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Adopt the required 3-part PowerShell script template before merge.

This new script goes straight into execution logic and omits the required sections for RMM variable declaration and input handling, which risks incompatibility with existing automation paths.

Proposed structure scaffold
+# =========================
+# 1) RMM variable declaration
+# =========================
+# Example: preserve existing RMM variable names used by the template/repo
+$RMM = $env:RMM
+
+# =========================
+# 2) Input handling
+# =========================
+param(
+  [switch]$RunAsRmm
+)
+
+# =========================
+# 3) Script logic
+# =========================
 Write-Host "===== HOSTNAME & OS =====" -ForegroundColor Cyan
 ...
 Write-Host "===== AUDIT COMPLETE =====" -ForegroundColor Green

Based on learnings: “All PowerShell scripts must follow the three-part structure from script-template-powershell.ps1 (RMM variable declaration, input handling, script logic)” and “When modifying scripts, preserve the three-section structure and maintain backward compatibility with existing RMM variable names”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@msft-windows/msft-windows-vm-lifecycle-audit` around lines 1 - 65, The script
lacks the required three-part PowerShell template (RMM variable declaration,
input handling, script logic) so preserve backward compatibility by adding the
RMM variable declaration block and input parsing before any immediate execution
(before the first Write-Host), implement the input handling/parameter mapping
that mirrors script-template-powershell.ps1 (declare expected RMM variables and
parse parameters/env), and then move the existing audit commands into a clearly
delimited "script logic" section; reference the template name
script-template-powershell.ps1 and the existing top-level execution points (the
initial Write-Host and subsequent audit command blocks) to locate where to
insert the RMM variables and input handling.

Comment on lines +12 to +14
Write-Host "===== AD FSMO ROLES (if DC) =====" -ForegroundColor Cyan
try { netdom query fsmo 2>&1 } catch { Write-Host "Not a DC or netdom unavailable" }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

In PowerShell 5.1 and 7.x, does try/catch catch non-zero exit codes from native executables (like netdom), or only terminating PowerShell errors? Please cite official docs.

💡 Result:

No, in PowerShell 5.1 and 7.x (up to 7.5 as of 2026-03-24), try/catch does not catch non-zero exit codes from native executables like netdom by default. These exit codes do not generate terminating PowerShell errors that try/catch can handle. Instead, check the $LASTEXITCODE automatic variable after running the executable. To handle native command errors in try/catch in PowerShell 7.3+, opt-in by setting $PSNativeCommandUseErrorActionPreference = $true (defaults to $false) and $ErrorActionPreference = 'Stop'. Then non-zero exit codes produce catchable errors (NativeCommandExitException). This is a mainstream feature since 7.4 but requires explicit enabling. Official docs confirm try/catch handles terminating errors/exceptions (about_Try_Catch_Finally), not native exit codes directly. about_Preference_Variables documents $PSNativeCommandUseErrorActionPreference.

Citations:


🏁 Script executed:

#!/bin/bash
# Check if the file exists and read lines around 12-14
find . -type f -name "*msft-windows-vm-lifecycle-audit*" 2>/dev/null

Repository: DTC-Inc/msp-script-library

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Read the file to check content around lines 12-14
head -20 ./msft-windows/msft-windows-vm-lifecycle-audit

Repository: DTC-Inc/msp-script-library

Length of output: 1366


Harden netdom error handling in the FSMO block.

Line 13 uses try/catch around a native executable call; this can miss failure states. In PowerShell 5.1 and 7.x, try/catch does not catch non-zero exit codes from native executables—you must explicitly check $LASTEXITCODE after the call.

Proposed robust FSMO query block
 Write-Host "===== AD FSMO ROLES (if DC) =====" -ForegroundColor Cyan
-try { netdom query fsmo 2>&1 } catch { Write-Host "Not a DC or netdom unavailable" }
+if (Get-Command netdom -ErrorAction SilentlyContinue) {
+  $fsmo = & netdom query fsmo 2>&1
+  if ($LASTEXITCODE -eq 0) {
+    $fsmo
+  } else {
+    Write-Host "Not a DC or netdom unavailable"
+  }
+} else {
+  Write-Host "netdom unavailable"
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@msft-windows/msft-windows-vm-lifecycle-audit` around lines 12 - 14, The
netdom call in the FSMO block (netdom query fsmo) currently relies on try/catch
which won't catch non-zero exit codes from the native executable; update the
block that calls netdom to capture its output, examine $LASTEXITCODE immediately
after the call, and handle non-zero values by logging a clear message via
Write-Host (including the captured output/error) instead of silently relying on
the catch. Ensure the modified logic still handles the non-DC case and
unavailability of netdom by checking for $LASTEXITCODE and/or specific error
text and emitting a concise failure message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant