Private/Get-ChangeSnapshot.ps1
|
function Get-ChangeSnapshot { <# .SYNOPSIS Takes a point-in-time snapshot of the current state for future baseline comparison. .DESCRIPTION Captures AD object summary (counts by type, key group memberships), GPO list with version numbers and modification times, DNS records from AD-integrated zones, and server service configurations. Saves the snapshot as a timestamped JSON file. .PARAMETER OutputPath Full file path for the snapshot JSON file. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$OutputPath ) $Snapshot = [ordered]@{ CaptureTime = (Get-Date).ToString('o') CapturedBy = "$env:USERDOMAIN\$env:USERNAME" ComputerName = $env:COMPUTERNAME ActiveDirectory = $null GroupPolicy = $null DNS = $null ServerServices = $null } # ================================================================ # Active Directory Snapshot # ================================================================ Write-Verbose 'Capturing AD snapshot...' try { $ADData = [ordered]@{ UserCount = 0 GroupCount = 0 ComputerCount = 0 OUCount = 0 SensitiveGroupMembers = @{} } try { $ADData.UserCount = @(Get-ADUser -Filter * -ErrorAction Stop).Count } catch { Write-Verbose "Could not count users: $_" } try { $ADData.GroupCount = @(Get-ADGroup -Filter * -ErrorAction Stop).Count } catch { Write-Verbose "Could not count groups: $_" } try { $ADData.ComputerCount = @(Get-ADComputer -Filter * -ErrorAction Stop).Count } catch { Write-Verbose "Could not count computers: $_" } try { $ADData.OUCount = @(Get-ADOrganizationalUnit -Filter * -ErrorAction Stop).Count } catch { Write-Verbose "Could not count OUs: $_" } # Capture membership of sensitive groups $SensitiveGroups = @('Domain Admins', 'Enterprise Admins', 'Schema Admins', 'Administrators') foreach ($GroupName in $SensitiveGroups) { try { $Members = Get-ADGroupMember -Identity $GroupName -ErrorAction Stop | Select-Object -ExpandProperty SamAccountName $ADData.SensitiveGroupMembers[$GroupName] = @($Members) } catch { Write-Verbose "Could not get members of '${GroupName}': $_" } } $Snapshot.ActiveDirectory = $ADData } catch { Write-Warning "AD snapshot failed: $_" } # ================================================================ # Group Policy Snapshot # ================================================================ Write-Verbose 'Capturing GPO snapshot...' try { $GPOs = Get-GPO -All -ErrorAction Stop | ForEach-Object { [ordered]@{ Id = $_.Id.ToString() Name = $_.DisplayName Status = $_.GpoStatus.ToString() CreationTime = $_.CreationTime.ToString('o') ModificationTime = $_.ModificationTime.ToString('o') UserVersion = $_.User.DSVersion ComputerVersion = $_.Computer.DSVersion Owner = $_.Owner } } $Snapshot.GroupPolicy = @($GPOs) } catch { Write-Warning "GPO snapshot failed: $_" } # ================================================================ # DNS Snapshot # ================================================================ Write-Verbose 'Capturing DNS snapshot...' try { $DnsZones = Get-DnsServerZone -ErrorAction Stop | Where-Object { $_.ZoneType -ne 'Forwarder' -and $_.IsAutoCreated -eq $false } $DnsData = [System.Collections.Generic.List[object]]::new() foreach ($Zone in $DnsZones) { try { $Records = Get-DnsServerResourceRecord -ZoneName $Zone.ZoneName -ErrorAction Stop | Where-Object { $_.RecordType -in @('A', 'AAAA', 'CNAME', 'MX', 'SRV', 'PTR', 'TXT') } | ForEach-Object { $RData = switch ($_.RecordType) { 'A' { $_.RecordData.IPv4Address.ToString() } 'AAAA' { $_.RecordData.IPv6Address.ToString() } 'CNAME' { $_.RecordData.HostNameAlias } 'MX' { "$($_.RecordData.Preference) $($_.RecordData.MailExchange)" } 'SRV' { "$($_.RecordData.Priority) $($_.RecordData.Weight) $($_.RecordData.Port) $($_.RecordData.DomainName)" } 'PTR' { $_.RecordData.PtrDomainName } 'TXT' { ($_.RecordData.DescriptiveText -join '; ') } default { $_.RecordData.ToString() } } [ordered]@{ ZoneName = $Zone.ZoneName HostName = $_.HostName RecordType = $_.RecordType Data = $RData TTL = $_.TimeToLive.TotalSeconds } } foreach ($Rec in $Records) { $DnsData.Add($Rec) } } catch { Write-Verbose "Could not snapshot records in zone '$($Zone.ZoneName)': $_" } } $Snapshot.DNS = @($DnsData) } catch { Write-Warning "DNS snapshot failed: $_" } # ================================================================ # Server Services Snapshot (local machine only for baseline) # ================================================================ Write-Verbose 'Capturing local server services snapshot...' try { $Services = Get-CimInstance -ClassName Win32_Service -ErrorAction Stop | Select-Object Name, DisplayName, StartMode, State, PathName, StartName | ForEach-Object { [ordered]@{ Name = $_.Name DisplayName = $_.DisplayName StartMode = $_.StartMode State = $_.State PathName = $_.PathName StartName = $_.StartName } } $Snapshot.ServerServices = @($Services) } catch { Write-Warning "Server services snapshot failed: $_" } # ================================================================ # Write snapshot to file # ================================================================ $SnapshotDir = Split-Path -Path $OutputPath -Parent if ($SnapshotDir -and -not (Test-Path -Path $SnapshotDir)) { New-Item -ItemType Directory -Path $SnapshotDir -Force | Out-Null } $JsonContent = $Snapshot | ConvertTo-Json -Depth 10 [System.IO.File]::WriteAllText( (Resolve-Path -Path $OutputPath -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path) ?? [System.IO.Path]::GetFullPath($OutputPath), $JsonContent, [System.Text.UTF8Encoding]::new($true) ) Write-Verbose "Snapshot saved to $OutputPath" return $OutputPath } |