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>
1159 lines
50 KiB
PowerShell
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
|