Private/State/Get-StateFromRegistry.ps1
|
function Get-StateFromRegistry { <# .SYNOPSIS Gets deferral state from registry or file fallback .DESCRIPTION Retrieves persisted deferral state for an application from the registry, falling back to file-based state if registry not accessible .PARAMETER AppId The application ID to get state for #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$AppId ) $config = Get-ModuleConfiguration $appKey = Join-Path $config.StateRegistryKey $AppId $state = [DeferralState]::new() $state.AppId = $AppId # Try registry first try { if (Test-Path $appKey) { $regValues = Get-ItemProperty -Path $appKey -ErrorAction Stop if ($regValues.DeferralCount) { $state.DeferralCount = [int]$regValues.DeferralCount } if ($regValues.FirstNotification) { $state.FirstNotification = ConvertTo-UtcDateTimeInternal -Value $regValues.FirstNotification } if ($regValues.LastDeferral) { $state.LastDeferral = ConvertTo-UtcDateTimeInternal -Value $regValues.LastDeferral } if ($regValues.TargetVersion) { $state.TargetVersion = $regValues.TargetVersion } # Ensure TargetVersion is never empty if ([string]::IsNullOrWhiteSpace($state.TargetVersion)) { $state.TargetVersion = 'Latest' } if ($regValues.DeadlineDate) { $state.DeadlineDate = ConvertTo-UtcDateTimeInternal -Value $regValues.DeadlineDate } if ($regValues.Phase) { $state.Phase = [DeferralPhase]$regValues.Phase } if ($regValues.MaxDeferrals) { $state.MaxDeferrals = [int]$regValues.MaxDeferrals } return $state } } catch { # Registry not accessible - try file fallback } # Try file-based state as fallback $state = Get-StateFromFile -AppId $AppId return $state } function ConvertTo-UtcDateTimeInternal { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Value ) try { return [DateTime]::Parse( $Value, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::RoundtripKind ).ToUniversalTime() } catch { # Last resort: let PowerShell parse it return ([datetime]$Value).ToUniversalTime() } } function Get-StateFromFile { <# .SYNOPSIS Gets deferral state from file (fallback) #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$AppId ) $state = [DeferralState]::new() $state.AppId = $AppId # Check multiple locations $stateLocations = @( (Join-Path (Get-ModuleConfiguration).StatePath "$($AppId -replace '[^a-zA-Z0-9]', '_').json") (Join-Path "$env:TEMP\PsPatchMyPC\State" "$($AppId -replace '[^a-zA-Z0-9]', '_').json") ) foreach ($stateFile in $stateLocations) { if (Test-Path $stateFile) { try { $stateData = Get-Content -Path $stateFile -Raw | ConvertFrom-Json $state.DeferralCount = $stateData.DeferralCount $state.TargetVersion = $stateData.TargetVersion $state.MaxDeferrals = $stateData.MaxDeferrals # Ensure TargetVersion is never empty if ([string]::IsNullOrWhiteSpace($state.TargetVersion)) { $state.TargetVersion = 'Latest' } if ($stateData.FirstNotification) { $state.FirstNotification = ConvertTo-UtcDateTimeInternal -Value $stateData.FirstNotification } if ($stateData.LastDeferral) { $state.LastDeferral = ConvertTo-UtcDateTimeInternal -Value $stateData.LastDeferral } if ($stateData.DeadlineDate) { $state.DeadlineDate = ConvertTo-UtcDateTimeInternal -Value $stateData.DeadlineDate } if ($stateData.Phase) { $state.Phase = [DeferralPhase]$stateData.Phase } return $state } catch { # Continue to next location } } } return $state } function Get-AllDeferralStates { <# .SYNOPSIS Gets all persisted deferral states .DESCRIPTION Retrieves deferral state for all applications with stored state #> [CmdletBinding()] param() $config = Get-ModuleConfiguration $states = @() if (-not (Test-Path $config.StateRegistryKey)) { return $states } try { $appKeys = Get-ChildItem -Path $config.StateRegistryKey -ErrorAction SilentlyContinue foreach ($appKey in $appKeys) { $appId = $appKey.PSChildName $state = Get-StateFromRegistry -AppId $appId $states += $state } } catch { Write-PatchLog "Failed to enumerate deferral states: $_" -Type Warning } return $states } |