Files
claudetools/projects/msp-tools/msp-audit-scripts/workstation_audit.ps1
Howard Enos 8d975c1b44 import: ingested 160 files from C:\Users\howar\Clients
Howard's personal MSP client documentation folder imported into shared
ClaudeTools repo via /import command. Scope:

Clients (structured MSP docs under clients/<name>/docs/):
- anaise       (NEW)  - 13 files
- cascades-tucson     - 47 files merged (existing had only reports/)
- dataforth           - 18 files merged (alongside incident reports)
- instrumental-music-center - 14 files merged
- khalsa       (NEW)  - 22 files, multi-site (camden, river)
- kittle       (NEW)  - 16 files incl. fix-pdf-preview, gpo-intranet-zone
- lens-auto-brokerage (NEW) - 3 files (name matches SOPS vault)
- _client_template    - 13-file scaffold for new clients

MSP tooling (projects/msp-tools/):
- msp-audit-scripts/ - server_audit.ps1, workstation_audit.ps1, README
- utilities/         - clean_printer_ports, win11_upgrade,
                       screenconnect-toolbox-commands

Credential handling:
- Extracted 1 inline password (Anaise DESKTOP-O8GF4SD / david)
  to SOPS vault: clients/anaise/desktop-o8gf4sd.sops.yaml
- Redacted overview.md with vault reference pattern
- Scanned all 160 files for keys/tokens/connection strings -
  no other credentials found

Skipped:
- Cascades/.claude/settings.local.json (per-machine config)
- Source-root CLAUDE.md (personal, claudetools has its own)
- scripts/server_audit.ps1 and workstation_audit.ps1 at source root
  (identical duplicates of msp-audit-scripts versions)

Memory updates:
- reference_client_docs_structure.md (layout, conventions, active list)
- reference_msp_audit_scripts.md (locations, ScreenConnect 80-char rule)

Session log: session-logs/2026-04-16-howard-client-docs-import.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:43:58 -07:00

1159 lines
50 KiB
PowerShell

