Files
ms-private/ms-private-GUI.ps1
T
0x000 4dfec83063
Windows tests / test-and-build (push) Canceled after 0s
Fix GUI operation script scope
2026-09-24 13:19:44 -04:00

143 lines
23 KiB
PowerShell

#Requires -Version 5.1
[CmdletBinding()]param()
Set-StrictMode -Version Latest;$ErrorActionPreference='Stop'
if([Environment]::OSVersion.Platform-ne[PlatformID]::Win32NT){throw 'Windows is required.'}
Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing
Import-Module (Join-Path $PSScriptRoot 'src/Wind.Core.psm1');Import-Module (Join-Path $PSScriptRoot 'src/Wind.Hardware.psm1');Import-Module (Join-Path $PSScriptRoot 'src/Wind.Apps.psm1');Import-Module (Join-Path $PSScriptRoot 'src/Wind.Recall.psm1');Import-Module (Join-Path $PSScriptRoot 'src/MsPrivate.Presets.psm1');Import-Module (Join-Path $PSScriptRoot 'src/MsPrivate.Execution.psm1')
$windowsContext=Get-WindContext
function Add-WindCheckBox {param($Parent,[string]$Text,[int]$X,[int]$Y,[bool]$Checked=$false,[int]$Width=520);$c=[Windows.Forms.CheckBox]::new();$c.Text=$Text;$c.Location=[Drawing.Point]::new($X,$Y);$c.Size=[Drawing.Size]::new($Width,26);$c.Checked=$Checked;$Parent.Controls.Add($c);$c}
function Add-WindLabel {param($Parent,[string]$Text,[int]$X,[int]$Y,[int]$Width=600,[int]$Height=40);$l=[Windows.Forms.Label]::new();$l.Text=$Text;$l.Location=[Drawing.Point]::new($X,$Y);$l.Size=[Drawing.Size]::new($Width,$Height);$Parent.Controls.Add($l);$l}
$invokeWindChild=New-MsPrivateScriptInvoker -Root $PSScriptRoot
$form=[Windows.Forms.Form]::new();$form.Text='ms-private';$form.Size=[Drawing.Size]::new(860,690);$form.MinimumSize=[Drawing.Size]::new(760,600);$form.StartPosition='CenterScreen';$form.Font=[Drawing.Font]::new('Segoe UI',10);$form.BackColor=[Drawing.Color]::FromArgb(245,247,250)
$title=Add-WindLabel $form 'ms-private - Windows privacy control center' 22 15 790 35;$title.Font=[Drawing.Font]::new('Segoe UI Semibold',16);$title.ForeColor=[Drawing.Color]::FromArgb(20,65,110)
$tabs=[Windows.Forms.TabControl]::new();$tabs.Location=[Drawing.Point]::new(20,58);$tabs.Size=[Drawing.Size]::new(804,510);$form.Controls.Add($tabs)
$privacy=[Windows.Forms.TabPage]::new('Privacy');$apps=[Windows.Forms.TabPage]::new('Apps & browsers');$network=[Windows.Forms.TabPage]::new('Network');$devices=[Windows.Forms.TabPage]::new('Devices');$maintenance=[Windows.Forms.TabPage]::new('Maintenance');$homeTab=[Windows.Forms.TabPage]::new('Home guide');$tabs.TabPages.AddRange(@($privacy,$apps,$network,$devices,$maintenance,$homeTab))
Add-WindLabel $privacy 'The starting preset is selected from Windows and profile age. Every apply operation still requires exact-plan review.' 20 18 700 28|Out-Null
$preset=[Windows.Forms.ComboBox]::new();$preset.DropDownStyle='DropDownList';$preset.Location=[Drawing.Point]::new(530,52);$preset.Width=230;$privacy.Controls.Add($preset);Add-WindLabel $privacy 'Automatic preset' 530 20 220 28|Out-Null
$presetCatalog=Get-MsPrivatePresetCatalog;$installContext=Get-MsPrivateInstallContext;[void]$preset.Items.Add('Custom');foreach($item in $presetCatalog.Presets){[void]$preset.Items.Add($item.Label)}
$profile=[Windows.Forms.ComboBox]::new();$profile.DropDownStyle='DropDownList';$profile.Items.AddRange(@('Standard','Strict'));$profile.SelectedIndex=1;$profile.Location=[Drawing.Point]::new(24,58);$profile.Width=220;$privacy.Controls.Add($profile)
$updates=[Windows.Forms.ComboBox]::new();$updates.DropDownStyle='DropDownList';$updates.Items.AddRange(@('Keep','Automatic','Notify','Manual'));$updates.SelectedIndex=0;$updates.Location=[Drawing.Point]::new(270,58);$updates.Width=220;$privacy.Controls.Add($updates)
$services=Add-WindCheckBox $privacy 'Disable connected-device, cloud-sync, diagnostic, location and WER services' 24 110 $true 720
$accounts=Add-WindCheckBox $privacy 'Sign out Microsoft app accounts (Store, Xbox, packaged Office/Teams, shared account broker)' 24 148 $true 760
$profile.Add_SelectedIndexChanged({$strict=([string]$profile.SelectedItem-eq'Strict');$services.Checked=$strict;$accounts.Checked=$strict})
$recall=Add-WindCheckBox $privacy 'Disable Recall policy and delete existing Recall snapshots' 24 188 $false 620
$removeRecall=Add-WindCheckBox $privacy 'Also disable the Recall optional feature (may require reboot)' 48 222 $false 620
Add-WindLabel $privacy 'Strict blocks future Microsoft app authentication. Account purge permanently deletes current-user app sign-in data and matching stored credentials; it does not remove the Windows logon identity.' 24 258 730 72|Out-Null
$installContextLabel=Add-WindLabel $privacy ("Detected: $($installContext.Classification). Recommended: $($installContext.RecommendedPreset). $($installContext.Reason)") 24 338 730 68;$installContextLabel.ForeColor=[Drawing.Color]::FromArgb(75,85,95)
Add-WindLabel $apps 'Selected browsers are installed and verified before the first app removal.' 20 18 700 28|Out-Null
$zen=Add-WindCheckBox $apps 'Zen Browser' 24 58 $true 250;$helium=Add-WindCheckBox $apps 'Helium' 24 90 $false 250;$tor=Add-WindCheckBox $apps 'Tor Browser' 24 122 $false 250
$bundled=Add-WindCheckBox $apps 'Remove curated bundled/promotional apps' 330 58 $true 420;$oneDrive=Add-WindCheckBox $apps 'Remove OneDrive' 330 90 $true 420;$edge=Add-WindCheckBox $apps 'Remove Edge after browser verification' 330 122 $true 420
$mullvad=Add-WindCheckBox $apps 'Install Mullvad VPN (configuration remains manual)' 24 172 $false 350;$veracrypt=Add-WindCheckBox $apps 'Install VeraCrypt' 400 172 $false 300
$personal=Add-WindCheckBox $apps 'Remove every removable personal / third-party app' 24 216 $false 520
$store=Add-WindCheckBox $apps 'Remove Microsoft Store and Store Purchase App for this user' 24 250 $false 620
Add-WindLabel $apps 'Fresh Install, Medium and stronger presets select both options. Established PC keeps every application. WebView2, security tools, drivers, runtimes, servicing, chosen browsers and recovery components remain protected.' 24 290 710 70|Out-Null
Add-WindLabel $network 'Observe changes nothing. Optional telemetry blocks only the reviewed endpoint catalog. Isolation blocks default outbound traffic and automatically rolls back.' 20 18 730 58|Out-Null
$netProfile=[Windows.Forms.ComboBox]::new();$netProfile.DropDownStyle='DropDownList';$netProfile.Items.AddRange(@('Observe','BlockOptionalTelemetry','Isolate'));$netProfile.SelectedIndex=0;$netProfile.Location=[Drawing.Point]::new(24,85);$netProfile.Width=280;$network.Controls.Add($netProfile)
$rollback=[Windows.Forms.NumericUpDown]::new();$rollback.Minimum=2;$rollback.Maximum=60;$rollback.Value=10;$rollback.Location=[Drawing.Point]::new(330,85);$network.Controls.Add($rollback);Add-WindLabel $network 'minutes to automatic rollback' 460 87 240 28|Out-Null
$capture=[Windows.Forms.Button]::new();$capture.Text='Start local packet capture';$capture.Location=[Drawing.Point]::new(24,140);$capture.Size=[Drawing.Size]::new(250,38);$network.Controls.Add($capture)
$capture.Add_Click({try{& $invokeWindChild 'Network.ps1' @{Mode='CaptureStart'};[Windows.Forms.MessageBox]::Show('Capture started. Use Network.ps1 -Mode CaptureStop -CaptureDirectory <shown path> to stop and convert it.','ms-private')|Out-Null}catch{[Windows.Forms.MessageBox]::Show($_.Exception.Message,'ms-private error')|Out-Null}})
Add-WindLabel $devices 'Select exact hardware devices to disable. Nothing is selected by default.' 20 18 700 28|Out-Null
$deviceList=[Windows.Forms.CheckedListBox]::new();$deviceList.Location=[Drawing.Point]::new(24,58);$deviceList.Size=[Drawing.Size]::new(720,340);$deviceList.CheckOnClick=$true;$devices.Controls.Add($deviceList)
$sensorItems=@();try{$sensorItems=@(Get-WindSensorInventory);foreach($d in $sensorItems){[void]$deviceList.Items.Add("$($d.Kind): $($d.Name) [$($d.Status)]")}}catch{Add-WindLabel $devices ('Inventory unavailable: '+$_.Exception.Message) 24 410 700 50|Out-Null}
$cleanup=Add-WindCheckBox $maintenance 'Run safe temporary-file cleanup now (files older than 7 days)' 24 40 $false 650
$privacyCleanup=Add-WindCheckBox $maintenance 'Clear privacy artifacts now: WER/crash dumps, recent items, thumbnails, browser caches, PowerShell history and clipboard' 24 76 $false 760
$cleanupSchedule=Add-WindCheckBox $maintenance 'Install daily Temp and expired ms-private-report cleanup' 24 112 $false 650
$lock=Add-WindCheckBox $maintenance 'Install session-lock clipboard clearing' 24 148 $false 650
$vc=[Windows.Forms.TextBox]::new();$vc.Location=[Drawing.Point]::new(48,190);$vc.Width=210;$maintenance.Controls.Add($vc);Add-WindLabel $maintenance 'VeraCrypt drive letters, e.g. X:,Y:' 275 192 330 28|Out-Null
Add-WindLabel $maintenance 'Optional selected VeraCrypt volumes are unmounted gracefully on lock. Privacy cleanup is ordinary deletion, not guaranteed SSD erasure. Security, Defender, update, firewall and authentication event logs remain protected.' 24 236 700 72|Out-Null
$securityAudit=[Windows.Forms.Button]::new();$securityAudit.Text='Audit encryption and security';$securityAudit.Location=[Drawing.Point]::new(24,322);$securityAudit.Size=[Drawing.Size]::new(260,38);$maintenance.Controls.Add($securityAudit);$securityAudit.Add_Click({try{& $invokeWindChild 'Security.ps1' @{};[Windows.Forms.MessageBox]::Show('Security status was written to the PowerShell host. No recovery keys were read.','ms-private')|Out-Null}catch{[Windows.Forms.MessageBox]::Show($_.Exception.Message,'ms-private error')|Out-Null}})
Add-WindLabel $homeTab 'Windows Home does not enforce the same policy CSPs as Pro/Enterprise. These buttons open Microsoft-supported Settings pages; verify each visible toggle before treating it as applied.' 20 18 720 70|Out-Null
$pages=@('Location','Camera','Microphone','Diagnostics','Recall','Updates','Apps','DefaultApps');for($i=0;$i-lt$pages.Count;$i++){$p=$pages[$i];$b=[Windows.Forms.Button]::new();$b.Text=$p;$b.Size=[Drawing.Size]::new(165,38);$b.Location=[Drawing.Point]::new(24+(($i%4)*182),105+([Math]::Floor($i/4)*55));$b.Tag=$p;$b.Add_Click({param($s,$e)Start-Process (Join-Path $PSScriptRoot 'Home.ps1') -ArgumentList @('-Page',[string]$s.Tag)});$homeTab.Controls.Add($b)}
function Get-WindGuiAppPlan {
$inventory=@(Get-WindAppInventory)
@(Get-WindAppPlan -Inventory $inventory -SkipBundled:(-not$bundled.Checked) -KeepOneDrive:(-not$oneDrive.Checked) -KeepEdge:(-not$edge.Checked) -RemovePersonal:$personal.Checked -RemoveStore:$store.Checked)
}
function Set-WindGuiPreset {
param($p)
$script:applyingPreset=$true
try{
$profile.SelectedItem=$p.Profile;$updates.SelectedItem=$p.Updates;$services.Checked=$p.Services;$accounts.Checked=$p.Accounts
$zen.Checked=$p.Browsers-contains'Zen';$helium.Checked=$p.Browsers-contains'Helium';$tor.Checked=$p.Browsers-contains'Tor'
$bundled.Checked=$p.RemoveBundled;$personal.Checked=$p.RemovePersonal;$store.Checked=$p.RemoveStore;$oneDrive.Checked=$p.RemoveOneDrive;$edge.Checked=$p.RemoveEdge
$mullvad.Checked=$p.InstallMullvad;$veracrypt.Checked=$p.InstallVeraCrypt;$recall.Checked=$p.Recall;$removeRecall.Checked=$p.RemoveRecall
$netProfile.SelectedItem=$p.Network;$rollback.Value=[decimal]$p.RollbackMinutes;$cleanup.Checked=$p.Cleanup;$privacyCleanup.Checked=$p.PrivacyCleanup;$cleanupSchedule.Checked=$p.CleanupSchedule;$lock.Checked=$p.Lock
for($i=0;$i-lt$deviceList.Items.Count;$i++){$deviceList.SetItemChecked($i,[bool]$p.DisableSensors)}
}finally{$script:applyingPreset=$false}
}
function Get-WindGuiReviewText {
param([object[]]$AppPlan,[object[]]$PolicyPlan=@())
$remove=@($AppPlan|Where-Object {$_.Status -eq 'Remove'}|Sort-Object Name)
$supportedCount=@($PolicyPlan|Where-Object {$_.Status-in@('WouldChange','Configured')}).Count;$unsupportedCount=@($PolicyPlan|Where-Object {$_.Status-eq'Unsupported'}).Count
$lines=@("Windows: $($windowsContext.Edition), build $($windowsContext.Build).$($windowsContext.Revision)","Policy controls available: $supportedCount; unsupported on this edition/build: $unsupportedCount","Preset: $($preset.SelectedItem)","Privacy: $($profile.SelectedItem); updates: $($updates.SelectedItem)","Privacy services: $($services.Checked); account purge: $($accounts.Checked)","Network: $($netProfile.SelectedItem); Recall deletion/removal: $($recall.Checked)/$($removeRecall.Checked)","Cleanup now/privacy/schedule: $($cleanup.Checked)/$($privacyCleanup.Checked)/$($cleanupSchedule.Checked)","Sensors selected: $($deviceList.CheckedIndices.Count)","Applications selected: $($remove.Count)",'')
if($windowsContext.Edition-match'^Core' -and$unsupportedCount){$lines+='Windows Home does not support the managed-edition CSP policies below. Direct registry controls explicitly documented for Windows remain available; use the Home guide for the rest.';$lines+=''}
foreach($p in $PolicyPlan){$lines+="POLICY [$($p.Status)] $($p.Id)=$($p.Desired)"}
if($recall.Checked){$rp=Get-WindRecallPlan -RemoveFeature:$removeRecall.Checked;foreach($item in $rp.Items){$lines+="RECALL [$($item.Status)] $($item.Action)"}}
if($PolicyPlan.Count){$lines+=''}
foreach($a in $remove){$lines+="REMOVE [$($a.Scope)] $($a.Name) $($a.Version) - $($a.Reason)"}
if($deviceList.CheckedIndices.Count){$lines+='';foreach($i in $deviceList.CheckedIndices){$lines+="DISABLE $($deviceList.Items[[int]$i])"}}
$lines -join [Environment]::NewLine
}
function Show-WindGuiReview {
param([string]$Text)
$dialog=[Windows.Forms.Form]::new();$dialog.Text='ms-private exact plan';$dialog.Size=[Drawing.Size]::new(820,620);$dialog.StartPosition='CenterParent';$dialog.Font=$form.Font
$box=[Windows.Forms.TextBox]::new();$box.Multiline=$true;$box.ReadOnly=$true;$box.ScrollBars='Both';$box.WordWrap=$false;$box.Dock='Fill';$box.Text=$Text;$dialog.Controls.Add($box)
[void]$dialog.ShowDialog($form)
}
$script:applyingPreset=$false
$markCustom={if(-not$script:applyingPreset-and[string]$preset.SelectedItem-ne'Custom'){$preset.SelectedItem='Custom'}}
foreach($control in @($profile,$updates,$netProfile)){$control.Add_SelectedValueChanged($markCustom)}
foreach($control in @($services,$accounts,$recall,$removeRecall,$zen,$helium,$tor,$bundled,$oneDrive,$edge,$personal,$store,$mullvad,$veracrypt,$cleanup,$privacyCleanup,$cleanupSchedule,$lock)){$control.Add_CheckStateChanged($markCustom)}
$rollback.Add_ValueChanged($markCustom);$deviceList.Add_ItemCheck($markCustom)
$preset.Add_SelectedIndexChanged({$selected=@($presetCatalog.Presets|Where-Object Label -CEQ ([string]$preset.SelectedItem));if($selected.Count-eq1){Set-WindGuiPreset $selected[0]}})
$preset.SelectedItem=(@($presetCatalog.Presets|Where-Object {$_.Id -eq $installContext.RecommendedPreset})[0].Label)
$statusLabel=Add-WindLabel $form ("Ready on $($windowsContext.Edition) build $($windowsContext.Build). $($installContext.RecommendedPreset) was selected from installation age; review before applying.") 24 580 610 55;$statusLabel.ForeColor=[Drawing.Color]::FromArgb(60,70,80)
$review=[Windows.Forms.Button]::new();$review.Text='Review plan';$review.Location=[Drawing.Point]::new(620,575);$review.Size=[Drawing.Size]::new(95,42);$form.Controls.Add($review)
$apply=[Windows.Forms.Button]::new();$apply.Text='Apply';$apply.Location=[Drawing.Point]::new(722,575);$apply.Size=[Drawing.Size]::new(95,42);$apply.BackColor=[Drawing.Color]::FromArgb(35,105,165);$apply.ForeColor=[Drawing.Color]::White;$form.Controls.Add($apply)
$review.Add_Click({
try{$appPlan=Get-WindGuiAppPlan;$policyPlan=@(Get-WindPlan -Context (Get-WindContext) -Store (New-WindRegistryStore) -Profile ([string]$profile.SelectedItem) -Updates ([string]$updates.SelectedItem));Show-WindGuiReview (Get-WindGuiReviewText $appPlan $policyPlan)}catch{[Windows.Forms.MessageBox]::Show($_.Exception.Message,'ms-private error')|Out-Null}
})
$apply.Add_Click({
try{
$id=[Security.Principal.WindowsIdentity]::GetCurrent();$admin=[Security.Principal.WindowsPrincipal]::new($id).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator);$id.Dispose();if(-not$admin){throw 'Apply requires an elevated PowerShell started with the same Windows account.'}
$appPlan=Get-WindGuiAppPlan;$appIssues=@($appPlan|Where-Object {$_.Status -eq 'Blocked'})
$policyPlan=@(Get-WindPlan -Context (Get-WindContext) -Store (New-WindRegistryStore) -Profile ([string]$profile.SelectedItem) -Updates ([string]$updates.SelectedItem));$policyIssues=@($policyPlan|Where-Object {$_.Status -in @('Conflict','ReadError')})
$recallPlan=if($recall.Checked){Get-WindRecallPlan -RemoveFeature:$removeRecall.Checked}else{$null};$recallDisposition=Resolve-MsPrivateRecallDisposition -Requested $recall.Checked -Plan $recallPlan
$policyIds=@($policyPlan|Where-Object {$_.Status -in @('WouldChange','Configured')}|ForEach-Object Id)
Show-WindGuiReview (Get-WindGuiReviewText $appPlan $policyPlan)
$removeCount=@($appPlan|Where-Object {$_.Status -eq 'Remove'}).Count
$answer=[Windows.Forms.MessageBox]::Show("Apply this complete plan now?`r`n`r`n$removeCount applications, $($deviceList.CheckedIndices.Count) devices, account purge=$($accounts.Checked), Recall deletion=$($recall.Checked), network=$($netProfile.SelectedItem).`r`n`r`nApp/account/Recall/cache deletion is not restored by policy rollback. Maximum isolation keeps a timed recovery until separately confirmed.",'Final ms-private approval',[Windows.Forms.MessageBoxButtons]::YesNo,[Windows.Forms.MessageBoxIcon]::Warning);if($answer-ne[Windows.Forms.DialogResult]::Yes){throw 'The complete plan was not approved.'}
$tools=@();if($mullvad.Checked){$tools+='Mullvad'};if($veracrypt.Checked){$tools+='VeraCrypt'};$b=@();if($zen.Checked){$b+='Zen'};if($helium.Checked){$b+='Helium'};if($tor.Checked){$b+='Tor'};$selected=@();foreach($i in $deviceList.CheckedIndices){$selected+=@($sensorItems[[int]$i].InstanceId)}
$runState=[pscustomobject]@{NetworkJournal=$null};$groups=[Collections.Generic.List[object]]::new()
if($policyIds.Count-or$policyIssues.Count){$groups.Add([pscustomobject]@{Id='Policies';Label='Registry policy writes';DependsOn=@();Action=({if($policyIds.Count){& $invokeWindChild 'ms-private.ps1' @{Mode='Apply';Profile=[string]$profile.SelectedItem;Updates=[string]$updates.SelectedItem;Id=$policyIds;Confirm=$false}};if($policyIssues.Count){throw "$($policyIssues.Count) requested policies had a conflict or read error; eligible policies were attempted."}}.GetNewClosure())})}
if($services.Checked){$groups.Add([pscustomobject]@{Id='Services';Label='Service configuration';DependsOn=@();Action=({& $invokeWindChild 'Services.ps1' @{Mode='Apply';Profile='Strict';Confirm=$false}}.GetNewClosure())})}
if($accounts.Checked){$groups.Add([pscustomobject]@{Id='Accounts';Label='Credential and application-account purge';DependsOn=@();Action=({& $invokeWindChild 'Accounts.ps1' @{Mode='Apply';Scope='AllAppAccounts';AcknowledgeAccountDataDeletion=$true;Confirm=$false}}.GetNewClosure())})}
if($tools.Count){$groups.Add([pscustomobject]@{Id='PrivacyTools';Label='Privacy-tool installation';DependsOn=@();Action=({& $invokeWindChild 'Install-Apps.ps1' @{App=$tools;Confirm=$false}}.GetNewClosure())})}
if($b.Count-or@($appPlan | Where-Object { $_.Status -eq 'Remove' }).Count-or$appIssues.Count){$groups.Add([pscustomobject]@{Id='Applications';Label='Browser installation and exact app removal';DependsOn=@();Action=({if($appIssues.Count){throw "$($appIssues.Count) application targets were blocked or ambiguous; no app removal was attempted."};$a=@{Mode='Apply';Browser=$b;SkipBundled=(-not$bundled.Checked);KeepOneDrive=(-not$oneDrive.Checked);KeepEdge=(-not$edge.Checked);RemovePersonal=$personal.Checked;RemoveStore=$store.Checked;ReviewedPlan=$appPlan;Confirm=$false};& $invokeWindChild 'Manage-Apps.ps1' $a}.GetNewClosure())})}
if($netProfile.SelectedItem-ne'Observe'){$groups.Add([pscustomobject]@{Id='Network';Label='Firewall changes';DependsOn=@();Action=({$networkRoot=Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MsPrivate\network';[IO.Directory]::CreateDirectory($networkRoot)|Out-Null;$runState.NetworkJournal=Join-Path $networkRoot ((Get-Date -Format 'yyyyMMdd-HHmmss')+'-'+[guid]::NewGuid().ToString('N')+'.json');& $invokeWindChild 'Network.ps1' @{Mode='Apply';Profile=[string]$netProfile.SelectedItem;RollbackMinutes=[int]$rollback.Value;Journal=$runState.NetworkJournal;Confirm=$false}}.GetNewClosure())})}
if($selected.Count){$groups.Add([pscustomobject]@{Id='Sensors';Label='Exact device disablement';DependsOn=@();Action=({& $invokeWindChild 'Hardware.ps1' @{Mode='Apply';InstanceId=$selected;Confirm=$false}}.GetNewClosure())})}
if($recall.Checked-and($recallDisposition.Applicable-or$recallDisposition.Conflict)){$groups.Add([pscustomobject]@{Id='Recall';Label='Recall policy, snapshot and feature removal';DependsOn=@();Action=({if($recallDisposition.Conflict){throw 'The Recall plan contains a conflicting value.'};& $invokeWindChild 'Recall.ps1' @{Mode='Apply';AcknowledgeSnapshotDeletion=$true;RemoveFeature=[bool]$removeRecall.Checked;Confirm=$false}}.GetNewClosure())})}
if($cleanup.Checked-or$privacyCleanup.Checked){$groups.Add([pscustomobject]@{Id='Cleanup';Label='Allowlisted file cleanup';DependsOn=@();Action=({if($cleanup.Checked){& $invokeWindChild 'Cleanup.ps1' @{Mode='Run';AgeDays=7;Category=@('Temp');Confirm=$false}};if($privacyCleanup.Checked){& $invokeWindChild 'Cleanup.ps1' @{Mode='Run';AgeDays=0;Category=@('CrashReports','RecentItems','ThumbnailCache','BrowserCache','PowerShellHistory');ClearClipboard=$true;Confirm=$false}}}.GetNewClosure())})}
if($cleanupSchedule.Checked){$groups.Add([pscustomobject]@{Id='CleanupSchedule';Label='Cleanup schedule';DependsOn=@();Action=({& $invokeWindChild 'Cleanup.ps1' @{Mode='InstallSchedule';AgeDays=7;Confirm=$false}}.GetNewClosure())})}
if($lock.Checked){$vols=@($vc.Text.Split(',')|ForEach-Object{$_.Trim().ToUpperInvariant()}|Where-Object{$_});$groups.Add([pscustomobject]@{Id='LockHandler';Label='Session-lock handler';DependsOn=@();Action=({& $invokeWindChild 'Lock.ps1' @{Mode='Install';VeraCryptVolume=$vols;Confirm=$false}}.GetNewClosure())})}
$runRoot=Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MsPrivate\runs';$runPath=Join-Path $runRoot ((Get-Date -Format 'yyyyMMdd-HHmmss')+'-'+[guid]::NewGuid().ToString('N')+'.json');$statusLabel.Text='Running the approved plan...';$form.Refresh();$runResult=Invoke-MsPrivateGroupPlan -Groups @($groups) -ReportPath $runPath
$networkEntry=@($runResult.Entries | Where-Object { $_.Id -eq 'Network' });if($netProfile.SelectedItem-eq'Isolate'-and$networkEntry.Count-and$networkEntry[0].Status-eq'Succeeded'){$answer=[Windows.Forms.MessageBox]::Show("Isolation is active. Verify required connectivity and local recovery now.`r`n`r`nYes keeps the block profile and cancels timed rollback.`r`nNo restores immediately. Closing this prompt leaves the timed rollback active.`r`n`r`nJournal: $($runState.NetworkJournal)",'Confirm network isolation',[Windows.Forms.MessageBoxButtons]::YesNoCancel,[Windows.Forms.MessageBoxIcon]::Warning);if($answer-eq[Windows.Forms.DialogResult]::Yes){& $invokeWindChild 'Network.ps1' @{Mode='Confirm';Journal=$runState.NetworkJournal}}elseif($answer-eq[Windows.Forms.DialogResult]::No){& $invokeWindChild 'Network.ps1' @{Mode='Restore';Journal=$runState.NetworkJournal}}}
$failed=@($runResult.Entries | Where-Object { $_.Status -in @('Failed','SkippedDependency') })
$statusLabel.Text=if($failed.Count){"Finished with $($failed.Count) failed/skipped groups. Report: $runPath"}elseif($recallDisposition.Skipped.Count){'Completed with unsupported Recall actions skipped. Review each report and use the Home guide.'}else{'Completed. Review each generated journal/report and reboot before validation.'}
$message=$statusLabel.Text
if($failed.Count){$details=@($failed|ForEach-Object{"$($_.Label): $($_.Error)"});$message+="`r`n`r`n"+($details-join"`r`n")}
[Windows.Forms.MessageBox]::Show($message,'ms-private',[Windows.Forms.MessageBoxButtons]::OK,$(if($failed.Count){[Windows.Forms.MessageBoxIcon]::Warning}else{[Windows.Forms.MessageBoxIcon]::Information}))|Out-Null
}catch{$statusLabel.Text='Stopped: '+$_.Exception.Message;[Windows.Forms.MessageBox]::Show($_.Exception.Message,'ms-private error',[Windows.Forms.MessageBoxButtons]::OK,[Windows.Forms.MessageBoxIcon]::Error)|Out-Null}
})
[void]$form.ShowDialog()