Modules/CyberConfigApp/CyberConfigAppHelpers/CyberConfigAppAutoSaveHelper.psm1
|
<#
.SYNOPSIS AutoSave helper functions for CyberAssessment Configuration Editor. .DESCRIPTION This module provides functionality to automatically save and restore user progress in policy configurations. It saves individual policy data to JSON files and can restore them when the UI is relaunched. .NOTES SAVE FLOW: User clicks "Save Exclusions" on MS.AAD.1.1v1 ↓ Data saved to ExclusionData hashtable (existing functionality) ↓ Save-AutoSavePolicy called with: - CardName: "Exclusions" - PolicyId: "MS.AAD.1.1v1" - ProductName: "AAD" - FlipFieldValueAndPolicyId: $true ↓ File saved to: LOCALAPPDATA\CyberConfig\AutoSave\Exclusions_MS.AAD.1.1v1.json RESTORE FLOW: UI launches ↓ Check if AutoSaveProgress enabled in JSON config ↓ Scan LOCALAPPDATA\CyberConfig\AutoSave\ for *.json files ↓ For each file: - Parse filename to get CardName and PolicyId - Find baselineControl by CardName - Get target hashtable from dataControlOutput - Restore data to correct hashtable (e.g., ExclusionData) #policy files LOCALAPPDATA\CyberConfig\AutoSave\ ├── Exclusions_MS.AAD.1.1v1.json ├── Exclusions_MS.EXO.2.1v1.json ├── Annotate_MS.AAD.3.1v1.json └── Omit_MS.TEAMS.1.1v1.json #settings files LOCALAPPDATA\CyberConfig\AutoSave\ ├── GeneralSettingsData.json ├── AdvancedSettingsData.json └── GlobalSettingsData.json #> Function Get-AutoSaveDirectory { <# .SYNOPSIS Gets the AutoSave directory path, creating it if it doesn't exist. .DESCRIPTION Returns the standardized AutoSave directory path and ensures the directory exists. Uses LOCALAPPDATA for consistent cross-user behavior. #> [CmdletBinding()] param() try { $autoSaveDir = Join-Path $env:LOCALAPPDATA "CyberConfig\AutoSave" if (-not (Test-Path $autoSaveDir)) { New-Item -Path $autoSaveDir -ItemType Directory -Force | Out-Null Write-DebugOutput -Message "Created AutoSave directory: $autoSaveDir" -Source $MyInvocation.MyCommand -Level "Info" } return $autoSaveDir } catch { Write-DebugOutput -Message "Error creating AutoSave directory: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" throw } } Function Test-AutoSaveEnabled { <# .SYNOPSIS Tests if AutoSave functionality is enabled in the configuration. .DESCRIPTION Checks the UIConfigs JSON configuration to determine if AutoSaveProgress is enabled. #> [CmdletBinding()] param() try { $autoSaveConfig = $syncHash.UIConfigs.AutoSaveProgress $isEnabled = $autoSaveConfig -eq $true Write-DebugOutput -Message "AutoSave enabled: $isEnabled" -Source $MyInvocation.MyCommand -Level "Verbose" return $isEnabled } catch { Write-DebugOutput -Message "Error checking AutoSave configuration: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Warning" return $false } } Function Save-AutoSavePolicy { <# .SYNOPSIS Saves policy configuration data to an AutoSave file. .DESCRIPTION Saves the current policy configuration data to a JSON file in the AutoSave directory. Uses configuration-driven approach to determine source data structure. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$CardName, [Parameter(Mandatory = $true)] [string]$PolicyId, [Parameter(Mandatory = $true)] [string]$ProductName, [Parameter(Mandatory = $false)] [bool]$FlipFieldValueAndPolicyId = $false ) try { # Check if AutoSave is enabled if (-not (Test-AutoSaveEnabled)) { Write-DebugOutput -Message "AutoSave is disabled, skipping save for $CardName/$PolicyId" -Source $MyInvocation.MyCommand -Level "Verbose" return } # Find the baseline control configuration for this card $baselineControl = $syncHash.UIConfigs.baselineControls | Where-Object { $_.cardName -eq $CardName } if (-not $baselineControl) { Write-DebugOutput -Message "No baseline control found for CardName: $CardName" -Source $MyInvocation.MyCommand -Level "Warning" return } # Get the source data hashtable name $sourceDataName = $baselineControl.dataControlOutput if (-not $sourceDataName) { Write-DebugOutput -Message "No dataControlOutput configured for CardName: $CardName" -Source $MyInvocation.MyCommand -Level "Warning" return } # Get the source data hashtable $sourceData = $syncHash.$sourceDataName if (-not $sourceData) { Write-DebugOutput -Message "Source data hashtable '$sourceDataName' not found or empty" -Source $MyInvocation.MyCommand -Level "Warning" return } # Extract the relevant data based on data structure type $policyData = $null if ($FlipFieldValueAndPolicyId) { # Flipped structure: Product -> FieldType -> PolicyId -> FieldData (Annotations/Omissions) if ($sourceData[$ProductName] -and $sourceData[$ProductName][$baselineControl.yamlValue] -and $sourceData[$ProductName][$baselineControl.yamlValue][$PolicyId]) { $policyData = @{ $ProductName = @{ $baselineControl.yamlValue = @{ $PolicyId = $sourceData[$ProductName][$baselineControl.yamlValue][$PolicyId] } } } } } else { # Normal structure: Product -> PolicyId -> ExclusionType -> FieldData (Exclusions) if ($sourceData[$ProductName] -and $sourceData[$ProductName][$PolicyId]) { $policyData = @{ $ProductName = @{ $PolicyId = $sourceData[$ProductName][$PolicyId] } } } } if (-not $policyData) { Write-DebugOutput -Message "No data found to save for $CardName/$ProductName/$PolicyId" -Source $MyInvocation.MyCommand -Level "Verbose" return } # Create filename and save $autoSaveDir = Get-AutoSaveDirectory $fileName = "${CardName}_${PolicyId}.json" $filePath = Join-Path $autoSaveDir $fileName $policyData | ConvertTo-Json -Depth 10 | Out-File -FilePath $filePath -Encoding UTF8 -Force Write-DebugOutput -Message "Saved AutoSave policy data: $filePath" -Source $MyInvocation.MyCommand -Level "Info" } catch { Write-DebugOutput -Message "Error saving AutoSave policy data: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" } } Function Remove-AutoSavePolicy { <# .SYNOPSIS Removes an AutoSave policy file when a policy is deleted. .DESCRIPTION Deletes the corresponding AutoSave JSON file when a policy configuration is removed. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$CardName, [Parameter(Mandatory = $true)] [string]$PolicyId ) try { # Check if AutoSave is enabled if (-not (Test-AutoSaveEnabled)) { Write-DebugOutput -Message "AutoSave is disabled, skipping remove for $CardName/$PolicyId" -Source $MyInvocation.MyCommand -Level "Verbose" return } $autoSaveDir = Get-AutoSaveDirectory $fileName = "${CardName}_${PolicyId}.json" $filePath = Join-Path $autoSaveDir $fileName if (Test-Path $filePath) { Remove-Item -Path $filePath -Force Write-DebugOutput -Message "Removed AutoSave policy file: $filePath" -Source $MyInvocation.MyCommand -Level "Info" } else { Write-DebugOutput -Message "AutoSave policy file not found (already removed): $filePath" -Source $MyInvocation.MyCommand -Level "Verbose" } } catch { Write-DebugOutput -Message "Error removing AutoSave policy file: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" } } Function Get-AutoSavePolicies { <# .SYNOPSIS Gets all available AutoSave policy files. .DESCRIPTION Scans the AutoSave directory and returns information about all saved policy files. Returns objects with CardName, PolicyId, and FilePath properties. #> [CmdletBinding()] param() try { $autoSaveDir = Get-AutoSaveDirectory $autoSavePolicies = @() if (Test-Path $autoSaveDir) { $jsonFiles = Get-ChildItem -Path $autoSaveDir -Filter "*.json" -File foreach ($file in $jsonFiles) { # Parse filename: CardName_PolicyId.json if ($file.BaseName -match '^(.+)_(.+)$') { $cardName = $matches[1] $policyId = $matches[2] $autoSavePolicies += [PSCustomObject]@{ CardName = $cardName PolicyId = $policyId FilePath = $file.FullName LastModified = $file.LastWriteTime } } else { Write-DebugOutput -Message "Skipping AutoSave file with invalid naming pattern: $($file.Name)" -Source $MyInvocation.MyCommand -Level "Warning" } } } Write-DebugOutput -Message "Found $($autoSavePolicies.Count) AutoSave policy files" -Source $MyInvocation.MyCommand -Level "Info" return $autoSavePolicies } catch { Write-DebugOutput -Message "Error getting AutoSave policies: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" return @() } } Function Restore-AutoSavePolicies { <# .SYNOPSIS Restores all AutoSave policy data when the UI launches. .DESCRIPTION Scans the AutoSave directory and restores all saved policy configurations to their appropriate data hashtables. Uses configuration-driven approach. #> [CmdletBinding()] param() try { # Check if AutoSave is enabled if (-not (Test-AutoSaveEnabled)) { Write-DebugOutput -Message "AutoSave is disabled, skipping restore" -Source $MyInvocation.MyCommand -Level "Info" return } $autoSavePolicies = Get-AutoSavePolicies if ($autoSavePolicies.Count -eq 0) { Write-DebugOutput -Message "No AutoSave policy files to restore" -Source $MyInvocation.MyCommand -Level "Info" return } $restoredCount = 0 foreach ($policy in $autoSavePolicies) { try { # Find the baseline control configuration for this card $baselineControl = $syncHash.UIConfigs.baselineControls | Where-Object { $_.controlType -eq $policy.CardName } if (-not $baselineControl) { Write-DebugOutput -Message "No baseline control found for CardName: $($policy.CardName), skipping restore of $($policy.PolicyId)" -Source $MyInvocation.MyCommand -Level "Warning" continue } # Get the target data hashtable name $targetDataName = $baselineControl.dataControlOutput if (-not $targetDataName) { Write-DebugOutput -Message "No dataControlOutput configured for CardName: $($policy.CardName), skipping restore of $($policy.PolicyId)" -Source $MyInvocation.MyCommand -Level "Warning" continue } # Ensure the target hashtable exists if (-not $syncHash.$targetDataName) { $syncHash.$targetDataName = [ordered]@{} } # Load and parse the JSON data $jsonContent = Get-Content -Path $policy.FilePath -Raw | ConvertFrom-Json # Merge the data into the target hashtable foreach ($productName in $jsonContent.PSObject.Properties.Name) { $productData = $jsonContent.$productName # Initialize product level if needed if (-not $syncHash.$targetDataName[$productName]) { $syncHash.$targetDataName[$productName] = [ordered]@{} } # Merge the product data foreach ($key in $productData.PSObject.Properties.Name) { $keyData = $productData.$key # Handle both flipped and normal data structures if ($baselineControl.supportsAllProducts) { # Flipped structure: merge at yamlValue level if (-not $syncHash.$targetDataName[$productName][$key]) { $syncHash.$targetDataName[$productName][$key] = [ordered]@{} } foreach ($policyIdKey in $keyData.PSObject.Properties.Name) { $syncHash.$targetDataName[$productName][$key][$policyIdKey] = $keyData.$policyIdKey } } else { # Normal structure: merge at policy level $syncHash.$targetDataName[$productName][$key] = $keyData } } } $restoredCount++ Write-DebugOutput -Message "Restored AutoSave policy: $($policy.CardName)/$($policy.PolicyId) to $targetDataName" -Source $MyInvocation.MyCommand -Level "Info" } catch { Write-DebugOutput -Message "Error restoring AutoSave policy $($policy.CardName)/$($policy.PolicyId): $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" } } Write-DebugOutput -Message "AutoSave restore completed: $restoredCount of $($autoSavePolicies.Count) policies restored" -Source $MyInvocation.MyCommand -Level "Info" } catch { Write-DebugOutput -Message "Error during AutoSave restore: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" } } Function Save-AutoSaveSettings { <# .SYNOPSIS Saves settings data (General, Advanced, Global) to AutoSave files. .DESCRIPTION Saves the current settings data to JSON files for the specified settings type. Uses configuration-driven approach based on dataValidationKeys. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$SettingsType ) try { # Check if AutoSave is enabled if (-not (Test-AutoSaveEnabled)) { Write-DebugOutput -Message "AutoSave is disabled, skipping save for $SettingsType" -Source $MyInvocation.MyCommand -Level "Verbose" return } # Validate settings type exists in configuration using new settingsControl structure $settingsControl = $syncHash.UIConfigs.settingsControl $matchingTabConfig = $null # Find the tab configuration that has this settings type as its dataControlOutput foreach ($tabName in $settingsControl.PSObject.Properties.Name) { $tabConfig = $settingsControl.$tabName if ($tabConfig.dataControlOutput -eq $SettingsType) { $matchingTabConfig = $tabConfig break } } if (-not $matchingTabConfig) { Write-DebugOutput -Message "Settings type '$SettingsType' not found in settingsControl configuration" -Source $MyInvocation.MyCommand -Level "Warning" return } # Before saving, collect current settings data from UI into the data structure switch ($SettingsType) { "AdvancedSettingsData" { if (Get-Command "Set-SettingsDataForAdvancedSection" -ErrorAction SilentlyContinue) { Set-SettingsDataForAdvancedSection Write-DebugOutput -Message "Collected advanced settings data before AutoSave" -Source $MyInvocation.MyCommand -Level "Verbose" } } "GlobalSettingsData" { if (Get-Command "Set-SettingsDataForGlobalSection" -ErrorAction SilentlyContinue) { Set-SettingsDataForGlobalSection Write-DebugOutput -Message "Collected global settings data before AutoSave" -Source $MyInvocation.MyCommand -Level "Verbose" } } "GeneralSettingsData" { if (Get-Command "Set-SettingsDataForGeneralSection" -ErrorAction SilentlyContinue) { Set-SettingsDataForGeneralSection Write-DebugOutput -Message "Collected general settings data before AutoSave" -Source $MyInvocation.MyCommand -Level "Verbose" } } } # Get the source data hashtable (after collection) $sourceData = $syncHash.$SettingsType if (-not $sourceData -or $sourceData.Count -eq 0) { Write-DebugOutput -Message "No data found in $SettingsType to save" -Source $MyInvocation.MyCommand -Level "Verbose" return } # Create filename and save $autoSaveDir = Get-AutoSaveDirectory $fileName = "${SettingsType}.json" $filePath = Join-Path $autoSaveDir $fileName $sourceData | ConvertTo-Json -Depth 10 | Out-File -FilePath $filePath -Encoding UTF8 -Force Write-DebugOutput -Message "Saved AutoSave settings data: $filePath" -Source $MyInvocation.MyCommand -Level "Info" } catch { Write-DebugOutput -Message "Error saving AutoSave settings data: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" } } Function Show-AutoSaveRestorePrompt { <# .SYNOPSIS Shows a dialog asking the user what to do with their previous session. .DESCRIPTION Prompts the user to choose between restoring last session, removing last session, or restoring later. Returns the user's choice. #> [CmdletBinding()] param() try { # Check if there are any AutoSave files to restore $autoSaveDir = Get-AutoSaveDirectory if (-not (Test-Path $autoSaveDir)) { Write-DebugOutput -Message "No AutoSave directory found, skipping restore prompt" -Source $MyInvocation.MyCommand -Level "Verbose" return "none" } $autoSaveFiles = Get-ChildItem -Path $autoSaveDir -Filter "*.json" -ErrorAction SilentlyContinue if (-not $autoSaveFiles -or $autoSaveFiles.Count -eq 0) { Write-DebugOutput -Message "No AutoSave files found, skipping restore prompt" -Source $MyInvocation.MyCommand -Level "Verbose" return "none" } # Count how many policies and settings we would restore $policyCount = ($autoSaveFiles | Where-Object { $_.Name -like "*_*.json" }).Count $settingsCount = ($autoSaveFiles | Where-Object { $_.Name -like "*SettingsData.json" }).Count $totalItems = $policyCount + $settingsCount if ($totalItems -eq 0) { Write-DebugOutput -Message "No valid AutoSave data found, skipping restore prompt" -Source $MyInvocation.MyCommand -Level "Verbose" return "none" } # Get the newest file timestamp for display $newestFile = $autoSaveFiles | Sort-Object LastWriteTime -Descending | Select-Object -First 1 $lastSavedTime = $newestFile.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss") # Create the prompt message $message = @" A previous session was detected with $totalItems saved items ($policyCount policies, $settingsCount settings configurations). Last saved: $lastSavedTime What would you like to do? - Yes = Restore previous session now - No = Delete previous session data - Cancel = Keep data, restore later (show Restore button) "@ # Show the dialog with three options $result = [System.Windows.MessageBox]::Show( $message, "Restore Previous Session?", [System.Windows.MessageBoxButton]::YesNoCancel, [System.Windows.MessageBoxImage]::Question, [System.Windows.MessageBoxResult]::Yes ) switch ($result) { "Yes" { Write-DebugOutput -Message "User chose to restore previous session" -Source $MyInvocation.MyCommand -Level "Info" return "restore" } "No" { Write-DebugOutput -Message "User chose to remove previous session" -Source $MyInvocation.MyCommand -Level "Info" return "remove" } "Cancel" { Write-DebugOutput -Message "User chose to restore later" -Source $MyInvocation.MyCommand -Level "Info" return "later" } default { Write-DebugOutput -Message "Unknown dialog result: $result, defaulting to later" -Source $MyInvocation.MyCommand -Level "Warning" return "later" } } } catch { Write-DebugOutput -Message "Error showing AutoSave restore prompt: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" return "none" } } Function Restore-AutoSaveWithProgress { <# .SYNOPSIS Restores AutoSave data with a progress dialog. .DESCRIPTION Restores both policies and settings from AutoSave files while showing progress to the user. Uses a runspace-based progress window similar to the YAML import functionality. #> [CmdletBinding()] param() $progress = $null try { Write-DebugOutput -Message "Starting AutoSave restoration with progress" -Source $MyInvocation.MyCommand -Level "Info" # Get count of items to restore for progress calculation $autoSaveDir = Get-AutoSaveDirectory $autoSaveFiles = Get-ChildItem -Path $autoSaveDir -Filter "*.json" -ErrorAction SilentlyContinue $policyFiles = $autoSaveFiles | Where-Object { $_.Name -like "*_*.json" } $settingsFiles = $autoSaveFiles | Where-Object { $_.Name -like "*SettingsData.json" } $totalItems = $policyFiles.Count + $settingsFiles.Count if ($totalItems -eq 0) { Write-DebugOutput -Message "No AutoSave data to restore" -Source $MyInvocation.MyCommand -Level "Info" return } # Show progress window $progress = Show-AutoSaveRestoreProgress -Title "Restoring Previous Session" if (-not $progress) { throw "Failed to create progress window" } # Small delay to ensure window is visible Start-Sleep -Milliseconds 300 # Step 1: Restore policies $progress.UpdateMessage.Invoke("Restoring policies...", "Processing $($policyFiles.Count) policy configurations") Start-Sleep -Milliseconds 200 Restore-AutoSavePolicies # Step 2: Restore settings $progress.UpdateMessage.Invoke("Restoring settings...", "Processing $($settingsFiles.Count) settings configurations") Start-Sleep -Milliseconds 200 Restore-AutoSaveSettings # Step 3: Update UI $progress.UpdateMessage.Invoke("Updating user interface...", "Refreshing UI controls") Start-Sleep -Milliseconds 300 if (Test-AutoSaveEnabled) { Update-UIFromSettingsData } # Step 4: Final processing $progress.UpdateMessage.Invoke($syncHash.UIConfigs.localeProgressMessages.SessionRestoreFinalize, $syncHash.UIConfigs.localeProgressMessages.SessionRestoreStatus) Start-Sleep -Milliseconds 300 Write-DebugOutput -Message "AutoSave restoration with progress completed successfully" -Source $MyInvocation.MyCommand -Level "Info" # Show completion message $syncHash.ShowMessageBox.Invoke( $syncHash.UIConfigs.localeProgressMessages.SessionRestoreSuccess -f $policyFiles.Count, $settingsFiles.Count, $syncHash.UIConfigs.localeTitles.SessionRestored, "OK", "Information" ) } catch { Write-DebugOutput -Message "Error during AutoSave restoration with progress: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" if ($progress -and $progress.UpdateMessage) { $progress.UpdateMessage.Invoke("Restore failed!", "Error: $($_.Exception.Message)") Start-Sleep -Milliseconds 1500 } $syncHash.ShowMessageBox.Invoke( "An error occurred while restoring the previous session: $($_.Exception.Message)", "Restore Error", "OK", "Error" ) } finally { # Always close progress window if ($progress -and $progress.Close) { $progress.Close.Invoke() } } } Function Show-AutoSaveRestoreProgress { <# .SYNOPSIS Shows a progress window for AutoSave restoration. .DESCRIPTION Creates a runspace-based progress window similar to the YAML import progress. #> param( [Parameter(Mandatory = $true)] [string]$Title ) # Create runspace for progress window $progressRunspace = [runspacefactory]::CreateRunspace() $progressRunspace.ApartmentState = "STA" $progressRunspace.ThreadOptions = "ReuseThread" $progressRunspace.Open() # Define XAML for progress window $xaml = @' <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Restoring Previous Session" Width="400" Height="200" WindowStartupLocation="CenterScreen" WindowStyle="ToolWindow" ResizeMode="NoResize" Topmost="True"> <Window.Resources> <Style TargetType="Label"> <Setter Property="FontFamily" Value="Segoe UI"/> <Setter Property="FontSize" Value="12"/> </Style> <Style TargetType="ProgressBar"> <Setter Property="Height" Value="20"/> <Setter Property="Margin" Value="0,5"/> </Style> </Window.Resources> <Grid Margin="20"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Label x:Name="lblMessage" Grid.Row="0" Content="Preparing to restore session..." FontWeight="Bold"/> <Label x:Name="lblStatus" Grid.Row="1" Content="Initializing..." Foreground="Gray" FontSize="10"/> <ProgressBar x:Name="RestoreProgressBar" Grid.Row="2" IsIndeterminate="True"/> <Label Grid.Row="3" Content="Please wait while your previous session is restored..." FontStyle="Italic" FontSize="10" HorizontalAlignment="Center" Margin="0,10,0,0"/> </Grid> </Window> '@ # Share variables with runspace $progressRunspace.SessionStateProxy.SetVariable("xaml", $xaml) $progressRunspace.SessionStateProxy.SetVariable("syncHash", $syncHash) $progressRunspace.SessionStateProxy.SetVariable("Title", $Title) # Create PowerShell instance for progress window $progressPowerShell = [powershell]::Create() $progressPowerShell.Runspace = $progressRunspace # Script for the progress window $progressScript = { Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName PresentationCore Add-Type -AssemblyName WindowsBase try { # Parse XAML $reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml)) $progressWindow = [Windows.Markup.XamlReader]::Load($reader) $reader.Close() # Set title $progressWindow.Title = $Title # Get controls $lblMessage = $progressWindow.FindName("lblMessage") $lblStatus = $progressWindow.FindName("lblStatus") $progressBar = $progressWindow.FindName("RestoreProgressBar") # Create shared hashtable for communication $progressSync = [hashtable]::Synchronized(@{ Window = $progressWindow Message = $lblMessage Status = $lblStatus ProgressBar = $progressBar ShouldClose = $false Error = $null }) # Store in main syncHash for communication $syncHash.ProgressSync = $progressSync # Update message function $updateMessage = { param($message, $status) $msg = $message $stat = $status $progressSync.Window.Dispatcher.Invoke([Action]{ if ($msg) { $progressSync.Message.Content = $msg } if ($stat) { $progressSync.Status.Content = $stat } }) } # Store update function $syncHash.UpdateProgressMessage = $updateMessage # Show window and wait $progressWindow.ShowDialog() } catch { # Store error for main thread if ($syncHash.ProgressSync) { $syncHash.ProgressSync.Error = $_.Exception.Message } } } # Start the progress window $progressPowerShell.AddScript($progressScript) $progressHandle = $progressPowerShell.BeginInvoke() # Wait for progress window to initialize $timeout = 0 while (-not $syncHash.ProgressSync -and $timeout -lt 50) { Start-Sleep -Milliseconds 100 $timeout++ } if (-not $syncHash.ProgressSync) { Write-Error -Message "Failed to initialize progress window" return $null } # Return control objects return @{ PowerShell = $progressPowerShell Handle = $progressHandle Runspace = $progressRunspace UpdateMessage = $syncHash.UpdateProgressMessage Close = { try { if ($syncHash.ProgressSync -and $syncHash.ProgressSync.Window) { $syncHash.ProgressSync.Window.Dispatcher.Invoke([Action]{ $syncHash.ProgressSync.Window.Close() }) } } catch { # Ignore close errors Write-DebugOutput -Message "Error closing progress window: $($_.Exception.Message)" -Source "Show-AutoSaveRestoreProgress" -Level "Warning" } # Cleanup try { if ($progressHandle -and $progressPowerShell) { $progressPowerShell.EndInvoke($progressHandle) } if ($progressPowerShell) { $progressPowerShell.Dispose() } if ($progressRunspace) { $progressRunspace.Close() $progressRunspace.Dispose() } } catch { Write-DebugOutput -Message "Error during cleanup: $($_.Exception.Message)" -Source "Show-AutoSaveRestoreProgress" -Level "Error" } # Remove from syncHash if ($syncHash.ProgressSync) { $syncHash.Remove("ProgressSync") } if ($syncHash.UpdateProgressMessage) { $syncHash.Remove("UpdateProgressMessage") } } } } Function Remove-AutoSaveData { <# .SYNOPSIS Removes all AutoSave data files. .DESCRIPTION Deletes all AutoSave files and shows confirmation to the user. #> [CmdletBinding()] param() try { $autoSaveDir = Get-AutoSaveDirectory if (Test-Path $autoSaveDir) { $autoSaveFiles = Get-ChildItem -Path $autoSaveDir -Filter "*.json" -ErrorAction SilentlyContinue $fileCount = $autoSaveFiles.Count if ($fileCount -gt 0) { Remove-Item -Path "$autoSaveDir\*.json" -Force -ErrorAction SilentlyContinue Write-DebugOutput -Message "Removed $fileCount AutoSave files" -Source $MyInvocation.MyCommand -Level "Info" $syncHash.ShowMessageBox.Invoke( "Previous session data has been removed ($fileCount files deleted).", "Session Data Removed", "OK", "Information" ) } else { Write-DebugOutput -Message "No AutoSave files found to remove" -Source $MyInvocation.MyCommand -Level "Info" } } } catch { Write-DebugOutput -Message "Error removing AutoSave data: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" $syncHash.ShowMessageBox.Invoke( "An error occurred while removing session data: $($_.Exception.Message)", "Remove Error", "OK", "Error" ) } } Function Restore-AutoSaveSettings { <# .SYNOPSIS Restores all settings data from AutoSave files. .DESCRIPTION Restores GeneralSettings, AdvancedSettings, and GlobalSettings from AutoSave files. Uses configuration-driven approach based on dataValidationKeys. #> [CmdletBinding()] param() try { # Check if AutoSave is enabled if (-not (Test-AutoSaveEnabled)) { Write-DebugOutput -Message "AutoSave is disabled, skipping settings restore" -Source $MyInvocation.MyCommand -Level "Info" return } $autoSaveDir = Get-AutoSaveDirectory $settingsControl = $syncHash.UIConfigs.settingsControl $restoredCount = 0 # Dynamically restore all settings types from configuration using new settingsControl structure foreach ($tabName in $settingsControl.PSObject.Properties.Name) { $tabConfig = $settingsControl.$tabName $settingsType = $tabConfig.dataControlOutput if (-not $settingsType) { Write-DebugOutput -Message "No dataControlOutput found for tab: $tabName" -Source $MyInvocation.MyCommand -Level "Verbose" continue } $fileName = "${settingsType}.json" $filePath = Join-Path $autoSaveDir $fileName if (Test-Path $filePath) { try { # Load and parse the JSON data $settingsData = Get-Content -Path $filePath -Raw | ConvertFrom-Json # Ensure the target hashtable exists if (-not $syncHash.$settingsType) { $syncHash.$settingsType = [ordered]@{} } # Restore the settings data foreach ($key in $settingsData.PSObject.Properties.Name) { $syncHash.$settingsType[$key] = $settingsData.$key } $restoredCount++ Write-DebugOutput -Message "Restored AutoSave settings: $settingsType from $fileName" -Source $MyInvocation.MyCommand -Level "Info" } catch { Write-DebugOutput -Message "Error restoring AutoSave settings ${settingsType}: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" } } else { Write-DebugOutput -Message "No AutoSave file found for ${settingsType}: $fileName" -Source $MyInvocation.MyCommand -Level "Verbose" } } Write-DebugOutput -Message "AutoSave settings restore completed: $restoredCount settings files restored" -Source $MyInvocation.MyCommand -Level "Info" } catch { Write-DebugOutput -Message "Error during AutoSave settings restore: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" } } Function Clear-AutoSaveData { <# .SYNOPSIS Clears all AutoSave data files. .DESCRIPTION Removes all AutoSave files from the AutoSave directory. Useful for cleanup or reset operations. #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [switch]$PolicyOnly, [Parameter(Mandatory = $false)] [switch]$SettingsOnly ) try { $autoSaveDir = Get-AutoSaveDirectory if (-not (Test-Path $autoSaveDir)) { Write-DebugOutput -Message "AutoSave directory does not exist, nothing to clear" -Source $MyInvocation.MyCommand -Level "Info" return } $filesToRemove = @() if ($PolicyOnly) { # Remove only policy files (CardName_PolicyId.json pattern) $filesToRemove = Get-ChildItem -Path $autoSaveDir -Filter "*.json" | Where-Object { $_.BaseName -match '^.+_.+$' } } elseif ($SettingsOnly) { # Remove only settings files (SettingsType.json pattern, no underscore) $filesToRemove = Get-ChildItem -Path $autoSaveDir -Filter "*.json" | Where-Object { $_.BaseName -notmatch '_' } } else { # Remove all JSON files $filesToRemove = Get-ChildItem -Path $autoSaveDir -Filter "*.json" } $removedCount = 0 foreach ($file in $filesToRemove) { Remove-Item -Path $file.FullName -Force $removedCount++ } $typeDescription = if ($PolicyOnly) { "policy" } elseif ($SettingsOnly) { "settings" } else { "all" } Write-DebugOutput -Message "Cleared $removedCount $typeDescription AutoSave files" -Source $MyInvocation.MyCommand -Level "Info" } catch { Write-DebugOutput -Message "Error clearing AutoSave data: $($_.Exception.Message)" -Source $MyInvocation.MyCommand -Level "Error" } } |