public/Compare-PSProfileSnapshot.ps1
|
<# .SYNOPSIS Compares two snapshots or a snapshot with the current profile. .DESCRIPTION Shows the differences between two snapshots, or between a snapshot and the current profile state. Differences are displayed in a clear side-by-side format. .PARAMETER Name1 The name of the first snapshot (or current profile if this is the only parameter). Can be piped from Get-PSProfileSnapshot. .PARAMETER Name2 The name of the second snapshot to compare against the first. If not specified, the current profile is used as the comparison target. .PARAMETER GUID1 The GUID of the first snapshot (alternative to Name1). Can be piped from Get-PSProfileSnapshot. .PARAMETER GUID2 The GUID of the second snapshot (alternative to Name2). .INPUTS None .OUTPUTS System.Management.Automation.PSCustomObject Returns difference objects with properties: InputObject, SideIndicator .EXAMPLE Compare-PSProfileSnapshot -Name1 "before-change" -Name2 "after-change" Shows differences between two named snapshots. .EXAMPLE Compare-PSProfileSnapshot -Name1 "old-config" Shows differences between the "old-config" snapshot and the current profile. .EXAMPLE Get-PSProfileSnapshot | Where-Object Name -EQ "my-snapshot" | Compare-PSProfileSnapshot Shows differences by piping snapshot from Get-PSProfileSnapshot. .NOTES Use the SideIndicator property to identify which profile each line comes from: - "=>" : Line exists in the second/current profile - "<=" : Line exists only in the first snapshot - "==" : Line exists in both (for reference) #> function Compare-PSProfileSnapshot { [CmdletBinding(DefaultParameterSetName = 'CompareWithCurrent')] param( [Parameter(Mandatory = $true, ParameterSetName = 'ByName1', ValueFromPipelineByPropertyName = $true)] [Alias('SnapshotName', 'SnapshotName1')] [string]$Name1, [Parameter(Mandatory = $false, ParameterSetName = 'ByNameBoth')] [Alias('SnapshotName2')] [string]$Name2, [Parameter(Mandatory = $true, ParameterSetName = 'ByGUID1', ValueFromPipelineByPropertyName = $true)] [Alias('SnapshotGUID', 'SnapshotGUID1')] [guid]$GUID1, [Parameter(Mandatory = $false, ParameterSetName = 'ByGUIDBoth')] [Alias('SnapshotGUID2')] [guid]$GUID2 ) try { $profilesPath = Split-Path -Path $profile -Parent $backupFiles = Get-ChildItem -Path $profilesPath -Filter "*.backup" -ErrorAction Stop # Helper function to find a snapshot by name or GUID function Get-SnapshotContent { param([string]$Name, [guid]$GUID) foreach ($backupFile in $backupFiles) { $fileGUID = $backupFile.BaseName -replace '^.*_([a-f0-9\-]+)$', '$1' if ($GUID) { if ($fileGUID -eq $GUID.ToString()) { return Get-Content -Path $backupFile.FullName -Raw -ErrorAction Stop } } elseif ($Name) { # Check metadata first for custom name $metadataFileName = "$($profile)_$($fileGUID).metadata" if (Test-Path -Path $metadataFileName -ErrorAction SilentlyContinue) { $metadata = Get-Content -Path $metadataFileName -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue if ($metadata.Name -eq $Name) { return Get-Content -Path $backupFile.FullName -Raw -ErrorAction Stop } } # Also check if name matches the timestamp format of CreatedTime $timestampName = $backupFile.CreationTime.ToString("yyyy-MM-dd HH:mm:ss") if ($timestampName -eq $Name) { return Get-Content -Path $backupFile.FullName -Raw -ErrorAction Stop } } } return $null } # Determine which parameter set was used $referenceContent = $null $comparisonContent = $null $referenceLabel = "" $comparisonLabel = "" switch ($PSCmdlet.ParameterSetName) { 'ByName1' { $referenceContent = Get-SnapshotContent -Name $Name1 $referenceLabel = "Snapshot: $Name1" $comparisonContent = Get-Content -Path $profile -Raw -ErrorAction Stop $comparisonLabel = "Current Profile" } 'ByNameBoth' { $referenceContent = Get-SnapshotContent -Name $Name1 $referenceLabel = "Snapshot: $Name1" $comparisonContent = Get-SnapshotContent -Name $Name2 $comparisonLabel = "Snapshot: $Name2" } 'ByGUID1' { $referenceContent = Get-SnapshotContent -GUID $GUID1 $referenceLabel = "Snapshot: $GUID1" $comparisonContent = Get-Content -Path $profile -Raw -ErrorAction Stop $comparisonLabel = "Current Profile" } 'ByGUIDBoth' { $referenceContent = Get-SnapshotContent -GUID $GUID1 $referenceLabel = "Snapshot: $GUID1" $comparisonContent = Get-SnapshotContent -GUID $GUID2 $comparisonLabel = "Snapshot: $GUID2" } } # Validate that we found the snapshots if (-not $referenceContent) { Write-Error "❌ Could not find first snapshot." return } if (-not $comparisonContent) { Write-Error "❌ Could not find second snapshot or current profile." return } # Split content into lines for comparison $referenceSplit = $referenceContent -split "`n" $comparisonSplit = $comparisonContent -split "`n" # Perform the comparison Write-Host "`n----- Comparison: $referenceLabel vs $comparisonLabel -----`n" -ForegroundColor Cyan $differences = Compare-Object -ReferenceObject $referenceSplit -DifferenceObject $comparisonSplit -IncludeEqual | Where-Object { $_.SideIndicator -ne '==' } if ($differences.Count -eq 0) { Write-Host "✅ No differences found between the profiles." -ForegroundColor Green } else { Write-Host "Found $($differences.Count) difference(s):`n" -ForegroundColor Yellow foreach ($diff in $differences) { $sideIndicator = $diff.SideIndicator if ($sideIndicator -eq '<=') { Write-Host "← Only in $referenceLabel" -ForegroundColor Red } else { Write-Host "→ Only in $comparisonLabel" -ForegroundColor Green } Write-Host " $($diff.InputObject)" -ForegroundColor Gray Write-Host "" } } return $differences } catch { Write-Error "❌ An error occurred while comparing snapshots." throw $_.Exception.Message } } |