149 lines
25 KiB
PowerShell
149 lines
25 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)
|
|
$capturePath=[Windows.Forms.TextBox]::new();$capturePath.Location=[Drawing.Point]::new(24,195);$capturePath.Width=720;$network.Controls.Add($capturePath)
|
|
Add-WindLabel $network 'Capture directory (copy this path to reopen or convert a saved capture)' 24 226 730 30|Out-Null
|
|
$stopCapture=[Windows.Forms.Button]::new();$stopCapture.Text='Stop and convert';$stopCapture.Location=[Drawing.Point]::new(285,140);$stopCapture.Size=[Drawing.Size]::new(210,38);$network.Controls.Add($stopCapture)
|
|
$convertCapture=[Windows.Forms.Button]::new();$convertCapture.Text='Convert saved capture';$convertCapture.Location=[Drawing.Point]::new(506,140);$convertCapture.Size=[Drawing.Size]::new(238,38);$network.Controls.Add($convertCapture)
|
|
$capture.Add_Click({try{$result=& $invokeWindChild 'Network.ps1' @{Mode='CaptureStart'} -PassThru;$captureResult=$result|ConvertFrom-Json;$capturePath.Text=[string]$captureResult.Directory;[Windows.Forms.MessageBox]::Show("Capture started. Use Stop and convert when finished.`r`n`r`n$($capturePath.Text)",'ms-private')|Out-Null}catch{[Windows.Forms.MessageBox]::Show($_.Exception.Message,'ms-private error')|Out-Null}})
|
|
foreach($button in @($stopCapture,$convertCapture)){$button.Tag=if($button-eq$stopCapture){'CaptureStop'}else{'CaptureConvert'};$button.Add_Click({param($sender,$eventArgs)try{if([string]::IsNullOrWhiteSpace($capturePath.Text)){throw 'Enter the capture directory first.'};& $invokeWindChild 'Network.ps1' @{Mode=[string]$sender.Tag;CaptureDirectory=$capturePath.Text};[Windows.Forms.MessageBox]::Show("Saved capture.pcapng in:`r`n$($capturePath.Text)",'ms-private')|Out-Null}catch{[Windows.Forms.MessageBox]::Show($_.Exception.Message,'ms-private error')|Out-Null}})}
|
|
Add-WindLabel $network 'Capture continues until stopped, including after this window closes. Pktmon has one system-wide session; do not use these controls while another tool owns a capture.' 24 270 730 68|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{$audit=& $invokeWindChild 'Security.ps1' @{Json=$true} -PassThru;Show-WindGuiReview -Text ($audit-join[Environment]::NewLine) -Title 'ms-private security audit'}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)try{& $invokeWindChild 'Home.ps1' @{Page=[string]$s.Tag}}catch{[Windows.Forms.MessageBox]::Show($_.Exception.Message,'ms-private error')|Out-Null}});$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,[string]$Title='ms-private exact plan')
|
|
$dialog=[Windows.Forms.Form]::new();$dialog.Text=$Title;$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)
|
|
try{[void]$dialog.ShowDialog($form)}finally{$dialog.Dispose()}
|
|
}
|
|
$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()
|