Private/Collectors/RemoteCollector.ps1
|
# This script block runs inside each target DC's existing PowerShell endpoint. # It reads local providers only. It never authenticates onward to a second host. $script:DCInvRemoteCollector = { param([string]$TargetName) $ErrorActionPreference = 'Stop' function Read-Value($Object, [string]$Property) { if ($null -ne $Object -and $null -ne $Object.PSObject.Properties[$Property]) { $Object.$Property } } function Join-Values($Values) { (@($Values | Where-Object { $null -ne $_ }) | ForEach-Object { [string]$_ }) -join '; ' } function Iso-Date($Value) { if ($null -ne $Value) { ([datetimeoffset]$Value).ToString('o') } } function Emit-Section([string]$Key, [string]$Method, [scriptblock]$Action) { $started = [datetimeoffset]::Now $clock = [diagnostics.stopwatch]::StartNew() $status = 'Succeeded'; $errorId = $null; $message = $null; $records = @() try { $records = @(& $Action) } catch { $status = 'Failed'; $errorId = $_.FullyQualifiedErrorId; $message = $_.Exception.Message } $clock.Stop() [pscustomobject]@{ Kind = 'Section'; Target = $TargetName; Key = $Key; Status = $status; Method = $Method StartedAt = $started.ToString('o'); CollectedAt = [datetimeoffset]::Now.ToString('o') DurationMs = $clock.ElapsedMilliseconds; Records = $records; ErrorId = $errorId; ErrorMessage = $message } } Emit-Section 'system' 'WinRM / local CIM (Win32_ComputerSystem, Win32_OperatingSystem)' { $computer = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop $os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop [pscustomobject][ordered]@{ Name = $TargetName; Manufacturer = $computer.Manufacturer; Model = $computer.Model CPU = $computer.NumberOfLogicalProcessors; 'Memory GB' = [math]::Round($computer.TotalPhysicalMemory / 1GB, 2) 'OS build' = $os.BuildNumber; 'Last boot' = Iso-Date $os.LastBootUpTime 'OS caption' = $os.Caption; 'OS version' = $os.Version 'Uptime days' = [math]::Round(([datetime]::Now - [datetime]$os.LastBootUpTime).TotalDays, 2) } } Emit-Section 'disks' 'WinRM / local CIM (fixed logical disks)' { Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3' -ErrorAction Stop | ForEach-Object { [pscustomobject][ordered]@{ Name = $TargetName; Drive = $_.DeviceID; Label = $_.VolumeName 'Capacity GB' = if ($null -ne $_.Size) { [math]::Round($_.Size / 1GB, 2) } else { $null } 'Free GB' = if ($null -ne $_.FreeSpace) { [math]::Round($_.FreeSpace / 1GB, 2) } else { $null } 'File system' = $_.FileSystem } } } Emit-Section 'paths' 'WinRM / local NTDS and Netlogon registry (read only)' { $ntds = Get-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters' -ErrorAction Stop $netlogon = Get-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -ErrorAction Stop [pscustomobject][ordered]@{ Name = $TargetName; Database = Read-Value $ntds 'DSA Database file' 'Log directory' = Read-Value $ntds 'Database log files path'; SYSVOL = Read-Value $netlogon 'SysVol' } } Emit-Section 'network' 'WinRM / local CIM (IP-enabled adapters)' { Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=TRUE' -ErrorAction Stop | ForEach-Object { $dns = @($_.DNSServerSearchOrder | Where-Object { $_ }) $v4 = @($_.IPAddress | Where-Object { $_ -notmatch ':' }) $v6 = @($_.IPAddress | Where-Object { $_ -match ':' }) [pscustomobject][ordered]@{ Name = $TargetName; Adapter = $_.Description; IPv4 = Join-Values $v4; IPv6 = Join-Values $v6 'Subnet masks / prefixes' = Join-Values $_.IPSubnet; Gateway = Join-Values $_.DefaultIPGateway 'Preferred DNS' = if ($dns.Count -gt 0) { $dns[0] } else { $null } 'Alternate DNS' = if ($dns.Count -gt 1) { $dns[1] } else { $null } 'Additional DNS' = if ($dns.Count -gt 2) { Join-Values $dns[2..($dns.Count-1)] } else { $null } 'DNS search order' = Join-Values $dns; 'Interface index' = $_.InterfaceIndex 'MAC address' = $_.MACAddress; DHCP = $_.DHCPEnabled } } } # Missing DNS service means this role is not installed, not a failed DNS query. $dnsService = @(); $dnsDetectionError = $null; $dnsAbsent = $false try { $dnsService = @(Get-Service -Name DNS -ErrorAction Stop) } catch { if ($_.FullyQualifiedErrorId -like 'NoServiceFoundForGivenName*') { $dnsAbsent = $true } else { $dnsDetectionError = $_ } } if ($null -ne $dnsDetectionError) { foreach ($key in @('forwarders', 'zones', 'conditional')) { [pscustomobject]@{ Kind='Section'; Target=$TargetName; Key=$key; Status='Failed'; Method='WinRM / local DNS role detection' StartedAt=[datetimeoffset]::Now.ToString('o'); CollectedAt=[datetimeoffset]::Now.ToString('o') DurationMs=0; Records=@(); ErrorId=$dnsDetectionError.FullyQualifiedErrorId; ErrorMessage=$dnsDetectionError.Exception.Message } } } elseif ($dnsAbsent) { foreach ($key in @('forwarders', 'zones', 'conditional')) { [pscustomobject]@{ Kind='Section'; Target=$TargetName; Key=$key; Status='NotApplicable'; Method='WinRM / local DNS role detection' StartedAt=[datetimeoffset]::Now.ToString('o'); CollectedAt=[datetimeoffset]::Now.ToString('o') DurationMs=0; Records=@(); ErrorId=$null; ErrorMessage='DNS Server service is not installed.' } } } else { Emit-Section 'forwarders' 'WinRM / local DnsServer module (Get-DnsServerForwarder)' { Import-Module DnsServer -ErrorAction Stop $configuration = Get-DnsServerForwarder -ErrorAction Stop $addresses = @($configuration.IPAddress | Where-Object { $null -ne $_ }) if ($addresses.Count -eq 0) { $addresses = @('(none configured)') } $order = 0 foreach ($address in $addresses) { $order++ [pscustomobject][ordered]@{ Name=$TargetName; Forwarder=[string]$address; Order=$order 'Timeout seconds'=Read-Value $configuration 'Timeout'; 'Use root hints'=Read-Value $configuration 'UseRootHint' 'Service state'=[string]$dnsService[0].Status } } } Emit-Section 'zones' 'WinRM / local DnsServer module (non-forwarder zones)' { Import-Module DnsServer -ErrorAction Stop Get-DnsServerZone -ErrorAction Stop | Where-Object { [string]$_.ZoneType -ne 'Forwarder' } | ForEach-Object { [pscustomobject][ordered]@{ Name=$TargetName; Zone=$_.ZoneName; Type=[string]$_.ZoneType 'AD integrated'=$_.IsDsIntegrated; 'Directory partition'=Read-Value $_ 'DirectoryPartitionName' 'Dynamic updates'=[string]$_.DynamicUpdate; 'Reverse lookup'=$_.IsReverseLookupZone 'Zone file'=Read-Value $_ 'ZoneFile' } } } Emit-Section 'conditional' 'WinRM / local Get-DnsServerZone (ZoneType Forwarder)' { Import-Module DnsServer -ErrorAction Stop Get-DnsServerZone -ErrorAction Stop | Where-Object { [string]$_.ZoneType -eq 'Forwarder' } | ForEach-Object { [pscustomobject][ordered]@{ Name=$TargetName; 'DNS namespace'=$_.ZoneName; 'Master servers'=Join-Values (Read-Value $_ 'MasterServers') 'AD integrated'=$_.IsDsIntegrated; 'Directory partition'=Read-Value $_ 'DirectoryPartitionName' 'Timeout seconds'=Read-Value $_ 'ForwarderTimeout'; 'Use recursion'=Read-Value $_ 'UseRecursion' } } } } Emit-Section 'time' 'WinRM / local W32Time registry and w32tm /query /source' { $parameters=Get-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters' -ErrorAction Stop $config=Get-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config' -ErrorAction Stop $source = & "$env:SystemRoot\System32\w32tm.exe" /query /source 2>&1 if ($LASTEXITCODE -ne 0) { throw "w32tm /query /source exited $LASTEXITCODE : $($source -join ' ')" } [pscustomobject][ordered]@{ Name=$TargetName; Source=($source -join ' ').Trim(); Type=Read-Value $parameters 'Type' 'NTP server setting'=Read-Value $parameters 'NtpServer'; 'Announce flags'=Read-Value $config 'AnnounceFlags' } } Emit-Section 'certificates' 'WinRM / certificate provider (LocalMachine\My, public metadata only)' { Get-ChildItem -LiteralPath Cert:\LocalMachine\My -ErrorAction Stop | ForEach-Object { $templates=@($_.Extensions | Where-Object { $_.Oid.Value -in @('1.3.6.1.4.1.311.20.2','1.3.6.1.4.1.311.21.7') } | ForEach-Object { $_.Format($false) }) $eku=@($_.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.37' } | ForEach-Object { $_.Format($false) }) [pscustomobject][ordered]@{ Name=$TargetName; Subject=$_.Subject; Issuer=$_.Issuer; Template=Join-Values $templates 'Not after'=Iso-Date $_.NotAfter; 'Not before'=Iso-Date $_.NotBefore Store='LocalMachine\My'; Thumbprint=$_.Thumbprint; 'Has private key'=$_.HasPrivateKey; EKU=Join-Values $eku } } } Emit-Section 'shares' 'WinRM / local CIM (Win32_Share)' { Get-CimInstance -ClassName Win32_Share -ErrorAction Stop | ForEach-Object { [pscustomobject][ordered]@{ Name=$TargetName; Share=$_.Name; Path=$_.Path; Description=$_.Description; Type=$_.Type } } } } |