<# .SYNOPSIS NoID Privacy - Interactive Menu Interface .DESCRIPTION User-friendly interactive menu for Windows 11 security hardening with: - Visual menu navigation - Clear status feedback - Automatic backups before changes - Easy restore and verification - Guided workflow LINK https://github.com/NexusOne23/noid-privacy .NOTES DISCLAIMER: This software is provided "as is" without warranty of any kind. By using this software, you agree that the authors are not liable for any damages resulting from its use. USE AT YOUR OWN RISK. Author: NexusOne23 Version: 2.2.2 Requires: PowerShell 5.1+, Administrator For CLI mode use: NoIDPrivacy.ps1 -Module #> #Requires -Version 5.1 #Requires -RunAsAdministrator # No parameters - interactive mode only $ErrorActionPreference = 'Stop' $Host.UI.RawUI.WindowTitle = "NoID Privacy v2.2.2" # Set script root path (required by modules to load configs) $script:RootPath = $PSScriptRoot # ============================================================================ # COLOR FUNCTIONS # ============================================================================ function Write-ColorText { param( [string]$Text, [string]$Color = 'White', [switch]$NoNewline ) if ($NoColor) { if ($NoNewline) { Write-Host $Text -NoNewline } else { Write-Host $Text } } else { if ($NoNewline) { Write-Host $Text -ForegroundColor $Color -NoNewline } else { Write-Host $Text -ForegroundColor $Color } } } function Write-Header { param([string]$Text) Write-Host "" Write-ColorText "====================================================================" -Color Cyan # Pad the header text so it fully overwrites any residual characters on the console line Write-ColorText (" $Text".PadRight(68)) -Color Cyan Write-ColorText "====================================================================" -Color Cyan Write-Host "" } function Write-Step { param( [string]$Text, [string]$Status = "INFO" ) $symbol = switch ($Status) { "SUCCESS" { "[+]"; $color = "Green" } "ERROR" { "[-]"; $color = "Red" } "WARNING" { "[!]"; $color = "Yellow" } "INFO" { "[>]"; $color = "Cyan" } "WAIT" { "[.]"; $color = "Gray" } default { "[ ]"; $color = "White" } } Write-ColorText $symbol -Color $color -NoNewline Write-Host " $Text" } function Write-Banner { Clear-Host Write-Host "" Write-Host " ========================================" -ForegroundColor Cyan Write-Host " NoID Privacy v2.2.2 " -ForegroundColor Cyan Write-Host " ========================================" -ForegroundColor Cyan Write-Host "" Write-Host " Professional Windows 11 Security & Privacy Hardening Framework" -ForegroundColor Gray # Dynamic environment line: fixed product version, live Windows build + PowerShell version try { $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue } catch { $os = $null } $osBuild = if ($os) { $os.BuildNumber } else { $null } $psVersion = $PSVersionTable.PSVersion.ToString() $envLine = " Version 2.2.2" if ($osBuild) { $envLine += " | Windows Build $osBuild" } else { $envLine += " | Windows 11+" } $envLine += " | PowerShell $psVersion" Write-Host $envLine -ForegroundColor DarkGray Write-Host "" } # ============================================================================ # PROGRESS FUNCTIONS # ============================================================================ function Show-Progress { param( [string]$Activity, [string]$Status, [int]$PercentComplete ) if (-not $Quiet) { Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete } } function Show-ModuleProgress { param( [string]$Module, [int]$Current, [int]$Total, [string]$Status = "Processing" ) $percent = [math]::Round(($Current / $Total) * 100) Show-Progress -Activity "Applying $Module" -Status "$Status ($Current/$Total)" -PercentComplete $percent } # ============================================================================ # SYSTEM INFO FUNCTION # ============================================================================ function Show-SystemInfo { Write-Header "SYSTEM INFORMATION" try { $os = Get-CimInstance Win32_OperatingSystem $cs = Get-CimInstance Win32_ComputerSystem Write-ColorText " Computer Name: " -Color Gray -NoNewline Write-ColorText $cs.Name -Color White Write-ColorText " OS Version: " -Color Gray -NoNewline Write-ColorText "$($os.Caption) Build $($os.BuildNumber)" -Color White Write-ColorText " PowerShell: " -Color Gray -NoNewline Write-ColorText "$($PSVersionTable.PSVersion)" -Color White Write-ColorText " Administrator: " -Color Gray -NoNewline $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) $adminStatus = if ($isAdmin) { "Yes [+]" } else { "No [-]" } $adminColor = if ($isAdmin) { "Green" } else { "Red" } Write-ColorText $adminStatus -Color $adminColor Write-ColorText " Domain Joined: " -Color Gray -NoNewline $isDomain = $cs.PartOfDomain $domainStatus = if ($isDomain) { "Yes" } else { "No (Standalone)" } $domainColor = if ($isDomain) { "Yellow" } else { "Cyan" } Write-ColorText $domainStatus -Color $domainColor Write-Host "" # Security Status Write-ColorText " Security Status:" -Color Yellow # Check VBS try { $vbs = Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard -ErrorAction SilentlyContinue if ($vbs) { Write-ColorText " VBS Enabled: " -Color Gray -NoNewline $vbsStatus = if ($vbs.VirtualizationBasedSecurityStatus -eq 2) { "Yes [+]" } else { "No [-]" } $vbsColor = if ($vbs.VirtualizationBasedSecurityStatus -eq 2) { "Green" } else { "Red" } Write-ColorText $vbsStatus -Color $vbsColor } } catch { # VBS status could not be determined - not critical for menu display Write-ColorText " VBS Enabled: " -Color Gray -NoNewline Write-ColorText "Unknown" -Color Yellow } # Check Defender try { $defender = Get-MpComputerStatus -ErrorAction SilentlyContinue if ($defender) { Write-ColorText " Defender Active: " -Color Gray -NoNewline $defenderStatus = if ($defender.AntivirusEnabled) { "Yes [+]" } else { "No [-]" } $defenderColor = if ($defender.AntivirusEnabled) { "Green" } else { "Red" } Write-ColorText $defenderStatus -Color $defenderColor } } catch { # Defender status could not be determined - not critical for menu display Write-ColorText " Defender Active: " -Color Gray -NoNewline Write-ColorText "Unknown" -Color Yellow } Write-Host "" } catch { Write-Step "Failed to retrieve system information" -Status ERROR } Write-Host "" Write-ColorText "====================================================================" -Color Cyan Write-ColorText " Press any key to return to the main menu..." -Color White Write-ColorText "====================================================================" -Color Cyan Write-Host "" $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") Write-Host "" } # ============================================================================ # REBOOT PROMPT FUNCTION # ============================================================================ function Invoke-RebootPrompt { <# .SYNOPSIS Prompts user for system reboot with countdown .PARAMETER Context Context for reboot: 'Hardening' or 'Restore' #> param( [Parameter(Mandatory = $false)] [ValidateSet('Hardening', 'Restore')] [string]$Context = 'Hardening' ) Write-Host "" Write-ColorText "====================================================================" -Color Cyan Write-ColorText " SYSTEM REBOOT RECOMMENDED" -Color Yellow Write-ColorText "====================================================================" -Color Cyan Write-Host "" if ($Context -eq 'Hardening') { Write-ColorText " The applied security changes require a system reboot to take full effect." -Color White Write-Host "" Write-ColorText " Changes that may require reboot:" -Color Gray Write-Host "" Write-ColorText " - Registry Policies" -Color Yellow -NoNewline Write-ColorText " (Security settings and configurations)" -Color Gray Write-ColorText " - Windows Services" -Color Yellow -NoNewline Write-ColorText " (Startup type and state changes)" -Color Gray Write-ColorText " - Windows Features" -Color Yellow -NoNewline Write-ColorText " (PowerShell v2, optional components)" -Color Gray Write-ColorText " - Network Settings" -Color Yellow -NoNewline Write-ColorText " (Admin Shares, NetBIOS, Firewall rules)" -Color Gray Write-Host "" Write-ColorText " All applied changes are active but become " -Color Gray -NoNewline Write-ColorText "fully effective after reboot." -Color White Write-Host "" Write-Host "" Write-ColorText " To verify full compliance after reboot, run:" -Color White Write-ColorText " .\Tools\Verify-Complete-Hardening.ps1" -Color Cyan } else { # Restore context Write-ColorText " RECOMMENDED: Reboot after restore" -Color White Write-Host "" Write-ColorText " Some security settings require a reboot to be fully activated:" -Color Gray Write-Host "" Write-ColorText " - Group Policy changes (processed but not fully active)" -Color Gray Write-ColorText " - Security Template settings (user rights, audit)" -Color Gray Write-ColorText " - Registry policies affecting boot-time services" -Color Gray Write-Host "" Write-ColorText " While gpupdate has processed the restored policies, a reboot" -Color Gray Write-ColorText " ensures complete activation of all security settings." -Color Gray } Write-Host "" Write-ColorText "====================================================================" -Color Cyan Write-Host "" # Prompt user with validation loop do { Write-ColorText " Reboot now? [Y/N] (default: Y): " -Color White -NoNewline $choice = Read-Host if ([string]::IsNullOrWhiteSpace($choice)) { $choice = "Y" } $choice = $choice.Trim().ToUpper() if ($choice -notin @('Y', 'N')) { Write-Host "" Write-ColorText " Invalid input. Please enter Y or N." -Color Red Write-Host "" } } while ($choice -notin @('Y', 'N')) if ($choice -eq 'Y') { Write-Host "" Write-Step "Initiating system reboot in 10 seconds..." -Status WARNING Write-ColorText " Press Ctrl+C to cancel" -Color Gray Write-Host "" # Countdown from 10 for ($i = 10; $i -gt 0; $i--) { Write-ColorText " Rebooting in $i seconds..." -Color Yellow Start-Sleep -Seconds 1 } Write-Host "" Write-Step "Rebooting system now..." -Status SUCCESS Write-Host "" # Reboot Restart-Computer -Force } else { Write-Host "" Write-Step "Reboot deferred" -Status WARNING Write-Host "" Write-ColorText " IMPORTANT: Please reboot manually at your earliest convenience." -Color White Write-ColorText " The hardening will not be fully effective until after reboot." -Color Gray Write-Host "" if ($Context -eq 'Hardening') { Write-ColorText " To verify after reboot, run:" -Color White Write-ColorText " .\Tools\Verify-Complete-Hardening.ps1" -Color Cyan Write-Host "" } } } # ============================================================================ # BACKUP LIST FUNCTION # ============================================================================ function Show-BackupList { Write-Header "AVAILABLE BACKUP SESSIONS" $backupPath = Join-Path $PSScriptRoot "Backups" # Get sessions using new Rollback.ps1 function try { # Force result to array to handle single-session case correctly if (Test-Path $backupPath) { $sessions = @(Get-BackupSessions -BackupDirectory $backupPath) } else { $sessions = @() } } catch { Write-Step "Failed to load backup sessions: $_" -Status ERROR Write-Host "" return $null } # Single check for no sessions (whether folder doesn't exist or is empty) if ($sessions.Count -eq 0) { Write-Step "No backup sessions found" -Status WARNING Write-Host "" return $null } Write-ColorText " Found $($sessions.Count) backup session(s):" -Color Cyan Write-Host "" for ($i = 0; $i -lt $sessions.Count; $i++) { $session = $sessions[$i] try { $age = (Get-Date) - $session.Timestamp $ageStr = if ($age.TotalHours -lt 1) { "$([math]::Round($age.TotalMinutes)) minutes ago" } elseif ($age.TotalDays -lt 1) { "$([math]::Round($age.TotalHours)) hours ago" } else { "$([math]::Round($age.TotalDays)) days ago" } } catch { $ageStr = "unknown age" } Write-ColorText " [$($i+1)] " -Color Yellow -NoNewline Write-ColorText "$($session.SessionId)" -Color White -NoNewline Write-ColorText " ($ageStr)" -Color Gray Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Modules: " -Color DarkGray -NoNewline if ($session.Modules -and $session.Modules.Count -gt 0) { Write-ColorText ($session.Modules.name -join ", ") -Color Cyan } else { Write-ColorText "No modules" -Color Red } Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Total Items: " -Color DarkGray -NoNewline Write-ColorText "$($session.TotalItems)" -Color Green Write-Host "" } Write-Host "" # Ensure we return an array (even for single item) return @($sessions) } # ============================================================================ # MAIN MENU # ============================================================================ function Show-MainMenu { Write-Banner Write-Header "MAIN MENU" Write-ColorText " [A]" -Color Green -NoNewline Write-Host " Apply Security Hardening" Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Automatic backup before changes" -Color DarkGray Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Comprehensive verification" -Color DarkGray Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Detailed progress tracking" -Color DarkGray Write-Host "" Write-ColorText " [V]" -Color Cyan -NoNewline Write-Host " Verify Current Settings" Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Check all 630+ hardening settings" -Color DarkGray Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Detailed compliance report" -Color DarkGray Write-Host "" Write-ColorText " [R]" -Color Yellow -NoNewline Write-Host " Restore from Backup" Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Rollback to previous state" -Color DarkGray Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Complete system restoration" -Color DarkGray Write-Host "" Write-ColorText " [I]" -Color Magenta -NoNewline Write-Host " System Information" Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> OS, Build, Security Status" -Color DarkGray Write-Host "" Write-ColorText " [X]" -Color Red -NoNewline Write-Host " Exit" Write-Host "" Write-ColorText "====================================================================" -Color Cyan Write-Host "" Write-ColorText " Select [A/V/R/I/X]: " -Color White -NoNewline } # ============================================================================ # MODULE SELECTION MENU # ============================================================================ function Show-ModuleMenu { Write-Banner Write-Header "SELECT MODULES TO APPLY" # Module definitions with descriptions $moduleDefinitions = @{ "SecurityBaseline" = "Microsoft Security Baseline (425 settings)" "ASR" = "Attack Surface Reduction (19 rules)" "DNS" = "Secure DNS with DoH (Quad9/Cloudflare/AdGuard)" "Privacy" = "Telemetry & Privacy hardening (3 modes)" "AntiAI" = "Disable Windows AI features (15 features, 32 policies)" "EdgeHardening" = "Secure Microsoft Edge browser (24 policies)" "AdvancedSecurity" = "Beyond MS Baseline (50 settings, 15 features)" } # Try to load config.json to check module status $configPath = Join-Path $PSScriptRoot "config.json" $config = $null if (Test-Path $configPath) { try { $config = Get-Content $configPath -Raw | ConvertFrom-Json } catch { # If config fails to load, all modules default to enabled } } # Build module list with status from config $modules = @( [PSCustomObject]@{ Key = "1"; Name = "SecurityBaseline"; Description = $moduleDefinitions["SecurityBaseline"]; Enabled = $true } [PSCustomObject]@{ Key = "2"; Name = "ASR"; Description = $moduleDefinitions["ASR"]; Enabled = $true } [PSCustomObject]@{ Key = "3"; Name = "DNS"; Description = $moduleDefinitions["DNS"]; Enabled = $true } [PSCustomObject]@{ Key = "4"; Name = "Privacy"; Description = $moduleDefinitions["Privacy"]; Enabled = $true } [PSCustomObject]@{ Key = "5"; Name = "AntiAI"; Description = $moduleDefinitions["AntiAI"]; Enabled = $true } [PSCustomObject]@{ Key = "6"; Name = "EdgeHardening"; Description = $moduleDefinitions["EdgeHardening"]; Enabled = $true } [PSCustomObject]@{ Key = "7"; Name = "AdvancedSecurity"; Description = $moduleDefinitions["AdvancedSecurity"]; Enabled = $true } ) # Override enabled status from config.json if available if ($config -and $config.modules) { foreach ($module in $modules) { $configModule = $config.modules.PSObject.Properties[$module.Name] if ($configModule -and $configModule.Value.PSObject.Properties['enabled']) { $module.Enabled = [bool]$configModule.Value.enabled } } } foreach ($module in $modules) { if ($module.Enabled) { Write-ColorText " [$($module.Key)]" -Color Green -NoNewline } else { Write-ColorText " [$($module.Key)]" -Color DarkGray -NoNewline } Write-Host " $($module.Name)" Write-ColorText " " -Color Gray -NoNewline if ($module.Enabled) { Write-ColorText "> $($module.Description)" -Color White } else { Write-ColorText "> $($module.Description) " -Color DarkGray -NoNewline Write-ColorText "(Not yet implemented)" -Color DarkRed } } Write-Host "" Write-ColorText " [99]" -Color Yellow -NoNewline Write-Host " ALL MODULES (WIZARD) - Interactive setup for all modules" Write-Host "" Write-ColorText " [0]" -Color Red -NoNewline Write-Host " Back to Main Menu" Write-Host "" Write-ColorText "====================================================================" -Color Cyan Write-Host "" Write-ColorText " Select module [1-7, 99, 0]: " -Color White -NoNewline return $modules } # ============================================================================ # APPLY HARDENING WORKFLOW # ============================================================================ function Invoke-HardeningWorkflow { param([string[]]$SelectedModules) Write-Banner Write-Header "SECURITY HARDENING WORKFLOW" # Phase 1: Pre-flight checks Write-Step "Running pre-flight checks..." -Status INFO if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Step "Administrator privileges required" -Status ERROR return } Write-Step "Administrator privileges confirmed" -Status SUCCESS # Phase 2: Call real Framework Write-Host "" Write-Step "Initializing NoID Privacy Framework..." -Status INFO Write-ColorText " " -Color Gray -NoNewline Write-ColorText "> Automatic backup will be created before changes" -Color DarkGray Write-Host "" try { # Determine which modules to execute - ensure proper string array [string[]]$modulesToRun = @($SelectedModules) # Debug output to verify correct module names Write-Step "Modules to apply: $($modulesToRun -join ', ')" -Status INFO # Call the real framework via NoIDPrivacy.ps1 $frameworkScript = Join-Path $PSScriptRoot "NoIDPrivacy.ps1" if (-not (Test-Path $frameworkScript)) { Write-Step "Framework script not found: $frameworkScript" -Status ERROR return } # Execute framework (always with verbose logging for full support trace) Write-Step "Executing hardening modules..." -Status INFO Write-Host "" $allSucceeded = $true # FIX: Call framework ONCE with all modules instead of separate calls # This ensures single backup session and single log file # Exit code handling: 0 = Success, 10 = Success with Reboot recommended # Any other code indicates failure $rebootRecommended = $false if ($modulesToRun.Count -eq 7) { # All modules selected - use "All" for single unified session Write-Step "Running ALL modules in unified session..." -Status INFO & $frameworkScript -Module All -VerboseLogging if ($LASTEXITCODE -eq 10) { $rebootRecommended = $true } elseif ($LASTEXITCODE -ne 0) { $allSucceeded = $false } } elseif ($modulesToRun.Count -eq 1) { # Single module Write-Step "Running module: $($modulesToRun[0])" -Status INFO & $frameworkScript -Module $modulesToRun[0] -VerboseLogging if ($LASTEXITCODE -eq 10) { $rebootRecommended = $true } elseif ($LASTEXITCODE -ne 0) { $allSucceeded = $false } } else { # Multiple but not all modules - must run separately (Framework doesn't support partial list) # TODO: Future enhancement - add -Modules parameter to Framework for partial lists foreach ($mod in $modulesToRun) { Write-Step "Running module: $mod" -Status INFO & $frameworkScript -Module $mod -VerboseLogging if ($LASTEXITCODE -eq 10) { $rebootRecommended = $true } elseif ($LASTEXITCODE -ne 0) { $allSucceeded = $false } Write-Host "" } } Write-Host "" # Display results Write-Host "" Write-Header "HARDENING COMPLETE" if ($allSucceeded) { Write-ColorText " Status: " -Color Gray -NoNewline Write-ColorText "SUCCESS [+]" -Color Green } else { Write-ColorText " Status: " -Color Gray -NoNewline Write-ColorText "FAILED [-]" -Color Red } Write-ColorText " Modules Selected: " -Color Gray -NoNewline Write-ColorText "$($modulesToRun.Count)" -Color White Write-ColorText " (Check output above for actual results per module)" -Color DarkGray Write-Host "" if ($allSucceeded) { Write-ColorText " Your system is now hardened with enterprise-grade security!" -Color Green if ($rebootRecommended) { Write-ColorText " A system reboot is recommended for all changes to take effect." -Color Yellow } } else { Write-ColorText " Some modules had warnings or were skipped. Check details above." -Color Yellow Write-ColorText " Review the log file for complete details." -Color White } Write-Host "" # Prompt for reboot if recommended by exit code or if changes were made if ($rebootRecommended -or $allSucceeded) { Invoke-RebootPrompt -Context 'Hardening' } Write-Host "" } catch { Write-Host "" Write-Step "Fatal error: $($_.Exception.Message)" -Status ERROR Write-Host "" } } # ============================================================================ # VERIFY WORKFLOW # ============================================================================ function Invoke-VerifyWorkflow { Write-Banner Write-Header "SETTINGS VERIFICATION" Write-Step "Running comprehensive verification..." -Status INFO Write-Host "" try { # Call the real verification script $verifyScript = Join-Path $PSScriptRoot "Tools\Verify-Complete-Hardening.ps1" if (Test-Path $verifyScript) { # Discard return value so that 'True' / 'False' is not printed to console $null = & $verifyScript } else { Write-Step "Verification script not found: $verifyScript" -Status ERROR } } catch { Write-Step "Verification failed: $($_.Exception.Message)" -Status ERROR } Write-Host "" } # ============================================================================ # RESTORE WORKFLOW # ============================================================================ function Invoke-RestoreWorkflow { $sessions = Show-BackupList # Show-BackupList already displays warning if no sessions found if (-not $sessions -or $sessions.Count -eq 0) { Start-Sleep -Seconds 2 return } # Force as array to ensure Count property exists $sessions = @($sessions) Write-ColorText " Enter session number to restore [1-$($sessions.Count)] or 0 to cancel: " -Color White -NoNewline $selection = Read-Host # Trim whitespace $selection = $selection.Trim() if ($selection -eq "0" -or [string]::IsNullOrWhiteSpace($selection)) { return } # Try to parse as integer try { $index = [int]$selection - 1 } catch { Write-Step "Invalid selection - please enter a number" -Status ERROR Start-Sleep -Seconds 2 return } if ($index -lt 0 -or $index -ge $sessions.Count) { Write-Step "Invalid selection - please enter a number between 1 and $($sessions.Count)" -Status ERROR Start-Sleep -Seconds 2 return } $selectedSession = $sessions[$index] # Determine available modules in this session $availableModules = @() if ($selectedSession.Modules) { $availableModules = @($selectedSession.Modules) } $restoreMode = "A" # A = All modules, M = Selected modules $selectedModuleNames = @() if ($availableModules.Count -gt 0) { Write-Host "" Write-Header "RESTORE MODE" Write-ColorText " Session contains the following modules:" -Color Yellow Write-Host "" for ($m = 0; $m -lt $availableModules.Count; $m++) { $mod = $availableModules[$m] Write-ColorText " [$($m+1)] " -Color Cyan -NoNewline Write-ColorText "$($mod.name)" -Color White } Write-Host "" # Restore mode selection do { Write-ColorText " [A] Restore ALL modules in this session (recommended)" -Color Green Write-ColorText " [M] Restore only SELECTED modules from this session" -Color Cyan Write-Host "" Write-ColorText " Select restore mode [A/M] (default: A): " -Color White -NoNewline $modeInput = Read-Host if ([string]::IsNullOrWhiteSpace($modeInput)) { $modeInput = "A" } $modeInput = $modeInput.Trim().ToUpper() if ($modeInput -in @('A','M')) { $restoreMode = $modeInput break } Write-Host "" Write-Step "Invalid input. Please enter A or M." -Status ERROR Write-Host "" } while ($true) # If user chose module selection, ask for specific modules if ($restoreMode -eq 'M') { Write-Host "" Write-ColorText " Enter module numbers to restore [1-$($availableModules.Count), e.g. 1,3,5] or 0 to cancel: " -Color White -NoNewline $moduleInput = Read-Host $moduleInput = $moduleInput.Trim() if ([string]::IsNullOrWhiteSpace($moduleInput) -or $moduleInput -eq '0') { Write-Step "Restore cancelled" -Status WARNING Start-Sleep -Seconds 1 return } $indices = @() foreach ($token in $moduleInput.Split(',', ';', ' ')) { if (-not [string]::IsNullOrWhiteSpace($token)) { $parsed = 0 if ([int]::TryParse($token.Trim(), [ref]$parsed)) { if ($parsed -ge 1 -and $parsed -le $availableModules.Count) { $indices += $parsed } } } } $indices = $indices | Sort-Object -Unique if ($indices.Count -eq 0) { Write-Step "No valid module numbers selected - restore cancelled" -Status ERROR Start-Sleep -Seconds 2 return } foreach ($i in $indices) { $selectedModuleNames += $availableModules[$i-1].name } } } Write-Host "" Write-Header "RESTORE CONFIRMATION" Write-ColorText " You are about to restore from:" -Color Yellow Write-ColorText " Session: $($selectedSession.SessionId)" -Color White Write-ColorText " Created: $($selectedSession.Timestamp)" -Color Gray if ($restoreMode -eq 'M' -and $selectedModuleNames.Count -gt 0) { Write-ColorText " Modules to restore: $($selectedModuleNames -join ', ')" -Color Cyan } else { Write-ColorText " Modules: $($selectedSession.Modules.name -join ', ')" -Color Cyan } Write-ColorText " Total Items: $($selectedSession.TotalItems)" -Color Green Write-Host "" if ($restoreMode -eq 'M' -and $selectedModuleNames.Count -gt 0) { Write-ColorText " This will revert ALL settings for the selected modules in this session." -Color Yellow } else { Write-ColorText " This will revert ALL security settings to the backup state for this session." -Color Yellow } Write-Host "" Write-ColorText " Are you sure? [Y/N]: " -Color White -NoNewline $confirm = Read-Host if ($confirm -ne "Y" -and $confirm -ne "y") { Write-Step "Restore cancelled" -Status WARNING Start-Sleep -Seconds 1 return } Write-Host "" Write-Step "Starting session restore..." -Status INFO Write-Host "" try { # Restore-Session should already be loaded from Rollback.ps1 if (Get-Command Restore-Session -ErrorAction SilentlyContinue) { # Call restore for the selected session (full or partial) if ($restoreMode -eq 'M' -and $selectedModuleNames.Count -gt 0) { $success = Restore-Session -SessionPath $selectedSession.FolderPath -ModuleNames $selectedModuleNames } else { $success = Restore-Session -SessionPath $selectedSession.FolderPath } if ($success) { Write-Host "" Write-Step "Session restored successfully" -Status SUCCESS # Note: Reboot prompt is handled by Restore-Session in Rollback.ps1 } else { Write-Host "" Write-Step "Session restore completed with some failures" -Status WARNING # Note: Reboot prompt is handled by Restore-Session in Rollback.ps1 } } else { Write-Step "Restore function not available - Rollback.ps1 not loaded" -Status ERROR } } catch { Write-Step "Restore failed: $($_.Exception.Message)" -Status ERROR } Write-Host "" } # ============================================================================ # MAIN PROGRAM LOOP # ============================================================================ try { # Load Logger (required by Rollback.ps1) but don't initialize yet $loggerPath = Join-Path $PSScriptRoot "Core\Logger.ps1" if (Test-Path $loggerPath) { . $loggerPath } # Create dummy Write-Log if not initialized (for Rollback.ps1) if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) { function Write-Log { param($Level, $Message, $Module, $Exception) } } # Load Rollback system (required for Get-BackupSessions) $rollbackPath = Join-Path $PSScriptRoot "Core\Rollback.ps1" if (Test-Path $rollbackPath) { . $rollbackPath } else { Write-Host "[ERROR] Rollback.ps1 not found!" -ForegroundColor Red exit 1 } # Load Framework (required for core functions like Test-IsAdmin used by modules) $frameworkPath = Join-Path $PSScriptRoot "Core\Framework.ps1" if (Test-Path $frameworkPath) { . $frameworkPath } else { Write-Host "[ERROR] Framework.ps1 not found!" -ForegroundColor Red exit 1 } while ($true) { # Clear before each main menu redraw Clear-Host Show-MainMenu $choice = Read-Host switch ($choice.ToUpper()) { "A" { # Initialize to ensure clean state [string[]]$selectedModules = @() $modules = Show-ModuleMenu $moduleChoice = Read-Host if ($moduleChoice -eq "0") { continue } elseif ($moduleChoice -eq "99") { # All modules - force as string array [string[]]$selectedModules = @($modules | Where-Object { $_.Enabled } | ForEach-Object { $_.Name }) } else { $selectedModule = $modules | Where-Object { $_.Key -eq $moduleChoice } if ($selectedModule -and $selectedModule.Enabled) { # Single module - force as string array with explicit cast [string[]]$selectedModules = @([string]$selectedModule.Name) } else { Write-Step "Invalid or unavailable module" -Status ERROR Start-Sleep -Seconds 1 continue } } # Pass as explicit array Invoke-HardeningWorkflow -SelectedModules ([string[]]$selectedModules) Write-Host "" Write-ColorText "====================================================================" -Color Cyan Write-ColorText " Press any key to return to the main menu..." -Color White Write-ColorText "====================================================================" -Color Cyan Write-Host "" $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } "V" { Invoke-VerifyWorkflow Write-Host "" Write-ColorText "====================================================================" -Color Cyan Write-ColorText " Press any key to return to the main menu..." -Color White Write-ColorText "====================================================================" -Color Cyan Write-Host "" $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } "R" { Invoke-RestoreWorkflow Write-Host "" Write-ColorText "====================================================================" -Color Cyan Write-ColorText " Press any key to return to the main menu..." -Color White Write-ColorText "====================================================================" -Color Cyan Write-Host "" $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } "I" { Show-SystemInfo } "X" { Write-Host "" Write-ColorText " Thank you for using NoID Privacy!" -Color Cyan Write-Host "" exit 0 } default { # Invalid choice, loop continues } } } } catch { Write-Host "" Write-Step "Fatal error: $($_.Exception.Message)" -Status ERROR Write-Host "" Write-ColorText " Press any key to exit..." -Color Gray $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") exit 1 }