# ==========================================
# UNIVERSAL WORKSTATION AUDIT SCRIPT
# Works on Windows 10 / 11 (domain or workgroup)
# Outputs: HOSTNAME_workstation_audit_DATE.json to C:\Temp
# ==========================================
# Auto-relaunch with ExecutionPolicy Bypass if needed
if ($MyInvocation.MyCommand.Path) {
$currentPolicy = Get-ExecutionPolicy -Scope Process
if ($currentPolicy -eq 'Restricted' -or $currentPolicy -eq 'AllSigned') {
Start-Process powershell.exe -ArgumentList "-ExecutionPolicy Bypass -NoProfile -WindowStyle Hidden -File `"$($MyInvocation.MyCommand.Path)`"" -Wait -NoNewWindow
exit
}
}
# Verify running as admin
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "ERROR: This script must be run as Administrator." -ForegroundColor Red
exit 1
}
$Date = Get-Date -Format 'yyyy-MM-dd'
$OutputDir = "C:\Temp"
$Name = $env:COMPUTERNAME
$JsonFile = "$OutputDir\${Name}_workstation_audit_$Date.json"
if (!(Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir | Out-Null }
# Structured data collector
$audit = [ordered]@{
_metadata = [ordered]@{
ScriptVersion = "1.0"
ScriptType = "Workstation"
RunDate = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
RunBy = "$env:USERDOMAIN\$env:USERNAME"
Hostname = $env:COMPUTERNAME
}
_errors = @()
}
Write-Host "======================================="
Write-Host " UNIVERSAL WORKSTATION AUDIT v1.0"
Write-Host " $((Get-Date).ToString('yyyy-MM-dd HH:mm:ss'))"
Write-Host "======================================="
Write-Host ""
# =====================================================
# 1. SYSTEM INFO
# =====================================================
Write-Host "=== 1. SYSTEM INFO ===" -ForegroundColor Cyan
Write-Host " Running: Get-CimInstance Win32_OperatingSystem, Win32_ComputerSystem"
try {
$os = Get-CimInstance Win32_OperatingSystem
$cs = Get-CimInstance Win32_ComputerSystem
$uptime = (Get-Date) - $os.LastBootUpTime
# Friendly version (e.g., 23H2)
$displayVersion = "N/A"
try {
$displayVersion = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction Stop).DisplayVersion
} catch {}
$sysInfo = [ordered]@{
Hostname = $env:COMPUTERNAME
OS = $os.Caption
OSVersion = $os.Version
DisplayVersion = $displayVersion
BuildNumber = $os.BuildNumber
Architecture = $os.OSArchitecture
InstallDate = $os.InstallDate.ToString("yyyy-MM-dd")
LastBoot = $os.LastBootUpTime.ToString("yyyy-MM-dd HH:mm:ss")
UptimeDays = [math]::Round($uptime.TotalDays, 1)
Domain = $cs.Domain
DomainJoined = $cs.PartOfDomain
CurrentUser = $cs.UserName
TotalRAM_GB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 1)
}
$sysInfo.GetEnumerator() | ForEach-Object { Write-Host " $($_.Key): $($_.Value)" }
$audit.SystemInfo = $sysInfo
Write-Host " [OK] System info collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "SystemInfo"; Error = $_.Exception.Message }
}
# =====================================================
# 2. HARDWARE
# =====================================================
Write-Host ""
Write-Host "=== 2. HARDWARE ===" -ForegroundColor Cyan
Write-Host " Running: Get-CimInstance Win32_ComputerSystem, Win32_Processor, Win32_BIOS, Win32_SystemEnclosure"
try {
$hw = Get-CimInstance Win32_ComputerSystem
$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
$bios = Get-CimInstance Win32_BIOS
$encl = Get-CimInstance Win32_SystemEnclosure | Select-Object -First 1
$battery = Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue
$hardware = [ordered]@{
Manufacturer = $hw.Manufacturer
Model = $hw.Model
SerialNumber = if ($bios.SerialNumber) { $bios.SerialNumber } else { $encl.SerialNumber }
CPU = $cpu.Name
CPUCores = $cpu.NumberOfCores
CPULogical = $cpu.NumberOfLogicalProcessors
RAM_GB = [math]::Round($hw.TotalPhysicalMemory / 1GB, 1)
BIOSVersion = $bios.SMBIOSBIOSVersion
ChassisType = switch ($encl.ChassisTypes[0]) {
3 {"Desktop"} 4 {"Low Profile Desktop"} 5 {"Pizza Box"} 6 {"Mini Tower"}
7 {"Tower"} 8 {"Portable"} 9 {"Laptop"} 10 {"Notebook"} 11 {"Hand Held"}
12 {"Docking Station"} 13 {"All in One"} 14 {"Sub Notebook"} 15 {"Space-Saving"}
default {"Other ($($encl.ChassisTypes[0]))"}
}
HasBattery = ($null -ne $battery)
}
$hardware.GetEnumerator() | ForEach-Object { Write-Host " $($_.Key): $($_.Value)" }
$audit.Hardware = $hardware
Write-Host " [OK] Hardware info collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "Hardware"; Error = $_.Exception.Message }
}
# =====================================================
# 3. OS ACTIVATION STATUS
# =====================================================
Write-Host ""
Write-Host "=== 3. OS ACTIVATION STATUS ===" -ForegroundColor Cyan
Write-Host " Running: Get-CimInstance SoftwareLicensingProduct"
try {
$lic = Get-CimInstance SoftwareLicensingProduct |
Where-Object { $_.PartialProductKey -and $_.Name -like '*Windows*' } |
Select-Object -First 1
$statusText = switch ($lic.LicenseStatus) {
0 {"Unlicensed"} 1 {"Licensed"} 2 {"OOB Grace"} 3 {"OOT Grace"}
4 {"Non-Genuine"} 5 {"Notification"} 6 {"Extended Grace"} default {"Unknown"}
}
$activation = [ordered]@{
ProductName = $lic.Name
LicenseStatus = $statusText
StatusCode = $lic.LicenseStatus
PartialKey = $lic.PartialProductKey
LicenseFamily = $lic.LicenseFamily
}
$activation.GetEnumerator() | ForEach-Object { Write-Host " $($_.Key): $($_.Value)" }
if ($lic.LicenseStatus -ne 1) {
Write-Host " WARNING: This machine is NOT fully licensed!" -ForegroundColor Red
}
$audit.Activation = $activation
Write-Host " [OK] Activation status collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "Activation"; Error = $_.Exception.Message }
}
# =====================================================
# 4. STORAGE
# =====================================================
Write-Host ""
Write-Host "=== 4. STORAGE ===" -ForegroundColor Cyan
Write-Host " Running: Get-Volume, Get-PhysicalDisk"
try {
$volumes = Get-Volume | Where-Object { $_.DriveLetter } | Select-Object DriveLetter,
FileSystemLabel, FileSystem,
@{N='SizeGB';E={[math]::Round($_.Size/1GB,1)}},
@{N='FreeGB';E={[math]::Round($_.SizeRemaining/1GB,1)}},
@{N='UsedPct';E={if($_.Size -gt 0){[math]::Round(($_.Size - $_.SizeRemaining)/$_.Size * 100,0)}else{0}}},
HealthStatus
$volumes | Format-Table -AutoSize
$audit.Volumes = @($volumes | ForEach-Object {
[ordered]@{
Drive = "$($_.DriveLetter):"; Label = $_.FileSystemLabel; FileSystem = $_.FileSystem
SizeGB = $_.SizeGB; FreeGB = $_.FreeGB; UsedPct = $_.UsedPct; Health = "$($_.HealthStatus)"
}
})
Write-Host " [OK] Volumes collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "Volumes"; Error = $_.Exception.Message }
}
Write-Host " Running: Get-PhysicalDisk"
try {
$disks = Get-PhysicalDisk | Select-Object DeviceId, FriendlyName, MediaType,
@{N='SizeGB';E={[math]::Round($_.Size/1GB,1)}}, HealthStatus, OperationalStatus
$disks | Format-Table -AutoSize
$audit.PhysicalDisks = @($disks | ForEach-Object {
[ordered]@{
DeviceId = "$($_.DeviceId)"; Name = $_.FriendlyName; MediaType = "$($_.MediaType)"
SizeGB = $_.SizeGB; Health = "$($_.HealthStatus)"; Status = "$($_.OperationalStatus)"
}
})
Write-Host " [OK] Physical disks collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] PhysicalDisk: $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "PhysicalDisks"; Error = $_.Exception.Message }
}
# =====================================================
# 5. BITLOCKER STATUS
# =====================================================
Write-Host ""
Write-Host "=== 5. BITLOCKER STATUS ===" -ForegroundColor Cyan
Write-Host " Running: Get-BitLockerVolume"
try {
$bl = Get-BitLockerVolume -ErrorAction Stop
$bl | ForEach-Object {
Write-Host " $($_.MountPoint): $($_.VolumeStatus), Protection: $($_.ProtectionStatus), Method: $($_.EncryptionMethod)"
}
$audit.BitLocker = @($bl | ForEach-Object {
[ordered]@{
MountPoint = "$($_.MountPoint)"; VolumeStatus = "$($_.VolumeStatus)"
Protection = "$($_.ProtectionStatus)"; Method = "$($_.EncryptionMethod)"
KeyProtectors = @($_.KeyProtector | ForEach-Object { "$($_.KeyProtectorType)" })
}
})
Write-Host " [OK] BitLocker status collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
Write-Host " Trying manage-bde fallback..." -ForegroundColor Yellow
try {
$bde = manage-bde -status 2>&1
$bde | ForEach-Object { Write-Host " $_" }
}
catch {
Write-Host " BitLocker not available on this edition" -ForegroundColor Yellow
}
$audit._errors += @{ Section = "BitLocker"; Error = $_.Exception.Message }
}
# =====================================================
# 6. NETWORK CONFIG
# =====================================================
Write-Host ""
Write-Host "=== 6. NETWORK CONFIG ===" -ForegroundColor Cyan
Write-Host " Running: Get-NetAdapter"
try {
$adapters = Get-NetAdapter | Select-Object Name, InterfaceDescription, MacAddress, Status, LinkSpeed
$adapters | Format-Table -AutoSize
$audit.NetworkAdapters = @($adapters | ForEach-Object {
[ordered]@{ Name = $_.Name; Description = $_.InterfaceDescription; MAC = $_.MacAddress; Status = "$($_.Status)"; LinkSpeed = $_.LinkSpeed }
})
Write-Host " [OK] Adapters collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "NetworkAdapters"; Error = $_.Exception.Message }
}
Write-Host " Running: Get-NetIPConfiguration"
try {
$ipConfigs = Get-NetIPConfiguration | Where-Object { $_.IPv4Address }
$audit.IPConfig = @($ipConfigs | ForEach-Object {
[ordered]@{
Interface = $_.InterfaceAlias
IPv4 = "$($_.IPv4Address.IPAddress)"
PrefixLength = $_.IPv4Address.PrefixLength
Gateway = "$($_.IPv4DefaultGateway.NextHop)"
DNS = @($_.DNSServer | Where-Object { $_.AddressFamily -eq 2 } | ForEach-Object { $_.ServerAddresses }) | Select-Object -Unique
DHCPEnabled = (Get-NetIPInterface -InterfaceAlias $_.InterfaceAlias -AddressFamily IPv4 -ErrorAction SilentlyContinue).Dhcp
}
})
$ipConfigs | Format-List InterfaceAlias, IPv4Address, IPv4DefaultGateway, DnsServer
Write-Host " [OK] IP config collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "IPConfig"; Error = $_.Exception.Message }
}
# =====================================================
# 7. DOMAIN MEMBERSHIP
# =====================================================
Write-Host ""
Write-Host "=== 7. DOMAIN MEMBERSHIP ===" -ForegroundColor Cyan
Write-Host " Running: Win32_ComputerSystem + nltest + DirectorySearcher"
try {
$cs = Get-CimInstance Win32_ComputerSystem
$domainInfo = [ordered]@{
DomainJoined = $cs.PartOfDomain
Domain = $cs.Domain
Workgroup = if (-not $cs.PartOfDomain) { $cs.Workgroup } else { $null }
}
if ($cs.PartOfDomain) {
# Get site
try {
$site = nltest /dsgetsite 2>&1 | Select-Object -First 1
$domainInfo.ADSite = $site.Trim()
} catch {}
# Get DC
try {
$dcInfo = nltest /dsgetdc:$($cs.Domain) 2>&1
$dcLine = ($dcInfo | Select-String "DC: \\\\").Line
if ($dcLine) { $domainInfo.DC = $dcLine.Trim() }
} catch {}
# Get computer OU via DirectorySearcher (no RSAT needed)
try {
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.Filter = "(&(objectClass=computer)(cn=$env:COMPUTERNAME))"
$result = $searcher.FindOne()
if ($result) { $domainInfo.ComputerDN = "$($result.Properties['distinguishedname'][0])" }
} catch {}
}
$domainInfo.GetEnumerator() | ForEach-Object { Write-Host " $($_.Key): $($_.Value)" }
$audit.DomainMembership = $domainInfo
Write-Host " [OK] Domain membership collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "DomainMembership"; Error = $_.Exception.Message }
}
# =====================================================
# 8. CURRENT USER INFO
# =====================================================
Write-Host ""
Write-Host "=== 8. CURRENT USER INFO ===" -ForegroundColor Cyan
Write-Host " Running: query user, whoami /groups"
try {
$currentUser = [ordered]@{
Username = $env:USERNAME
Domain = $env:USERDOMAIN
}
# Logged-in sessions
Write-Host " Active sessions:" -ForegroundColor Yellow
try {
$sessions = query user 2>&1
$sessions | ForEach-Object { Write-Host " $_" }
} catch { Write-Host " Unable to query sessions" }
# Group memberships (of the account running the script)
Write-Host " Group memberships:" -ForegroundColor Yellow
try {
$groups = whoami /groups /fo csv 2>&1 | ConvertFrom-Csv -ErrorAction Stop
$currentUser.Groups = @($groups | ForEach-Object { $_.'Group Name' })
$groups | ForEach-Object { Write-Host " $($_.'Group Name')" }
} catch {
Write-Host " Unable to enumerate groups" -ForegroundColor Yellow
}
$audit.CurrentUser = $currentUser
Write-Host " [OK] User info collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "CurrentUser"; Error = $_.Exception.Message }
}
# =====================================================
# 9. LOCAL USERS
# =====================================================
Write-Host ""
Write-Host "=== 9. LOCAL USERS ===" -ForegroundColor Cyan
Write-Host " Running: Get-LocalUser"
try {
$localUsers = Get-LocalUser | Select-Object Name, Enabled, LastLogon, PasswordRequired, PasswordLastSet
$localUsers | Format-Table -AutoSize
$audit.LocalUsers = @($localUsers | ForEach-Object {
[ordered]@{
Name = $_.Name; Enabled = $_.Enabled
LastLogon = if ($_.LastLogon) { $_.LastLogon.ToString("yyyy-MM-dd") } else { $null }
PasswordRequired = $_.PasswordRequired
PasswordLastSet = if ($_.PasswordLastSet) { $_.PasswordLastSet.ToString("yyyy-MM-dd") } else { $null }
}
})
Write-Host " [OK] Local users collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message) - trying net user fallback" -ForegroundColor Red
try { net user } catch {}
$audit._errors += @{ Section = "LocalUsers"; Error = $_.Exception.Message }
}
# =====================================================
# 10. LOCAL ADMINISTRATORS
# =====================================================
Write-Host ""
Write-Host "=== 10. LOCAL ADMINISTRATORS ===" -ForegroundColor Cyan
Write-Host " Running: Get-LocalGroupMember Administrators"
try {
$localAdmins = Get-LocalGroupMember Administrators | Select-Object Name, PrincipalSource, ObjectClass
$localAdmins | Format-Table -AutoSize
$audit.LocalAdmins = @($localAdmins | ForEach-Object {
[ordered]@{ Name = $_.Name; Source = "$($_.PrincipalSource)"; Type = "$($_.ObjectClass)" }
})
Write-Host " [OK] Local admins collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message) - trying net localgroup fallback" -ForegroundColor Red
try { net localgroup Administrators } catch {}
$audit._errors += @{ Section = "LocalAdmins"; Error = $_.Exception.Message }
}
# =====================================================
# 11. MAPPED DRIVES
# =====================================================
Write-Host ""
Write-Host "=== 11. MAPPED DRIVES ===" -ForegroundColor Cyan
Write-Host " Running: Get-PSDrive, net use, HKU registry"
try {
# Current session drives
$mappedDrives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.DisplayRoot } |
Select-Object Name, @{N='UNC';E={$_.DisplayRoot}}
if ($mappedDrives) {
Write-Host " Current session drives:" -ForegroundColor Yellow
$mappedDrives | Format-Table -AutoSize
}
# net use
Write-Host " net use output:" -ForegroundColor Yellow
net use 2>&1 | ForEach-Object { Write-Host " $_" }
# Try to get logged-in user's persistent drives via HKU
$audit.MappedDrives = @()
try {
$loggedOnUser = (Get-CimInstance Win32_ComputerSystem).UserName
if ($loggedOnUser) {
$sid = (New-Object System.Security.Principal.NTAccount($loggedOnUser)).Translate(
[System.Security.Principal.SecurityIdentifier]).Value
try {
New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS -ErrorAction Stop | Out-Null
$userDrives = Get-ItemProperty "HKU:\$sid\Network\*" -ErrorAction SilentlyContinue
if ($userDrives) {
Write-Host " Persistent drives for $loggedOnUser`:" -ForegroundColor Yellow
$userDrives | ForEach-Object {
Write-Host " $($_.PSChildName): -> $($_.RemotePath)"
$audit.MappedDrives += [ordered]@{ Drive = "$($_.PSChildName):"; UNC = $_.RemotePath; User = $loggedOnUser }
}
}
}
finally {
Remove-PSDrive HKU -ErrorAction SilentlyContinue
}
}
}
catch {
Write-Host " Unable to read user registry for mapped drives: $($_.Exception.Message)" -ForegroundColor Yellow
}
# Also add from PSDrive
if ($mappedDrives) {
foreach ($d in $mappedDrives) {
if (-not ($audit.MappedDrives | Where-Object { $_.Drive -eq "$($d.Name):" })) {
$audit.MappedDrives += [ordered]@{ Drive = "$($d.Name):"; UNC = $d.UNC; User = "CurrentSession" }
}
}
}
Write-Host " [OK] Mapped drives collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "MappedDrives"; Error = $_.Exception.Message }
}
# =====================================================
# 12. WIFI PROFILES
# =====================================================
Write-Host ""
Write-Host "=== 12. WIFI PROFILES ===" -ForegroundColor Cyan
Write-Host " Running: netsh wlan show profiles, netsh wlan show interfaces"
try {
Write-Host " Saved WiFi profiles:" -ForegroundColor Yellow
$profiles = netsh wlan show profiles 2>&1
$profiles | ForEach-Object { Write-Host " $_" }
$profileNames = @($profiles | Select-String "All User Profile\s+:\s+(.+)" | ForEach-Object { $_.Matches.Groups[1].Value.Trim() })
Write-Host ""
Write-Host " Current connection:" -ForegroundColor Yellow
$interfaces = netsh wlan show interfaces 2>&1
$interfaces | ForEach-Object { Write-Host " $_" }
$connectedSSID = ($interfaces | Select-String "SSID\s+:\s+(.+)" | Select-Object -First 1)
if ($connectedSSID) { $connectedSSID = $connectedSSID.Matches.Groups[1].Value.Trim() }
$audit.WiFi = [ordered]@{
SavedProfiles = $profileNames
ConnectedSSID = $connectedSSID
}
Write-Host " [OK] WiFi profiles collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
Write-Host " (No WiFi adapter or service not running)" -ForegroundColor Yellow
$audit._errors += @{ Section = "WiFi"; Error = $_.Exception.Message }
}
# =====================================================
# 13. ANTIVIRUS / SECURITY CENTER
# =====================================================
Write-Host ""
Write-Host "=== 13. ANTIVIRUS / SECURITY CENTER ===" -ForegroundColor Cyan
Write-Host " Running: Get-CimInstance root/SecurityCenter2 AntiVirusProduct"
try {
$avProducts = Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct -ErrorAction Stop
$audit.Antivirus = @($avProducts | ForEach-Object {
$state = '{0:X6}' -f $_.productState
$enabled = if ($state.Substring(2,2) -eq '10') { $true } else { $false }
$upToDate = if ($state.Substring(4,2) -eq '00') { $true } else { $false }
Write-Host " $($_.displayName): Enabled=$enabled, UpToDate=$upToDate"
[ordered]@{
Name = $_.displayName; Enabled = $enabled; UpToDate = $upToDate
Path = $_.pathToSignedProductExe
}
})
Write-Host " [OK] AV products collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "Antivirus"; Error = $_.Exception.Message }
}
Write-Host " Running: Get-MpComputerStatus (Windows Defender)"
try {
$defender = Get-MpComputerStatus -ErrorAction Stop
$defenderInfo = [ordered]@{
AMRunningMode = "$($defender.AMRunningMode)"
AntivirusEnabled = $defender.AntivirusEnabled
RealTimeProtection = $defender.RealTimeProtectionEnabled
BehaviorMonitor = $defender.BehaviorMonitorEnabled
SignatureLastUpdated = if ($defender.AntivirusSignatureLastUpdated) { $defender.AntivirusSignatureLastUpdated.ToString("yyyy-MM-dd HH:mm") } else { $null }
LastQuickScan = if ($defender.QuickScanEndTime) { $defender.QuickScanEndTime.ToString("yyyy-MM-dd HH:mm") } else { $null }
LastFullScan = if ($defender.FullScanEndTime) { $defender.FullScanEndTime.ToString("yyyy-MM-dd HH:mm") } else { $null }
}
$defenderInfo.GetEnumerator() | ForEach-Object { Write-Host " Defender $($_.Key): $($_.Value)" }
$audit.WindowsDefender = $defenderInfo
Write-Host " [OK] Defender status collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "WindowsDefender"; Error = $_.Exception.Message }
}
# =====================================================
# 14. PRINTERS
# =====================================================
Write-Host ""
Write-Host "=== 14. PRINTERS ===" -ForegroundColor Cyan
Write-Host " Running: Get-Printer, Get-PrinterPort"
try {
$printers = Get-Printer | Select-Object Name, DriverName, PortName, Type, Shared
$printers | Format-Table -AutoSize
$ports = Get-PrinterPort | Where-Object { $_.Name -like 'TCP_*' -or $_.Name -like 'IP_*' -or $_.PrinterHostAddress } |
Select-Object Name, PrinterHostAddress, PortNumber
if ($ports) {
Write-Host " Network printer ports:" -ForegroundColor DarkGray
$ports | Format-Table -AutoSize
}
$audit.Printers = @($printers | ForEach-Object {
[ordered]@{ Name = $_.Name; Driver = $_.DriverName; Port = $_.PortName; Type = "$($_.Type)"; Shared = $_.Shared }
})
$audit.PrinterPorts = @($ports | ForEach-Object {
[ordered]@{ Name = $_.Name; IP = $_.PrinterHostAddress; Port = $_.PortNumber }
})
Write-Host " [OK] Printers collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "Printers"; Error = $_.Exception.Message }
}
# =====================================================
# 15. INSTALLED SOFTWARE (registry-based)
# =====================================================
Write-Host ""
Write-Host "=== 15. INSTALLED SOFTWARE ===" -ForegroundColor Cyan
Write-Host " Running: Registry query (HKLM Uninstall keys)"
try {
$regPaths = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
$software = Get-ItemProperty $regPaths -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName } |
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
Sort-Object DisplayName -Unique
$software | Format-Table -AutoSize
$audit.InstalledSoftware = @($software | ForEach-Object {
[ordered]@{ Name = $_.DisplayName; Version = $_.DisplayVersion; Publisher = $_.Publisher; InstallDate = $_.InstallDate }
})
Write-Host " [OK] Software collected - $($software.Count) packages" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "InstalledSoftware"; Error = $_.Exception.Message }
}
# =====================================================
# 16. STARTUP PROGRAMS
# =====================================================
Write-Host ""
Write-Host "=== 16. STARTUP PROGRAMS ===" -ForegroundColor Cyan
Write-Host " Running: Get-CimInstance Win32_StartupCommand"
try {
$startup = Get-CimInstance Win32_StartupCommand | Select-Object Name, Command, Location, User
$startup | Format-Table -AutoSize
$audit.StartupPrograms = @($startup | ForEach-Object {
[ordered]@{ Name = $_.Name; Command = $_.Command; Location = $_.Location; User = $_.User }
})
Write-Host " [OK] Startup programs collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "StartupPrograms"; Error = $_.Exception.Message }
}
# =====================================================
# 17. WINDOWS UPDATES (last 15)
# =====================================================
Write-Host ""
Write-Host "=== 17. WINDOWS UPDATES (last 15) ===" -ForegroundColor Cyan
Write-Host " Running: Get-HotFix"
try {
$updates = Get-HotFix | Sort-Object InstalledOn -Descending -ErrorAction SilentlyContinue | Select-Object -First 15 |
Select-Object HotFixID, Description, InstalledOn, InstalledBy
$updates | Format-Table -AutoSize
$audit.WindowsUpdates = @($updates | ForEach-Object {
[ordered]@{
KB = $_.HotFixID; Description = $_.Description
InstalledOn = if ($_.InstalledOn) { $_.InstalledOn.ToString("yyyy-MM-dd") } else { $null }
}
})
Write-Host " [OK] Updates collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "WindowsUpdates"; Error = $_.Exception.Message }
}
# =====================================================
# 18. WINDOWS UPDATE SETTINGS
# =====================================================
Write-Host ""
Write-Host "=== 18. WINDOWS UPDATE SETTINGS ===" -ForegroundColor Cyan
Write-Host " Running: Registry check for WSUS/AU settings"
try {
$wuSettings = [ordered]@{}
$wu = Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -ErrorAction SilentlyContinue
if ($wu) {
$wuSettings.WUServer = $wu.WUServer
$wuSettings.WUStatusServer = $wu.WUStatusServer
Write-Host " WSUS Server: $($wu.WUServer)"
} else {
Write-Host " No WSUS configured (using Windows Update directly)"
$wuSettings.WUServer = "None (Windows Update)"
}
$au = Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -ErrorAction SilentlyContinue
if ($au) {
$auOption = switch ($au.AUOptions) {
2 {"Notify before download"} 3 {"Auto download, notify install"}
4 {"Auto download, auto install"} 5 {"Allow local admin to choose"} default {"$($au.AUOptions)"}
}
$wuSettings.AutoUpdateOption = $auOption
Write-Host " Auto Update: $auOption"
}
$audit.WindowsUpdateSettings = $wuSettings
Write-Host " [OK] Update settings collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "WindowsUpdateSettings"; Error = $_.Exception.Message }
}
# =====================================================
# 19. NETWORK SHARES
# =====================================================
Write-Host ""
Write-Host "=== 19. NETWORK SHARES (on this machine) ===" -ForegroundColor Cyan
Write-Host " Running: Get-SmbShare"
try {
$shares = Get-SmbShare | Where-Object { $_.Name -notlike '*$' }
if ($shares) {
$shares | Format-Table Name, Path, Description -AutoSize
$audit.LocalShares = @($shares | ForEach-Object {
[ordered]@{ Name = $_.Name; Path = $_.Path; Description = $_.Description }
})
} else {
Write-Host " No user shares on this machine"
$audit.LocalShares = @()
}
Write-Host " [OK] Shares collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "LocalShares"; Error = $_.Exception.Message }
}
# =====================================================
# 20. POWER SETTINGS
# =====================================================
Write-Host ""
Write-Host "=== 20. POWER SETTINGS ===" -ForegroundColor Cyan
Write-Host " Running: powercfg /getactivescheme"
try {
$powerScheme = powercfg /getactivescheme 2>&1
Write-Host " $powerScheme"
$audit.PowerScheme = "$powerScheme".Trim()
Write-Host " [OK] Power settings collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "PowerSettings"; Error = $_.Exception.Message }
}
# =====================================================
# 21. EVENT LOG ERRORS (last 14 days)
# =====================================================
Write-Host ""
Write-Host "=== 21. EVENT LOG ERRORS (last 14 days) ===" -ForegroundColor Cyan
Write-Host " Running: Get-WinEvent - Critical/Error, last 14 days"
try {
$errSince = (Get-Date).AddDays(-14)
$errors = Get-WinEvent -FilterHashtable @{LogName='System'; Level=1,2; StartTime=$errSince} -MaxEvents 30 -ErrorAction Stop |
Select-Object TimeCreated, Id, LevelDisplayName, ProviderName, @{N='Message';E={$_.Message.Substring(0, [Math]::Min(200, $_.Message.Length))}}
Write-Host " $($errors.Count) critical/error events found"
$errors | Format-Table TimeCreated, Id, ProviderName -AutoSize
$audit.EventLogErrors = @($errors | ForEach-Object {
[ordered]@{ Time = $_.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss"); EventId = $_.Id; Level = $_.LevelDisplayName; Source = $_.ProviderName; Message = $_.Message }
})
Write-Host " [OK] Event log errors collected" -ForegroundColor Green
}
catch {
Write-Host " No critical/error events in System log (last 14 days)" -ForegroundColor Green
$audit.EventLogErrors = @()
}
# =====================================================
# 22. EVENT LOG WARNINGS (last 14 days)
# =====================================================
Write-Host ""
Write-Host "=== 22. EVENT LOG WARNINGS (last 14 days) ===" -ForegroundColor Cyan
Write-Host " Running: Get-WinEvent - Warning level, last 14 days"
try {
$warnSince = (Get-Date).AddDays(-14)
$warnings = Get-WinEvent -FilterHashtable @{LogName='System'; Level=3; StartTime=$warnSince} -MaxEvents 50 -ErrorAction Stop |
Select-Object TimeCreated, Id, ProviderName, @{N='Message';E={$_.Message.Substring(0, [Math]::Min(200, $_.Message.Length))}}
Write-Host " $($warnings.Count) warning events found"
$warnings | Format-Table TimeCreated, Id, ProviderName -AutoSize
$audit.EventLogWarnings = @($warnings | ForEach-Object {
[ordered]@{ Time = $_.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss"); EventId = $_.Id; Source = $_.ProviderName; Message = $_.Message }
})
Write-Host " [OK] Event log warnings collected" -ForegroundColor Green
}
catch {
Write-Host " No warning events in System log (last 14 days)" -ForegroundColor Green
$audit.EventLogWarnings = @()
}
# =====================================================
# 23. EVENT LOG SETTINGS
# =====================================================
Write-Host ""
Write-Host "=== 23. EVENT LOG SETTINGS ===" -ForegroundColor Cyan
Write-Host " Running: Get-WinEvent -ListLog"
try {
$importantLogs = @('Application', 'Security', 'System', 'Setup', 'Microsoft-Windows-PowerShell/Operational')
$audit.EventLogSettings = @()
foreach ($logName in $importantLogs) {
try {
$log = Get-WinEvent -ListLog $logName -ErrorAction Stop
$logInfo = [ordered]@{
LogName = $log.LogName
MaxSizeKB = [math]::Round($log.MaximumSizeInBytes / 1KB)
CurrentSizeKB = [math]::Round($log.FileSize / 1KB)
RecordCount = $log.RecordCount
LogMode = "$($log.LogMode)"
IsEnabled = $log.IsEnabled
}
Write-Host " $($log.LogName): Max=$($logInfo.MaxSizeKB)KB, Mode=$($log.LogMode), Records=$($log.RecordCount)"
$audit.EventLogSettings += $logInfo
}
catch { Write-Host " $logName`: not available" -ForegroundColor DarkGray }
}
Write-Host " [OK] Event log settings collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "EventLogSettings"; Error = $_.Exception.Message }
}
# =====================================================
# 24. WINDOWS FIREWALL PROFILES
# =====================================================
Write-Host ""
Write-Host "=== 24. WINDOWS FIREWALL PROFILES ===" -ForegroundColor Cyan
Write-Host " Running: Get-NetFirewallProfile"
try {
$fwProfiles = Get-NetFirewallProfile -ErrorAction Stop
$audit.FirewallProfiles = @($fwProfiles | ForEach-Object {
$p = [ordered]@{
Profile = "$($_.Name)"; Enabled = $_.Enabled
DefaultInboundAction = "$($_.DefaultInboundAction)"
DefaultOutboundAction = "$($_.DefaultOutboundAction)"
}
Write-Host " $($_.Name): Enabled=$($_.Enabled), Inbound=$($_.DefaultInboundAction), Outbound=$($_.DefaultOutboundAction)"
if (-not $_.Enabled) {
Write-Host " [WARN] $($_.Name) firewall profile is DISABLED" -ForegroundColor Red
}
$p
})
Write-Host " [OK] Firewall profiles collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "FirewallProfiles"; Error = $_.Exception.Message }
}
# =====================================================
# 25. RDP SECURITY SETTINGS
# =====================================================
Write-Host ""
Write-Host "=== 25. RDP SECURITY SETTINGS ===" -ForegroundColor Cyan
Write-Host " Running: Registry check for RDP settings"
try {
$tsReg = 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server'
$rdpReg = 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp'
$rdpEnabled = (Get-ItemProperty $tsReg -ErrorAction Stop).fDenyTSConnections
$nla = (Get-ItemProperty $rdpReg -ErrorAction SilentlyContinue).UserAuthentication
$rdpSettings = [ordered]@{
RDPEnabled = ($rdpEnabled -eq 0)
NLARequired = ($nla -eq 1)
}
$rdpSettings.GetEnumerator() | ForEach-Object { Write-Host " $($_.Key): $($_.Value)" }
if ($rdpSettings.RDPEnabled -and -not $rdpSettings.NLARequired) {
Write-Host " [WARN] RDP is enabled but NLA is NOT required" -ForegroundColor Yellow
}
$audit.RDPSettings = $rdpSettings
Write-Host " [OK] RDP settings collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "RDPSettings"; Error = $_.Exception.Message }
}
# =====================================================
# 26. UAC SETTINGS
# =====================================================
Write-Host ""
Write-Host "=== 26. UAC SETTINGS ===" -ForegroundColor Cyan
Write-Host " Running: Registry check for UAC"
try {
$uacReg = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -ErrorAction Stop
$uacSettings = [ordered]@{
EnableLUA = $uacReg.EnableLUA
ConsentPromptBehaviorAdmin = switch ($uacReg.ConsentPromptBehaviorAdmin) {
0 {"Elevate without prompting"} 1 {"Prompt for credentials on secure desktop"}
2 {"Prompt for consent on secure desktop"} 3 {"Prompt for credentials"}
4 {"Prompt for consent"} 5 {"Prompt for consent for non-Windows binaries"} default {"Unknown"}
}
}
$uacSettings.GetEnumerator() | ForEach-Object { Write-Host " $($_.Key): $($_.Value)" }
if ($uacReg.EnableLUA -ne 1) {
Write-Host " [WARN] UAC is DISABLED" -ForegroundColor Red
}
$audit.UACSettings = $uacSettings
Write-Host " [OK] UAC settings collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "UACSettings"; Error = $_.Exception.Message }
}
# =====================================================
# 27. SCREEN LOCK / INACTIVITY TIMEOUT
# =====================================================
Write-Host ""
Write-Host "=== 27. SCREEN LOCK / INACTIVITY TIMEOUT ===" -ForegroundColor Cyan
Write-Host " Running: Registry check for screen lock policy"
try {
$screenLock = [ordered]@{}
$inactivity = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -ErrorAction SilentlyContinue).InactivityTimeoutSecs
$screenLock.InactivityTimeoutSecs = $inactivity
if ($inactivity) {
Write-Host " Machine inactivity timeout: $inactivity seconds"
} else {
Write-Host " Machine inactivity timeout: Not configured"
Write-Host " [WARN] No machine inactivity timeout - HIPAA requires automatic session lock" -ForegroundColor Yellow
}
# Screensaver (may be SYSTEM context - try HKLM policy too)
$ssPolicy = Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Control Panel\Desktop' -ErrorAction SilentlyContinue
if ($ssPolicy) {
$screenLock.ScreenSaverGPO = $true
$screenLock.ScreenSaveTimeout = $ssPolicy.ScreenSaveTimeOut
$screenLock.ScreenSaverSecure = $ssPolicy.ScreenSaverIsSecure
Write-Host " Screensaver GPO: Timeout=$($ssPolicy.ScreenSaveTimeOut)sec, Password=$($ssPolicy.ScreenSaverIsSecure)"
} else {
$screenLock.ScreenSaverGPO = $false
Write-Host " No screensaver GPO detected"
}
$audit.ScreenLockPolicy = $screenLock
Write-Host " [OK] Screen lock settings collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "ScreenLock"; Error = $_.Exception.Message }
}
# =====================================================
# 28. USB STORAGE POLICY
# =====================================================
Write-Host ""
Write-Host "=== 28. USB STORAGE POLICY ===" -ForegroundColor Cyan
Write-Host " Running: Registry check for USB storage restrictions"
try {
$usbStorage = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\USBSTOR' -ErrorAction Stop).Start
$usbStatus = switch ($usbStorage) { 3 { "Enabled" } 4 { "Disabled" } default { "Unknown ($usbStorage)" } }
Write-Host " USB Storage: $usbStatus"
$usbGPO = Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices' -ErrorAction SilentlyContinue
if ($usbGPO) { Write-Host " GPO removable storage restrictions detected" }
if ($usbStorage -eq 3 -and -not $usbGPO) {
Write-Host " [WARN] USB storage is unrestricted - data exfiltration risk" -ForegroundColor Yellow
}
$audit.USBStoragePolicy = [ordered]@{ Status = $usbStatus; GPORestrictions = ($null -ne $usbGPO) }
Write-Host " [OK] USB storage policy collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "USBStorage"; Error = $_.Exception.Message }
}
# =====================================================
# 29. TLS/SSL CONFIGURATION
# =====================================================
Write-Host ""
Write-Host "=== 29. TLS/SSL CONFIGURATION ===" -ForegroundColor Cyan
Write-Host " Running: Registry check for TLS protocol versions"
try {
$tlsVersions = @('SSL 2.0', 'SSL 3.0', 'TLS 1.0', 'TLS 1.1', 'TLS 1.2', 'TLS 1.3')
$audit.TLSConfig = [ordered]@{}
foreach ($ver in $tlsVersions) {
$clientPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$ver\Client"
$enabled = $null
if (Test-Path $clientPath) {
$enabled = (Get-ItemProperty $clientPath -ErrorAction SilentlyContinue).Enabled
}
$status = if ($null -eq $enabled) { "OS Default" } elseif ($enabled -eq 0) { "Disabled" } else { "Enabled" }
Write-Host " $ver`: $status"
$audit.TLSConfig[$ver] = $status
}
Write-Host " [OK] TLS config collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "TLSConfig"; Error = $_.Exception.Message }
}
# =====================================================
# 30. AUTOPLAY / AUTORUN
# =====================================================
Write-Host ""
Write-Host "=== 30. AUTOPLAY / AUTORUN ===" -ForegroundColor Cyan
Write-Host " Running: Registry check for AutoPlay/AutoRun"
try {
$autoplay = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer' -ErrorAction SilentlyContinue).NoDriveTypeAutoRun
$autorun = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer' -ErrorAction SilentlyContinue).NoAutorun
if ($autoplay -eq 255) {
Write-Host " AutoPlay: Disabled for all drives" -ForegroundColor Green
} elseif ($autoplay) {
Write-Host " AutoPlay: Partially restricted (value: $autoplay)" -ForegroundColor Yellow
} else {
Write-Host " AutoPlay: Not restricted by policy"
Write-Host " [WARN] AutoPlay is not disabled - malware risk from USB/optical media" -ForegroundColor Yellow
}
$audit.AutoPlay = [ordered]@{ NoDriveTypeAutoRun = $autoplay; NoAutorun = $autorun }
Write-Host " [OK] AutoPlay settings collected" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "AutoPlay"; Error = $_.Exception.Message }
}
# =====================================================
# 31. SERVICES (with expected state check)
# =====================================================
Write-Host ""
Write-Host "=== 31. SERVICES (Auto start - stopped unexpectedly) ===" -ForegroundColor Cyan
Write-Host " Running: Get-Service - Auto start services that are stopped"
try {
$stoppedAuto = Get-Service | Where-Object { $_.StartType -eq 'Automatic' -and $_.Status -ne 'Running' } |
Select-Object Name, DisplayName, Status, StartType
if ($stoppedAuto) {
Write-Host " [WARN] $($stoppedAuto.Count) auto-start services are NOT running:" -ForegroundColor Yellow
$stoppedAuto | Format-Table -AutoSize
} else {
Write-Host " All auto-start services are running" -ForegroundColor Green
}
$audit.StoppedAutoServices = @($stoppedAuto | ForEach-Object {
[ordered]@{ Name = $_.Name; DisplayName = $_.DisplayName; Status = "$($_.Status)" }
})
Write-Host " [OK] Service state check complete" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] $($_.Exception.Message)" -ForegroundColor Red
$audit._errors += @{ Section = "StoppedAutoServices"; Error = $_.Exception.Message }
}
# =====================================================
# 32. FAILED LOGON ATTEMPTS (last 7 days)
# =====================================================
Write-Host ""
Write-Host "=== 32. FAILED LOGON ATTEMPTS (last 7 days) ===" -ForegroundColor Cyan
Write-Host " Running: Get-WinEvent Security log - Event ID 4625"
$failedSince = (Get-Date).AddDays(-7)
$failedLogons = @(Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=$failedSince} -MaxEvents 50 -ErrorAction SilentlyContinue)
if ($failedLogons.Count -gt 0) {
$grouped = $failedLogons | ForEach-Object {
$xml = [xml]$_.ToXml()
$targetUser = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
$sourceIP = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'IpAddress' }).'#text'
[PSCustomObject]@{ Time = $_.TimeCreated; User = $targetUser; SourceIP = $sourceIP }
}
$summary = $grouped | Group-Object User | Sort-Object Count -Descending
Write-Host " $($failedLogons.Count) failed logon attempts:" -ForegroundColor Yellow
$summary | ForEach-Object { Write-Host " $($_.Name): $($_.Count) failures" -ForegroundColor Yellow }
$audit.FailedLogons = [ordered]@{
TotalCount = $failedLogons.Count
ByUser = @($summary | ForEach-Object { [ordered]@{ User = $_.Name; Count = $_.Count } })
}
} else {
Write-Host " No failed logon attempts" -ForegroundColor Green
$audit.FailedLogons = [ordered]@{ TotalCount = 0 }
}
Write-Host " [OK] Failed logons collected" -ForegroundColor Green
# =====================================================
# 33. SECURITY SUMMARY
# =====================================================
Write-Host ""
Write-Host "=== 33. SECURITY SUMMARY ===" -ForegroundColor Cyan
$audit.SecuritySummary = @()
# Check each finding
if ($audit.Activation -and $audit.Activation.StatusCode -ne 1) {
$audit.SecuritySummary += "OS is NOT fully licensed"
Write-Host " [WARN] OS is NOT fully licensed" -ForegroundColor Red
}
if ($audit.FirewallProfiles) {
$disabledFW = $audit.FirewallProfiles | Where-Object { -not $_.Enabled }
if ($disabledFW) {
$audit.SecuritySummary += "Windows Firewall disabled on: $($disabledFW.Profile -join ', ')"
Write-Host " [WARN] Windows Firewall disabled on: $($disabledFW.Profile -join ', ')" -ForegroundColor Red
}
}
if ($audit.BitLocker) {
$unencrypted = $audit.BitLocker | Where-Object { $_.Protection -eq 'Off' -or $_.VolumeStatus -eq 'FullyDecrypted' }
if ($unencrypted) {
$audit.SecuritySummary += "BitLocker not enabled on: $($unencrypted.MountPoint -join ', ')"
Write-Host " [WARN] BitLocker not enabled - HIPAA requires encryption at rest" -ForegroundColor Red
}
}
if (-not $audit.ScreenLockPolicy.InactivityTimeoutSecs -and -not $audit.ScreenLockPolicy.ScreenSaverGPO) {
$audit.SecuritySummary += "No screen lock / inactivity timeout configured"
Write-Host " [WARN] No screen lock configured - HIPAA requires automatic session lock" -ForegroundColor Yellow
}
if ($audit.USBStoragePolicy -and $audit.USBStoragePolicy.Status -eq 'Enabled' -and -not $audit.USBStoragePolicy.GPORestrictions) {
$audit.SecuritySummary += "USB storage unrestricted"
Write-Host " [WARN] USB storage unrestricted - data exfiltration risk" -ForegroundColor Yellow
}
if ($audit.UACSettings -and $audit.UACSettings.EnableLUA -ne 1) {
$audit.SecuritySummary += "UAC is disabled"
Write-Host " [WARN] UAC is disabled" -ForegroundColor Red
}
if ($audit.RDPSettings -and $audit.RDPSettings.RDPEnabled -and -not $audit.RDPSettings.NLARequired) {
$audit.SecuritySummary += "RDP enabled without NLA"
Write-Host " [WARN] RDP enabled without NLA" -ForegroundColor Yellow
}
if ($audit.Antivirus) {
$noAV = $audit.Antivirus | Where-Object { -not $_.Enabled }
if ($noAV) {
# Cross-reference with Get-MpComputerStatus - SecurityCenter2 is unreliable for Defender
$defenderActive = $audit.WindowsDefender -and $audit.WindowsDefender.AntivirusEnabled -and $audit.WindowsDefender.RealTimeProtection
$nonDefenderDown = $noAV | Where-Object { $_.Name -ne 'Windows Defender' }
if ($defenderActive -and -not $nonDefenderDown) {
# SecurityCenter2 false positive - Defender is actually running
} else {
$names = if ($nonDefenderDown) { $nonDefenderDown.Name -join ', ' } else { $noAV.Name -join ', ' }
$audit.SecuritySummary += "Antivirus not active: $names"
Write-Host " [WARN] Antivirus not active" -ForegroundColor Red
}
}
}
if ($audit.StoppedAutoServices.Count -gt 0) {
$audit.SecuritySummary += "$($audit.StoppedAutoServices.Count) auto-start services stopped"
Write-Host " [WARN] $($audit.StoppedAutoServices.Count) auto-start services stopped unexpectedly" -ForegroundColor Yellow
}
if ($audit.SecuritySummary.Count -eq 0) {
Write-Host " No critical security findings" -ForegroundColor Green
} else {
Write-Host " $($audit.SecuritySummary.Count) findings detected - review above" -ForegroundColor Yellow
}
# =====================================================
# DONE - SAVE JSON
# =====================================================
Write-Host ""
Write-Host "======================================="
Write-Host " WORKSTATION AUDIT COMPLETE"
Write-Host "======================================="
$errorCount = $audit._errors.Count
if ($errorCount -gt 0) {
Write-Host "Completed with $errorCount section errors (see _errors in JSON)" -ForegroundColor Yellow
} else {
Write-Host "All sections completed successfully" -ForegroundColor Green
}
Write-Host "JSON data: $JsonFile"
Write-Host "======================================="
# Save JSON
$audit | ConvertTo-Json -Depth 10 | Out-File $JsonFile -Encoding UTF8