|
| 1 | +--- |
| 2 | +title: "One-Line Server Inventory & AD Audit Report" |
| 3 | +date: 2025-10-04T10:00:00+05:30 |
| 4 | +draft: false |
| 5 | +description: "Bootstrap VB.ServerInventory from a gist and produce a full server + AD audit report — stale accounts, BitLocker, GPOs, patches — in one run." |
| 6 | +tags: ["PowerShell", "Active Directory", "Windows Server", "Automation", "Security", "Audit"] |
| 7 | +categories: ["PowerShell Automation"] |
| 8 | +author: |
| 9 | + name: Vibhu Bhatnagar |
| 10 | +--- |
| 11 | + |
| 12 | +## Purpose |
| 13 | + |
| 14 | +Every Windows environment I inherit has the same problem — nobody knows what's actually on the servers. Who's got a password older than a year? Which computers haven't checked in for six months? Is BitLocker actually on? This one-liner bootstraps `VB.ServerInventory` and produces a full server + AD audit report in a single run. No agent, no console, no license. |
| 15 | + |
| 16 | +## What This Covers |
| 17 | + |
| 18 | +- What the bootstrap script actually does (line by line) |
| 19 | +- The report it produces and what's in it |
| 20 | +- Which security baselines the audit checks map to |
| 21 | +- How to run it and verify the output |
| 22 | + |
| 23 | +## Before You Start |
| 24 | + |
| 25 | +- Windows Server 2016+ or Windows 10/11 with PowerShell 5.1 or later |
| 26 | +- Local admin on the target machine |
| 27 | +- Domain admin (or delegated read) if you want the AD, GPO, and DHCP sections populated |
| 28 | +- Internet access to reach PSGallery and the gist |
| 29 | +- ExecutionPolicy needs to allow the run — the script sets `Unrestricted` for CurrentUser scope |
| 30 | + |
| 31 | +## The Bootstrap |
| 32 | + |
| 33 | +```powershell |
| 34 | +Set-ExecutionPolicy Unrestricted -Scope CurrentUser -Force |
| 35 | +
|
| 36 | +# Enforce TLS 1.2 — PSGallery rejects older protocols |
| 37 | +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
| 38 | +
|
| 39 | +# Register PSGallery if it's missing (fresh servers often have no repos) |
| 40 | +if (-not (Get-PSRepository -Name "PSGallery" -ErrorAction SilentlyContinue)) { |
| 41 | + Register-PSRepository -Default -ErrorAction Stop |
| 42 | +} |
| 43 | +
|
| 44 | +# Trust PSGallery so Install-Module doesn't prompt |
| 45 | +Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted |
| 46 | +
|
| 47 | +# Widen the console buffer so nothing scrolls off screen |
| 48 | +$host.UI.RawUI.BufferSize = New-Object System.Management.Automation.Host.Size(500, 9000) |
| 49 | +
|
| 50 | +Get-PSRepository |
| 51 | +
|
| 52 | +# Pull and execute the inventory script from the gist |
| 53 | +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
| 54 | +iex (Invoke-WebRequest "https://gist.githubusercontent.com/Vibhu2/9a4ecf03d30c35d27073c71a2d5f0d4d/raw/b0541f3663701a2b16d0e1a8fd4a4030f9bf70ee/gistfile1.txt" -UseBasicParsing).Content |
| 55 | +``` |
| 56 | + |
| 57 | +The first block is prep — TLS, repo trust, buffer size. The last line is the actual work: it pulls the `Get-ServerInventory` script from a gist and runs it in the current session. |
| 58 | + |
| 59 | +> **[!] WARNING:** `iex` on remote content is convenient but risky. Only run this against a gist you trust — or better, download the raw file, read it, and dot-source it locally. |
| 60 | +
|
| 61 | +## What the Report Contains |
| 62 | + |
| 63 | +`Get-ServerInventory` runs a wide sweep across the machine and (if `-IncludeAD` is set) the domain. The output is grouped into sections: |
| 64 | + |
| 65 | +**System & Hardware** |
| 66 | + |
| 67 | +- OS build, BIOS, CPU, memory, uptime, last boot |
| 68 | +- Disk usage per logical drive |
| 69 | +- Network adapters — IP, DHCP vs static, DNS |
| 70 | + |
| 71 | +**Software & Roles** |
| 72 | + |
| 73 | +- Installed applications (from the registry uninstall keys — the reliable source) |
| 74 | +- Windows Store apps |
| 75 | +- Windows Features and Roles |
| 76 | +- Installed hotfixes and updates |
| 77 | +- Printers, drivers, and recent print jobs |
| 78 | + |
| 79 | +**Directory & Network Services** |
| 80 | + |
| 81 | +- AD domain info |
| 82 | +- All GPOs |
| 83 | +- DHCP scopes, reservations, exclusions |
| 84 | +- DNS zones and forwarders |
| 85 | +- SMB shares |
| 86 | +- Active RDS / terminal sessions |
| 87 | + |
| 88 | +**Security Posture** |
| 89 | + |
| 90 | +- Azure AD / Entra join status (`dsregcmd /status`) |
| 91 | +- BitLocker status and recovery key presence |
| 92 | +- Windows Defender configuration |
| 93 | + |
| 94 | +**Audit Findings** — this is the part that matters most |
| 95 | + |
| 96 | +- Users inactive 90+ days |
| 97 | +- Computers inactive 90+ days |
| 98 | +- Accounts that have never logged in |
| 99 | +- Expired user accounts |
| 100 | +- Users with **Password Never Expires** |
| 101 | +- Admin accounts with passwords older than 1 year |
| 102 | +- Empty AD groups |
| 103 | +- Accounts with **Password Not Required** |
| 104 | + |
| 105 | +The report renders to console with colored section headers by default. Pass `-ExportCSV` and it writes to `$env:USERPROFILE\Desktop\ServerInventory-[timestamp]`. |
| 106 | + |
| 107 | +## Why This Report Matters |
| 108 | + |
| 109 | +Most SMBs and even mid-size shops don't run a proper audit until something breaks — a ransomware hit, a failed audit, a departing admin. This report gets you 80% of that visibility in one run. Specifically: |
| 110 | + |
| 111 | +- **The stale-account checks catch abandoned identities** — the single most common initial-access vector in AD compromises |
| 112 | +- **Password-age and never-expires findings** surface accounts that violate every modern baseline |
| 113 | +- **BitLocker + Defender status** confirms two of the checks every cyber insurance renewal asks for |
| 114 | +- **Inventory of installed apps and hotfixes** is what you hand a vulnerability team on day one |
| 115 | + |
| 116 | +Run it quarterly on every server. Diff the CSVs. That's your change control. |
| 117 | + |
| 118 | +## Standards This Aligns With |
| 119 | + |
| 120 | +The script doesn't cite any standard directly — but the audit checks map cleanly to controls in every mainstream security baseline. The 90-day inactivity threshold, password-age checks, and privileged-account monitoring are lifted straight from these: |
| 121 | + |
| 122 | +| Check | Aligns with | |
| 123 | +| :--- | :--- | |
| 124 | +| Inactive users / computers (90+ days) | CIS Controls v8 §6.2, NIST 800-53 AC-2(3), ISO 27001 A.9.2.6 | |
| 125 | +| Password Never Expires / no password required | CIS Windows Benchmark §1.1.x, NIST 800-63B, Essential Eight ML1 | |
| 126 | +| Admin passwords older than 1 year | CIS Controls v8 §5.4, NIST 800-53 IA-5(1) | |
| 127 | +| BitLocker enforcement | CIS Windows Benchmark §18, NIST 800-171 3.13.11 | |
| 128 | +| Windows Defender enabled | CIS Controls v8 §10.1, Essential Eight ML1 | |
| 129 | +| Empty AD groups / group hygiene | CIS Controls v8 §6.8, ISO 27001 A.9.2.5 | |
| 130 | +| Installed patches / hotfixes | CIS Controls v8 §7.3, NIST 800-53 SI-2 | |
| 131 | + |
| 132 | +None of this replaces a formal audit. It gets you ready for one. |
| 133 | + |
| 134 | +## Verify |
| 135 | + |
| 136 | +After the script finishes: |
| 137 | + |
| 138 | +```powershell |
| 139 | +# Confirm the CSVs landed |
| 140 | +Get-ChildItem "$env:USERPROFILE\Desktop\ServerInventory-*" | Select-Object Name, LastWriteTime |
| 141 | +
|
| 142 | +# Spot-check the audit section |
| 143 | +Get-Content "$env:USERPROFILE\Desktop\ServerInventory-*\AuditFindings*.csv" | Select-Object -First 20 |
| 144 | +``` |
| 145 | + |
| 146 | +If the AD sections are empty, you're either not on a domain-joined machine or the account running the script doesn't have read access to AD. Re-run under an account with `Domain Users` at minimum. |
| 147 | + |
| 148 | +## Notes |
| 149 | + |
| 150 | +- Skip the Windows Store scan on servers — it's slow and returns nothing useful. Use `-SkipStore` |
| 151 | +- On DCs, always pass `-IncludeAD` — otherwise you're only inventorying the box |
| 152 | +- For fleet runs, wrap the bootstrap in `Invoke-Command` against a list of servers and dump each report to a share |
| 153 | + |
| 154 | +## References |
| 155 | + |
| 156 | +- [PSGallery — VB.ServerInventory](https://www.powershellgallery.com/packages/VB.ServerInventory) |
| 157 | +- [CIS Controls v8](https://www.cisecurity.org/controls/v8) |
| 158 | +- [NIST SP 800-53 Rev. 5](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final) |
| 159 | +- [ACSC Essential Eight](https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/essential-eight) |
| 160 | +- [Microsoft — dsregcmd status](https://learn.microsoft.com/en-us/azure/active-directory/devices/troubleshoot-device-dsregcmd) |
| 161 | + |
| 162 | +{{< post-cta module="VB.ServerInventory" module_url="https://www.powershellgallery.com/packages/VB.ServerInventory" >}} |
0 commit comments