Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0274e7b398 | ||
|
|
4dfec83063 | ||
|
|
cdfa0ecda8 | ||
|
|
22d06ac7fd |
No files matched your search
@@ -21,11 +21,11 @@ jobs:
|
||||
dotnet-version: 10.0.x
|
||||
- name: Build executable
|
||||
shell: powershell
|
||||
run: .\Build-Executable.ps1 -OutputDirectory .\dist\ci -Version 0.0.1
|
||||
run: .\Build-Executable.ps1 -OutputDirectory .\dist\ci -Version 0.0.5
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ms-private-0.0.1
|
||||
name: ms-private-0.0.5
|
||||
path: |
|
||||
dist/ci/ms-private-0.0.1.exe
|
||||
dist/ci/ms-private-0.0.1.exe.sha256
|
||||
dist/ci/ms-private-0.0.5.exe
|
||||
dist/ci/ms-private-0.0.5.exe.sha256
|
||||
dist/ci/SHA256SUMS.txt
|
||||
+1
-1
@@ -7,6 +7,6 @@ if($Mode-in@('Audit','Plan')){$safe=@($plan|Select-Object Id,Kind,Label,Status,I
|
||||
if(-not$AcknowledgeAccountDataDeletion){throw 'Apply requires -AcknowledgeAccountDataDeletion. App data, tokens and matching stored credentials cannot be restored by ms-private.'}
|
||||
Assert-WindMutationAllowed
|
||||
if(-not$PSCmdlet.ShouldProcess('Current-user Microsoft application accounts and matching stored credentials','Permanently sign out and delete local account data')){return}
|
||||
$root=Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MsPrivate\accounts';[IO.Directory]::CreateDirectory($root)|Out-Null;$reportPath=Join-Path $root ((Get-Date -Format 'yyyyMMdd-HHmmss')+'-'+[guid]::NewGuid().ToString('N')+'.json');$save={param($x)Save-MsPrivateAccountReport -Report $x -Path $reportPath}.GetNewClosure()
|
||||
$root=Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MsPrivate\accounts';[IO.Directory]::CreateDirectory($root)|Out-Null;$reportPath=Join-Path $root ((Get-Date -Format 'yyyyMMdd-HHmmss')+'-'+[guid]::NewGuid().ToString('N')+'.json');$reportWriter=Get-Command Save-MsPrivateAccountReport -CommandType Function;$save={param($x)& $reportWriter -Report $x -Path $reportPath}.GetNewClosure()
|
||||
try{$report=Invoke-MsPrivateAccountPurge -Scope $Scope -Backend $backend -Save $save}catch{throw "Account purge stopped after a partial failure. Inspect the redacted report at $reportPath. $($_.Exception.Message)"}
|
||||
$report|Select-Object Kind,Scope,Status,CreatedUtc|Format-List;Write-Output "Account-purge report (no account names or secrets): $reportPath";Write-Output 'Restart Windows. Existing organizational registration and the Windows logon identity were not removed.'
|
||||
@@ -1,5 +1,5 @@
|
||||
#Requires -Version 5.1
|
||||
[CmdletBinding()]param([string]$OutputDirectory,[ValidatePattern('^\d+\.\d+\.\d+$')][string]$Version='0.0.1')
|
||||
[CmdletBinding()]param([string]$OutputDirectory,[ValidatePattern('^\d+\.\d+\.\d+$')][string]$Version='0.0.5')
|
||||
Set-StrictMode -Version Latest;$ErrorActionPreference='Stop'
|
||||
if(-not$OutputDirectory){$OutputDirectory=Join-Path $PSScriptRoot 'dist'};$out=[IO.Path]::GetFullPath($OutputDirectory);[IO.Directory]::CreateDirectory($out)|Out-Null
|
||||
$seven=@((Get-Command 7z.exe -ErrorAction SilentlyContinue),(Get-Command (Join-Path $env:ProgramFiles '7-Zip\7z.exe') -ErrorAction SilentlyContinue))|Where-Object{$_}|Select-Object -First 1
|
||||
@@ -15,5 +15,6 @@ try{
|
||||
foreach($folder in @('src','config','docs')){Copy-Item -LiteralPath (Join-Path $PSScriptRoot $folder) -Destination $payload -Recurse}
|
||||
$target=Join-Path $out ("ms-private-$Version.apbx");if(Test-Path -LiteralPath $target){throw 'AME archive already exists. Use an empty output directory.'}
|
||||
Push-Location $stage;try{& $seven.Source a $target playbook.conf Executables Configuration '-pmalte'|Out-Host;if($LASTEXITCODE-ne 0){throw "7-Zip failed with exit code $LASTEXITCODE."}}finally{Pop-Location}
|
||||
$hash=(Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant();[IO.File]::WriteAllText((Join-Path $out ("ms-private-$Version.apbx.sha256")),($hash+' '+[IO.Path]::GetFileName($target)+[Environment]::NewLine),[Text.UTF8Encoding]::new($false));Write-Output "Created $target. SHA256: $hash"
|
||||
$hash=(Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant();[IO.File]::WriteAllText((Join-Path $out ("ms-private-$Version.apbx.sha256")),($hash+' '+[IO.Path]::GetFileName($target)+[Environment]::NewLine),[Text.UTF8Encoding]::new($false))
|
||||
$sumLines=@(Get-ChildItem -LiteralPath $out -File|Where-Object{$_.Extension-in@('.exe','.zip','.apbx')}|Sort-Object Name|ForEach-Object{((Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant())+' '+$_.Name});[IO.File]::WriteAllText((Join-Path $out 'SHA256SUMS.txt'),(($sumLines-join[Environment]::NewLine)+[Environment]::NewLine),[Text.UTF8Encoding]::new($false));Write-Output "Created $target. SHA256: $hash"
|
||||
}finally{if(Test-Path -LiteralPath $stage){Remove-Item -LiteralPath $stage -Recurse -Force}}
|
||||
@@ -1,5 +1,5 @@
|
||||
#Requires -Version 5.1
|
||||
[CmdletBinding()]param([string]$OutputDirectory,[string]$Version='0.0.1')
|
||||
[CmdletBinding()]param([string]$OutputDirectory,[string]$Version='0.0.5')
|
||||
Set-StrictMode -Version Latest;$ErrorActionPreference='Stop'
|
||||
if($Version-notmatch'^\d+\.\d+\.\d+$'){throw 'Version must use major.minor.patch.'}
|
||||
$root=[IO.Path]::GetFullPath($PSScriptRoot);if(-not$OutputDirectory){$OutputDirectory=Join-Path $root ('dist\ms-private-'+$Version+'-final-'+(Get-Date -Format yyyyMMdd))};$output=[IO.Path]::GetFullPath($OutputDirectory);[IO.Directory]::CreateDirectory($output)|Out-Null
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
#Requires -Version 5.1
|
||||
[CmdletBinding()]
|
||||
param([string]$OutputDirectory,[ValidatePattern('^\d+\.\d+\.\d+$')][string]$Version='0.0.1')
|
||||
param([string]$OutputDirectory,[ValidatePattern('^\d+\.\d+\.\d+$')][string]$Version='0.0.5')
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference='Stop'
|
||||
if (-not $OutputDirectory) { $OutputDirectory=Join-Path $PSScriptRoot 'dist' }
|
||||
@@ -25,5 +25,6 @@ try {
|
||||
}
|
||||
} finally {$zip.Dispose()}
|
||||
$hash=(Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
[IO.File]::WriteAllText((Join-Path $output 'SHA256SUMS.txt'),($hash+' '+[IO.Path]::GetFileName($zipPath)+[Environment]::NewLine),[Text.UTF8Encoding]::new($false))
|
||||
$sumLines=@(Get-ChildItem -LiteralPath $output -File|Where-Object{$_.Extension-in@('.exe','.zip','.apbx')}|Sort-Object Name|ForEach-Object{((Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant())+' '+$_.Name})
|
||||
[IO.File]::WriteAllText((Join-Path $output 'SHA256SUMS.txt'),(($sumLines-join[Environment]::NewLine)+[Environment]::NewLine),[Text.UTF8Encoding]::new($false))
|
||||
Write-Output "Created $zipPath ($($files.Count) source files). SHA256: $hash"
|
||||
+1
-1
@@ -5,4 +5,4 @@ if($Mode -eq 'List'){$x=@(Get-WindSensorInventory);if($Json){$x|ConvertTo-Json -
|
||||
if($Mode -eq 'Plan'){$x=@(Get-WindSensorPlan -InstanceId $InstanceId);if($Json){$x|ConvertTo-Json -Depth 5}else{$x|Format-Table -Wrap};return}
|
||||
Assert-WindMutationAllowed;$root=Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MsPrivate\hardware';[IO.Directory]::CreateDirectory($root)|Out-Null
|
||||
if($Mode-eq'Apply'){if(-not$InstanceId.Count){throw 'Apply requires at least one exact -InstanceId.'};if(-not$Journal){$Journal=Join-Path $root ((Get-Date -Format 'yyyyMMdd-HHmmss')+'-'+[guid]::NewGuid().ToString('N')+'.json')};Invoke-WindSensorApply -InstanceId $InstanceId -JournalPath $Journal -Confirm:$false;Write-Output "Hardware journal: $Journal";return}
|
||||
if(-not$Journal){throw 'Restore requires -Journal.'};$j=Get-Content -LiteralPath $Journal -Raw|ConvertFrom-Json;$save={param($x)Save-WindJournal -Journal $x -Path $Journal}.GetNewClosure();Restore-WindSensors -Journal $j -Save $save -Confirm:$false
|
||||
if(-not$Journal){throw 'Restore requires -Journal.'};$j=Get-Content -LiteralPath $Journal -Raw|ConvertFrom-Json;$journalWriter=Get-Command Save-WindJournal -CommandType Function;$save={param($x)& $journalWriter -Journal $x -Path $Journal}.GetNewClosure();Restore-WindSensors -Journal $j -Save $save -Confirm:$false
|
||||
+1
-1
@@ -40,5 +40,5 @@ if($Mode -eq 'Apply'){
|
||||
}
|
||||
if(-not $Journal){throw "$Mode requires -Journal."}
|
||||
$saved=Get-Content -LiteralPath $Journal -Raw|ConvertFrom-Json
|
||||
$save={param($x) Save-WindJournal -Journal $x -Path $Journal}.GetNewClosure()
|
||||
$journalWriter=Get-Command Save-WindJournal -CommandType Function;$save={param($x)& $journalWriter -Journal $x -Path $Journal}.GetNewClosure()
|
||||
if($Mode-eq'Confirm'){Confirm-WindNetwork -Journal $saved -Save $save|Format-List}else{Restore-WindNetwork -Journal $saved -Save $save|Format-List}
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
## Run
|
||||
|
||||
Download `ms-private-0.0.1.exe`, verify the adjacent SHA-256 file, then double-click the executable. Windows shows a UAC elevation prompt because the executable contains a `requireAdministrator` application manifest. Run it from the Windows account whose current-user apps and settings you intend to change; entering credentials for another administrator can target that account instead.
|
||||
Download `ms-private-0.0.5.exe`, verify the adjacent SHA-256 file, then double-click the executable. Windows shows a UAC elevation prompt because the executable contains a `requireAdministrator` application manifest. Run it from the Windows account whose current-user apps and settings you intend to change; entering credentials for another administrator can target that account instead.
|
||||
|
||||
The executable contains the reviewed runtime and does not fetch code. It validates the embedded manifest and every SHA-256 before extracting to `%ProgramFiles%\ms-private\runtime\<version>-<payload hash>`, validates the installed copy on every launch, repairs modified or extra runtime files, removes older runtime versions, then opens the graphical wizard with Windows PowerShell 5.1. The executable is currently unsigned, so Windows may show an unknown-publisher warning.
|
||||
|
||||
@@ -146,7 +146,7 @@ WinGet must be installed and its `winget` source usable. ms-private retains norm
|
||||
|
||||
## Support and recovery
|
||||
|
||||
- Policy apply/restore: Windows 11 Pro/Enterprise/Education families, builds 26100/26200. Home and unreviewed builds use the manual guide; the menu explicitly reports skipped policies.
|
||||
- Policy apply/restore: Windows 11 Pro/Enterprise/Education families, builds 26100/26200. On Home, seven Microsoft-documented direct registry controls remain available; managed-edition CSP policies are explicitly skipped and linked to the manual guide. Unreviewed builds remain audit-only.
|
||||
- App operations: build gate 19045/22631/26100/26200. Windows 10 requires an appropriately supported servicing arrangement. Build gating is not proof of successful live testing.
|
||||
- Domain/MDM-managed devices and non-elevated changes are refused. Use an administrator-managed deployment for those devices.
|
||||
- Policy changes save original values before writing and roll back failures. Restoration preserves later conflicting edits. Restore newer journals before older ones.
|
||||
@@ -185,6 +185,6 @@ Read the [complete tool breakdown](docs/TOOL-BREAKDOWN.md), [current roadmap](do
|
||||
.\Build-Release.ps1
|
||||
```
|
||||
|
||||
Tests use synthetic registry/app/network/service/account backends, read-only security inventory and temporary workspace files. CI runs 114 PowerShell checks on Windows PowerShell 5.1 and PowerShell 7, plus ten native launcher checks, PE inspection and the one-file publish. The source is ready for review as a **release candidate**; full live uninstall/sign-out/reboot/update/network/device validation on the supported OS matrix remains required before advertising a production-validated release. See [validation](docs/VALIDATION.md) and [contributing](CONTRIBUTING.md).
|
||||
Tests use synthetic registry/app/network/service/account backends, read-only security inventory and temporary workspace files. CI runs 120 PowerShell checks on Windows PowerShell 5.1 and PowerShell 7, plus ten native launcher checks, PE inspection and the one-file publish. The source is ready for review as a **release candidate**; full live uninstall/sign-out/reboot/update/network/device validation on the supported OS matrix remains required before advertising a production-validated release. See [validation](docs/VALIDATION.md) and [contributing](CONTRIBUTING.md).
|
||||
|
||||
`Build-Executable.ps1` creates the self-contained x64 executable and checksum. `Build-Release.ps1` writes a source ZIP and checksum, and `Build-AmePlaybook.ps1` creates the separate AME wrapper when 7-Zip is installed. Builders exclude local reports, refuse to overwrite an existing artifact and do not sign or publish it.
|
||||
+1
-1
@@ -5,4 +5,4 @@ Import-Module (Join-Path $PSScriptRoot 'src/Wind.Core.psm1');Import-Module (Join
|
||||
if($Mode -in @('Audit','Plan')){$x=Get-WindRecallPlan -RemoveFeature:$RemoveFeature;if($Json){$x|ConvertTo-Json -Depth 8}else{$x.Items|Format-Table -Wrap};return}
|
||||
Assert-WindMutationAllowed;$root=Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MsPrivate\recall';[IO.Directory]::CreateDirectory($root)|Out-Null
|
||||
if($Mode -eq 'Apply'){if(-not $Journal){$Journal=Join-Path $root ((Get-Date -Format 'yyyyMMdd-HHmmss')+'-'+[guid]::NewGuid().ToString('N')+'.json')};Invoke-WindRecallApply -RemoveFeature:$RemoveFeature -AcknowledgeSnapshotDeletion:$AcknowledgeSnapshotDeletion -JournalPath $Journal -Confirm:$false;Write-Output "Recall journal: $Journal";return}
|
||||
if(-not $Journal){throw 'Restore requires -Journal.'};$j=Get-Content -LiteralPath $Journal -Raw|ConvertFrom-Json;$save={param($x)Save-WindJournal -Journal $x -Path $Journal}.GetNewClosure();Restore-WindRecall -Journal $j -Save $save -Confirm:$false
|
||||
if(-not $Journal){throw 'Restore requires -Journal.'};$j=Get-Content -LiteralPath $Journal -Raw|ConvertFrom-Json;$journalWriter=Get-Command Save-WindJournal -CommandType Function;$save={param($x)& $journalWriter -Journal $x -Path $Journal}.GetNewClosure();Restore-WindRecall -Journal $j -Save $save -Confirm:$false
|
||||
+2
-1
@@ -7,5 +7,6 @@ Assert-WindMutationAllowed
|
||||
$root=Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MsPrivate\services';[IO.Directory]::CreateDirectory($root)|Out-Null
|
||||
if($Mode-eq'Apply'){$plan=@(Get-MsPrivateServicePlan -Profile $Profile -Backend $backend);$Journal=Join-Path $root ((Get-Date -Format 'yyyyMMdd-HHmmss')+'-'+[guid]::NewGuid().ToString('N')+'.json')}elseif([string]::IsNullOrWhiteSpace($Journal)){throw 'Restore requires -Journal.'}
|
||||
if(-not$PSCmdlet.ShouldProcess('Selected Windows privacy services',$Mode)){return}
|
||||
if($Mode-eq'Apply'){$journal=$null;$save={param($x)Save-MsPrivateServiceJournal -Journal $x -Path $Journal}.GetNewClosure();$journal=Invoke-MsPrivateServiceApply -Plan $plan -Backend $backend -Save $save}else{$journal=Get-Content -LiteralPath $Journal -Raw|ConvertFrom-Json;$save={param($x)Save-MsPrivateServiceJournal -Journal $x -Path $Journal}.GetNewClosure();$journal=Restore-MsPrivateServices -Journal $journal -Backend $backend -Save $save}
|
||||
$journalWriter=Get-Command Save-MsPrivateServiceJournal -CommandType Function;$save={param($x)& $journalWriter -Journal $x -Path $Journal}.GetNewClosure()
|
||||
if($Mode-eq'Apply'){$journal=$null;$journal=Invoke-MsPrivateServiceApply -Plan $plan -Backend $backend -Save $save}else{$journal=Get-Content -LiteralPath $Journal -Raw|ConvertFrom-Json;$journal=Restore-MsPrivateServices -Journal $journal -Backend $backend -Save $save}
|
||||
$journal|Select-Object Kind,Status,CreatedUtc|Format-List;Write-Output "Service recovery journal: $Journal"
|
||||
@@ -6,7 +6,7 @@
|
||||
<Title>ms-private</Title>
|
||||
<Description>Launches the ms-private graphical wizard. ms-private preserves Defender, Windows Firewall, Windows Update, security logs, WebView2, drivers and runtimes.</Description>
|
||||
<Details>Every selected control is previewed and applied by the same PowerShell engine used by the standalone package.</Details>
|
||||
<Version>0.0.1</Version>
|
||||
<Version>0.0.5</Version>
|
||||
<UniqueId>64d76b08-f58d-499c-aadc-1d72d620e704</UniqueId>
|
||||
<Overhaul>false</Overhaul>
|
||||
<SupportedBuilds>
|
||||
|
||||
@@ -262,6 +262,7 @@
|
||||
"Standard": 1,
|
||||
"Strict": 1,
|
||||
"EnterpriseValue": null,
|
||||
"HomeSupported": true,
|
||||
"Source": "https://learn.microsoft.com/en-us/windows/win32/wer/wer-settings",
|
||||
"Impact": "Stops Windows Error Reporting. Automatic crash analysis and solution lookup are unavailable; local security event channels are unaffected.",
|
||||
"Exceptions": []
|
||||
@@ -274,6 +275,7 @@
|
||||
"Standard": 1,
|
||||
"Strict": 1,
|
||||
"EnterpriseValue": null,
|
||||
"HomeSupported": true,
|
||||
"Source": "https://learn.microsoft.com/en-us/windows/win32/wer/wer-settings",
|
||||
"Impact": "Adds a defense-in-depth restriction on second-level WER data if another component re-enables reporting.",
|
||||
"Exceptions": []
|
||||
@@ -348,6 +350,7 @@
|
||||
"Standard": 1,
|
||||
"Strict": 1,
|
||||
"EnterpriseValue": null,
|
||||
"HomeSupported": true,
|
||||
"Source": "https://learn.microsoft.com/en-gb/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services",
|
||||
"Impact": "Restricts typing personalization collection for the current user. It does not block input observed by applications receiving keystrokes.",
|
||||
"Exceptions": []
|
||||
@@ -361,6 +364,7 @@
|
||||
"Standard": 1,
|
||||
"Strict": 1,
|
||||
"EnterpriseValue": null,
|
||||
"HomeSupported": true,
|
||||
"Source": "https://learn.microsoft.com/en-gb/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services",
|
||||
"Impact": "Restricts handwriting personalization collection for the current user; recognition and personalization quality can be reduced.",
|
||||
"Exceptions": []
|
||||
@@ -409,6 +413,7 @@
|
||||
"Standard": 0,
|
||||
"Strict": 1,
|
||||
"EnterpriseValue": null,
|
||||
"HomeSupported": true,
|
||||
"Source": "https://learn.microsoft.com/en-gb/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services",
|
||||
"Impact": "Strict stops device metadata downloads; device icons, names or companion information can be less complete.",
|
||||
"Exceptions": []
|
||||
@@ -421,6 +426,7 @@
|
||||
"Standard": 1,
|
||||
"Strict": 0,
|
||||
"EnterpriseValue": null,
|
||||
"HomeSupported": true,
|
||||
"Source": "https://learn.microsoft.com/en-gb/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services",
|
||||
"Impact": "Strict prevents Windows from downloading missing fonts; documents or sites can render with fallback fonts.",
|
||||
"Exceptions": []
|
||||
@@ -433,6 +439,7 @@
|
||||
"Standard": 1,
|
||||
"Strict": 1,
|
||||
"EnterpriseValue": null,
|
||||
"HomeSupported": true,
|
||||
"Source": "https://learn.microsoft.com/en-gb/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services",
|
||||
"Impact": "Stops MSRT infection reports while preserving the tool and its delivery through Windows Update.",
|
||||
"Exceptions": []
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
Build a Windows privacy wizard with three browser choices: Tor Browser, Zen and Helium. Its job is to reduce collection and retention, expose tradeoffs, and verify specific settings. It must never present a percentage-anonymous score or claim that a host is untraceable.
|
||||
|
||||
The current release-candidate version is 0.0.1. See [ROADMAP.md](ROADMAP.md) for the reconciled open and validation work and [NATIVE-API.md](NATIVE-API.md) for direct API and security-product behavior.
|
||||
The current release-candidate version is 0.0.5. See [ROADMAP.md](ROADMAP.md) for the reconciled open and validation work and [NATIVE-API.md](NATIVE-API.md) for direct API and security-product behavior.
|
||||
|
||||
The running Windows installation is the development host. Development and read-only inspection are authorized; this iteration creates the tool and planning artifacts without destructive OS changes or installation of applications on that host.
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# ms-private 0.0.1 roadmap
|
||||
# ms-private 0.0.5 roadmap
|
||||
|
||||
Version 0.0.1 is an unpublished release candidate. Implementation exists for the graphical and terminal interfaces, presets, policy engine, debloat, account purge, service control, network modes, Recall, sensors, cleanup, lock handling, security audit, source/AME packaging and the self-contained executable. The remaining work is mostly live acceptance, release provenance and the separately designed offline-maintenance system.
|
||||
Version 0.0.5 is a prerelease. Implementation exists for the graphical and terminal interfaces, presets, policy engine, debloat, account purge, service control, network modes, Recall, sensors, cleanup, lock handling, security audit, source/AME packaging and the self-contained executable. The remaining work is mostly live acceptance, signing provenance and the separately designed offline-maintenance system.
|
||||
|
||||
## Open work
|
||||
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
# Delivery status — 2026-09-19
|
||||
# Delivery status — 2026-09-24
|
||||
|
||||
The update architecture and implementation pass was completed on 2026-09-20. The product is named ms-private. Local OS inventory, offline bundle verification, the graphical and terminal wizards, app/debloat flows, Microsoft app-account purge, network enforcement and capture, reversible privacy-service shutdown, Recall, exact sensors, privacy-artifact cleanup, lock handling, security status and AME packaging are implemented or prepared as described below. No real package download, app removal, account purge, policy write, service mutation, firewall change, device disable, cleanup or Recall deletion was performed.
|
||||
|
||||
@@ -23,15 +23,15 @@ The supplied script was parsed without execution. Three isolated in-memory probe
|
||||
- Exact selected PnP sensor disable/restore; allowlisted Temp, crash-report, Recent/Jump List, thumbnail, PowerShell-history and expired-report cleanup; scheduling; and SessionLock clipboard/VeraCrypt handling.
|
||||
- Read-only BitLocker/device-encryption, Defender/cloud posture, firewall, Secure Boot, TPM, VBS/Credential Guard, LSA, UAC, SMB/SMB1, remote-access and protected-service status without recovery keys, account names, Defender exclusions or secrets.
|
||||
- AME wrapper source and builder that launch the same graphical wizard without disabling Defender or Windows Update.
|
||||
- Self-contained x64 `ms-private-0.0.1.exe` launcher with a `requireAdministrator` manifest, embedded hash-manifested runtime, exact-file verification, tamper repair and protected Program Files installation.
|
||||
- Self-contained x64 `ms-private-0.0.5.exe` launcher with a `requireAdministrator` manifest, embedded hash-manifested runtime, exact-file verification, tamper repair and protected Program Files installation.
|
||||
|
||||
## Verification performed
|
||||
|
||||
- 114 tests pass in PowerShell 7 and Windows PowerShell 5.1: 25 policy/transaction, 29 app/menu, 19 offline-bundle and 41 feature/package/security/service/account/preset/operation-runner checks.
|
||||
- 120 tests pass in PowerShell 7 and Windows PowerShell 5.1: 25 policy/transaction, 29 app/menu, 19 offline-bundle and 47 feature/package/security/service/account/preset/operation-runner checks. The runner writes real populated and empty reports during regression testing. GUI child-script invocation and deferred atomic writers are exercised across the same nested module boundaries used by Apply.
|
||||
- Ten native launcher tests pass for archive and installed-manifest integrity, installation validation, modified/extra-file repair, path traversal, bad hashes and undeclared entries. Managed PE inspection verifies AMD64 GUI format, the UAC manifest, version, checksum and signature state.
|
||||
- Shared journal/report create, flush, atomic-replace and parse tests passed using temporary workspace files.
|
||||
- All PowerShell source files parsed successfully during verification.
|
||||
- Real read-only service planning completed in both PowerShell engines. Policy enforcement remains audit-only on this Windows Home (`Core`) build 26200.
|
||||
- Real read-only service and policy planning completed in both PowerShell engines. On this Windows Home (`Core`) build 26200, seven documented direct registry controls are eligible and 45 managed-edition policies remain unsupported.
|
||||
- Installer previews used `-WhatIf`; no package installation occurred.
|
||||
- Live operating-system policy mutation, app uninstallation, firewall changes and telemetry suppression were not performed or claimed.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ ms-private is a local Windows privacy and debloat toolkit. It builds an exact pl
|
||||
|
||||
| Interface | Entry point | Use |
|
||||
| --- | --- | --- |
|
||||
| Single-file Windows release | `ms-private-0.0.1.exe` | Recommended launch path. Requests UAC, verifies and installs the embedded runtime, then opens the graphical wizard. |
|
||||
| Single-file Windows release | `ms-private-0.0.5.exe` | Recommended launch path. Requests UAC, verifies and installs the embedded runtime, then opens the graphical wizard. |
|
||||
| Graphical wizard | `ms-private-GUI.ps1` | Tabbed Windows Forms interface with one exact-plan review and final approval. |
|
||||
| Terminal wizard | `ms-private.ps1` | Arrow, Space, Enter and Esc navigation with searchable app selection. |
|
||||
| Complete preset CLI | `Preset.ps1` | Auto-detect or plan/apply EstablishedSafe, FreshInstall, Gentle, Medium, Strong or Maximum without navigating the UI. |
|
||||
@@ -31,7 +31,7 @@ The policy catalog contains 56 reviewed controls. Standard and Strict cover diag
|
||||
|
||||
Update choices are separate: Keep, Automatic, Notify and Manual. The update engine, Update Orchestrator, BITS, Cryptographic Services and servicing infrastructure remain installed.
|
||||
|
||||
The engine checks Windows edition and build, refuses domain or MDM-managed devices, journals original values before writing, reads back changes, rolls back partial failures and preserves later conflicting edits during restoration. Home and unreviewed builds receive an explicit unsupported result and the Settings guide.
|
||||
The engine checks Windows edition and build, refuses domain or MDM-managed devices, journals original values before writing, reads back changes, rolls back partial failures and preserves later conflicting edits during restoration. Home can apply seven direct registry controls documented independently of managed-edition CSP support; its remaining CSP policies are explicitly unsupported and linked to the Settings guide. Unreviewed builds remain audit-only.
|
||||
|
||||
## Services and GDID-related reduction
|
||||
|
||||
@@ -107,7 +107,7 @@ Reports are local and have no automatic upload. Installed-app inventories and ne
|
||||
|
||||
## Running a reviewed download
|
||||
|
||||
For a normal release, verify `ms-private-0.0.1.exe.sha256` and launch `ms-private-0.0.1.exe`. The executable is self-contained for Windows x64 and requests administrator rights through its application manifest. It embeds the source runtime, verifies the archive manifest and every file hash, extracts beneath `%ProgramFiles%\ms-private\runtime`, revalidates that copy at every launch, and repairs modifications or undeclared files. It is not code-signed yet, so the publisher warning is expected until a signing and provenance pipeline is added.
|
||||
For a normal release, verify `ms-private-0.0.5.exe.sha256` and launch `ms-private-0.0.5.exe`. The executable is self-contained for Windows x64 and requests administrator rights through its application manifest. It embeds the source runtime, verifies the archive manifest and every file hash, extracts beneath `%ProgramFiles%\ms-private\runtime`, revalidates that copy at every launch, and repairs modifications or undeclared files. It is not code-signed yet, so the publisher warning is expected until a signing and provenance pipeline is added.
|
||||
|
||||
The PowerShell implementation remains visible in the source release and inside the hash-manifested payload. Moving the policy, recovery, Appx, WinGet and firewall logic into another language would not itself improve privacy; the native launcher supplies one-file delivery, elevation and runtime integrity while retaining the tested engine.
|
||||
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
## Completed checks
|
||||
|
||||
Release-candidate update, 2026-09-22: 114 tests pass in each of Windows PowerShell 5.1 and PowerShell 7 (25 policy/transaction, 29 app/menu, 19 offline-bundle, 41 feature/package/security/service/account/preset/operation-runner). The policy tests include current-user registry scope and Enterprise-only selection. The app tests include aggressive all-removable-app and explicit Store selection, safe skipping of ambiguous desktop registrations, protection of security/dependency applications, completeness of FreshInstall/Medium/Strong/Maximum and the non-destructive EstablishedSafe preset, the reported JSON catalog failure and journal callback scope failure, browser-before-removal sequencing, installer errors, stale inventory, partial failure, bundle deduplication, group selection, keyboard navigation, filtering and terminal layout limits. Feature tests cover fresh/established/unknown install-age decisions, continued execution of independent groups after a failure, dependency skipping, preset schema refusal, shared Recall apply/skip/conflict decisions, atomic JSON replacement, malformed or duplicate policy/service/account/network catalogs, protected update-endpoint refusal, untrusted registry-hive refusal, network journal recovery, cleanup-root and protected-log refusal, recovery-journal exclusion, browser-cache scope, protected-service exclusion, service disable/restore, forged service-plan rejection, exact account-package reset, credential allowlisting, report redaction, VeraCrypt volume validation, versioned secret-free security audit behavior and AME manifest structure. Native arrow/Space/Enter/Esc browser selection was also exercised in a real Windows PowerShell console. Live host inventory, service planning and account planning were read-only. No real uninstall, account purge, policy or service write, reboot, firewall change, capture, device disable, Recall deletion or update installation was performed.
|
||||
Release-candidate update, 2026-09-24: 120 tests pass in each of Windows PowerShell 5.1 and PowerShell 7 (25 policy/transaction, 29 app/menu, 19 offline-bundle, 47 feature/package/security/service/account/preset/operation-runner). The policy tests include current-user registry scope, Home direct-control selection and managed-edition exclusion, and Enterprise-only selection. The app tests include aggressive all-removable-app and explicit Store selection, safe skipping of ambiguous desktop registrations, protection of security/dependency applications, completeness of FreshInstall/Medium/Strong/Maximum and the non-destructive EstablishedSafe preset, the reported JSON catalog failure and journal callback scope failure, browser-before-removal sequencing, installer errors, stale inventory, partial failure, bundle deduplication, group selection, keyboard navigation, filtering and terminal layout limits. Feature tests cover real populated and empty operation-runner reports, captured GUI child-script invocation and deferred atomic JSON writers across nested module boundaries, cleanup report finalization, invoker path-traversal refusal, fresh/established/unknown install-age decisions, continued execution of independent groups after a failure, dependency skipping, preset schema refusal, shared Recall apply/skip/conflict decisions, atomic JSON replacement, malformed or duplicate policy/service/account/network catalogs, protected update-endpoint refusal, untrusted registry-hive refusal, network journal recovery, cleanup-root and protected-log refusal, recovery-journal exclusion, browser-cache scope, protected-service exclusion, service disable/restore, forged service-plan rejection, exact account-package reset, credential allowlisting, report redaction, VeraCrypt volume validation, versioned secret-free security audit behavior and AME manifest structure. Native arrow/Space/Enter/Esc browser selection was also exercised in a real Windows PowerShell console. Live host inventory, service planning and account planning were read-only. No real uninstall, account purge, policy or service write, reboot, firewall change, capture, device disable, Recall deletion or update installation was performed.
|
||||
|
||||
The .NET launcher has ten passing tests for valid archive acceptance, installed payload and manifest verification, repair after modified files, rejection and repair of undeclared installed files, manifest path traversal refusal, bad-hash refusal and undeclared archive-entry refusal. `Build-Executable.ps1` must also produce exactly one x64 self-contained executable plus its checksum. Its managed PE inspection verifies AMD64 and GUI headers, the embedded `requireAdministrator` manifest, version, checksum and signature state. Before publishing, launch the GUI in a clean VM, alter and add runtime files to confirm repair, and test a Program Files access failure.
|
||||
|
||||
@@ -18,7 +18,7 @@ Run `./tests/Run-All.ps1` with both PowerShell 7 and Windows PowerShell 5.1. Thi
|
||||
|
||||
| OS | Test |
|
||||
| --- | --- |
|
||||
| Windows 11 Home 24H2/25H2 | Audit reports unsupported machine policies; Apply makes no changes; manual guide matches current Settings |
|
||||
| Windows 11 Home 24H2/25H2 | Seven documented direct registry controls apply transactionally; managed-edition policies remain unsupported; manual guide matches current Settings |
|
||||
| Windows 11 Pro 24H2/25H2 | Standard/Strict policy and service apply, readback, reboot, behavioral checks and restore |
|
||||
| Windows 11 Enterprise/Education 24H2/25H2 | Same, including actual diagnostic policy effect |
|
||||
| Unknown/future build | No automatic mutation until separately reviewed |
|
||||
|
||||
+28
-21
@@ -4,13 +4,14 @@ 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}
|
||||
function Invoke-WindChild {param([string]$Script,[hashtable]$Parameters=@{});& (Join-Path $PSScriptRoot $Script) @Parameters|Out-Host}
|
||||
$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)
|
||||
$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))
|
||||
|
||||
@@ -39,7 +40,7 @@ Add-WindLabel $network 'Observe changes nothing. Optional telemetry blocks only
|
||||
$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{Invoke-WindChild '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}})
|
||||
$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)
|
||||
@@ -51,7 +52,7 @@ $cleanupSchedule=Add-WindCheckBox $maintenance 'Install daily Temp and expired m
|
||||
$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{Invoke-WindChild '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}})
|
||||
$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)}
|
||||
@@ -75,7 +76,9 @@ function Set-WindGuiPreset {
|
||||
function Get-WindGuiReviewText {
|
||||
param([object[]]$AppPlan,[object[]]$PolicyPlan=@())
|
||||
$remove=@($AppPlan|Where-Object {$_.Status -eq 'Remove'}|Sort-Object Name)
|
||||
$lines=@("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)",'')
|
||||
$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+=''}
|
||||
@@ -97,7 +100,7 @@ $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)
|
||||
|
||||
$status=Add-WindLabel $form ("Ready. $($installContext.RecommendedPreset) was selected from installation age; review before applying.") 24 580 610 55;$status.ForeColor=[Drawing.Color]::FromArgb(60,70,80)
|
||||
$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)
|
||||
|
||||
@@ -116,20 +119,24 @@ $apply.Add_Click({
|
||||
$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){Invoke-WindChild '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=({Invoke-WindChild '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=({Invoke-WindChild '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=({Invoke-WindChild '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};Invoke-WindChild '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');Invoke-WindChild '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=({Invoke-WindChild '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.'};Invoke-WindChild '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){Invoke-WindChild 'Cleanup.ps1' @{Mode='Run';AgeDays=7;Category=@('Temp');Confirm=$false}};if($privacyCleanup.Checked){Invoke-WindChild '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=({Invoke-WindChild '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=({Invoke-WindChild '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');$progress={param($entry)$status.Text='Running: '+$entry.Label;$form.Refresh()}.GetNewClosure();$runResult=Invoke-MsPrivateGroupPlan -Groups @($groups) -OnProgress $progress -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){Invoke-WindChild 'Network.ps1' @{Mode='Confirm';Journal=$runState.NetworkJournal}}elseif($answer-eq[Windows.Forms.DialogResult]::No){Invoke-WindChild 'Network.ps1' @{Mode='Restore';Journal=$runState.NetworkJournal}}}
|
||||
$failed=@($runResult.Entries | Where-Object { $_.Status -in @('Failed','SkippedDependency') });$status.Text=if($failed.Count){"Finished with $($failed.Count) failed/skipped groups. Independent groups were attempted. 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.'};[Windows.Forms.MessageBox]::Show($status.Text,'ms-private', [Windows.Forms.MessageBoxButtons]::OK,$(if($failed.Count){[Windows.Forms.MessageBoxIcon]::Warning}else{[Windows.Forms.MessageBoxIcon]::Information}))|Out-Null
|
||||
}catch{$status.Text='Stopped: '+$_.Exception.Message;[Windows.Forms.MessageBox]::Show($_.Exception.Message,'ms-private error',[Windows.Forms.MessageBoxButtons]::OK,[Windows.Forms.MessageBoxIcon]::Error)|Out-Null}
|
||||
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()
|
||||
@@ -19,9 +19,9 @@
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AssemblyName>ms-private</AssemblyName>
|
||||
<RootNamespace>MsPrivate.Launcher</RootNamespace>
|
||||
<Version>0.0.1</Version>
|
||||
<AssemblyVersion>0.0.1.0</AssemblyVersion>
|
||||
<FileVersion>0.0.1.0</FileVersion>
|
||||
<Version>0.0.5</Version>
|
||||
<AssemblyVersion>0.0.5.0</AssemblyVersion>
|
||||
<FileVersion>0.0.5.0</FileVersion>
|
||||
<Product>ms-private</Product>
|
||||
<Description>Windows privacy control center</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2,9 +2,22 @@ Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference='Stop'
|
||||
Import-Module (Join-Path $PSScriptRoot 'MsPrivate.IO.psm1') -ErrorAction Stop
|
||||
|
||||
function New-MsPrivateScriptInvoker {
|
||||
param([Parameter(Mandatory)][string]$Root)
|
||||
$runtimeRoot=[IO.Path]::GetFullPath($Root).TrimEnd([IO.Path]::DirectorySeparatorChar)+[IO.Path]::DirectorySeparatorChar
|
||||
if(-not[IO.Directory]::Exists($runtimeRoot)){throw "Runtime root does not exist: $runtimeRoot"}
|
||||
{
|
||||
param([Parameter(Mandatory)][string]$Script,[hashtable]$Parameters=@{})
|
||||
if($Script-notmatch'^[A-Za-z0-9][A-Za-z0-9.-]*\.ps1$'){throw "Invalid runtime script name: $Script"}
|
||||
$path=[IO.Path]::GetFullPath((Join-Path $runtimeRoot $Script))
|
||||
if(-not$path.StartsWith($runtimeRoot,[StringComparison]::OrdinalIgnoreCase)-or-not[IO.File]::Exists($path)){throw "Runtime script was not found: $Script"}
|
||||
& $path @Parameters|Out-Host
|
||||
}.GetNewClosure()
|
||||
}
|
||||
|
||||
function Invoke-MsPrivateGroupPlan {
|
||||
param(
|
||||
[Parameter(Mandatory)][object[]]$Groups,
|
||||
[Parameter(Mandatory)][AllowEmptyCollection()][object[]]$Groups,
|
||||
[scriptblock]$OnProgress,
|
||||
[string]$ReportPath
|
||||
)
|
||||
@@ -17,7 +30,7 @@ function Invoke-MsPrivateGroupPlan {
|
||||
}
|
||||
$entries=@($Groups|ForEach-Object{[pscustomobject]@{Id=[string]$_.Id;Label=[string]$_.Label;DependsOn=@($_.DependsOn);Status='Pending';Error=$null}})
|
||||
$report=[pscustomobject]@{Schema=1;Kind='MsPrivateRun';CreatedUtc=[DateTime]::UtcNow.ToString('o');CompletedUtc=$null;Status='Running';Entries=$entries}
|
||||
$save={if($ReportPath){Write-MsPrivateJsonAtomic -InputObject $report -Path $ReportPath -Depth 8}}.GetNewClosure();&$save
|
||||
if($ReportPath){Write-MsPrivateJsonAtomic -InputObject $report -Path $ReportPath -Depth 8}
|
||||
foreach($group in $Groups){
|
||||
$entry=@($entries | Where-Object { $_.Id -ceq [string]$group.Id })[0]
|
||||
$blocked=@($entry.DependsOn | Where-Object {
|
||||
@@ -25,15 +38,15 @@ function Invoke-MsPrivateGroupPlan {
|
||||
$dependencyEntry=@($entries | Where-Object { $_.Id -ceq $dependency })
|
||||
$dependencyEntry.Count -ne 1 -or $dependencyEntry[0].Status -ne 'Succeeded'
|
||||
})
|
||||
if($blocked.Count){$entry.Status='SkippedDependency';$entry.Error='Required group did not succeed: '+($blocked-join', ');&$save;continue}
|
||||
$entry.Status='Running';&$save;if($OnProgress){&$OnProgress $entry}
|
||||
if($blocked.Count){$entry.Status='SkippedDependency';$entry.Error='Required group did not succeed: '+($blocked-join', ');if($ReportPath){Write-MsPrivateJsonAtomic -InputObject $report -Path $ReportPath -Depth 8};continue}
|
||||
$entry.Status='Running';if($ReportPath){Write-MsPrivateJsonAtomic -InputObject $report -Path $ReportPath -Depth 8};if($OnProgress){&$OnProgress $entry}
|
||||
try{& $group.Action|Out-Host;$entry.Status='Succeeded'}catch{$entry.Status='Failed';$entry.Error=$_.Exception.Message}
|
||||
&$save
|
||||
if($ReportPath){Write-MsPrivateJsonAtomic -InputObject $report -Path $ReportPath -Depth 8}
|
||||
}
|
||||
$failed=@($entries | Where-Object { $_.Status -eq 'Failed' }).Count
|
||||
$skipped=@($entries | Where-Object { $_.Status -eq 'SkippedDependency' }).Count
|
||||
$report.Status=if($failed-or$skipped){'PartialFailure'}else{'Completed'};$report.CompletedUtc=[DateTime]::UtcNow.ToString('o');&$save
|
||||
$report.Status=if($failed-or$skipped){'PartialFailure'}else{'Completed'};$report.CompletedUtc=[DateTime]::UtcNow.ToString('o');if($ReportPath){Write-MsPrivateJsonAtomic -InputObject $report -Path $ReportPath -Depth 8}
|
||||
$report
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Invoke-MsPrivateGroupPlan
|
||||
Export-ModuleMember -Function Invoke-MsPrivateGroupPlan,New-MsPrivateScriptInvoker
|
||||
+12
-1
@@ -38,4 +38,15 @@ function Write-MsPrivateJsonAtomic {
|
||||
}
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Write-MsPrivateJsonAtomic
|
||||
function New-MsPrivateJsonWriter {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Path,
|
||||
[ValidateRange(2,100)][int]$Depth=12
|
||||
)
|
||||
$target=[IO.Path]::GetFullPath($Path)
|
||||
$writer=Get-Command Write-MsPrivateJsonAtomic -CommandType Function
|
||||
{param($value)& $writer -InputObject $value -Path $target -Depth $Depth}.GetNewClosure()
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Write-MsPrivateJsonAtomic,New-MsPrivateJsonWriter
|
||||
@@ -88,7 +88,7 @@ function Invoke-WindCleanup {
|
||||
if($Plan.Schema-ne 2-or$Plan.AgeDays-lt 0){throw 'Invalid cleanup plan.'};$valid=@('Temp','CrashReports','RecentItems','ThumbnailCache','PowerShellHistory','BrowserCache','WindReports');if(@($Plan.Categories|Where-Object{$_-notin$valid}).Count){throw 'Cleanup plan contains an unknown category.'}
|
||||
$catalog=@(Get-WindPrivacyCleanupCatalog|Where-Object{$Plan.Categories-contains$_.Category});$cutoff=[datetime]::Parse($Plan.CutoffUtc).ToUniversalTime()
|
||||
$report=[pscustomobject]@{Schema=2;Kind='WindCleanup';CreatedUtc=[DateTime]::UtcNow.ToString('o');Categories=@($Plan.Categories);Status='Running';Deleted=0;Skipped=0;FreedBytes=[long]0;Entries=@()}
|
||||
$save={param($x)Write-MsPrivateJsonAtomic -InputObject $x -Path $ReportPath -Depth 7}.GetNewClosure();&$save $report
|
||||
$save=New-MsPrivateJsonWriter -Path $ReportPath -Depth 7;&$save $report
|
||||
foreach($entry in @($Plan.Files)){
|
||||
$path=[IO.Path]::GetFullPath([string]$entry.Path);$root=[IO.Path]::GetFullPath([string]$entry.Root).TrimEnd('\');$matches=@($catalog|Where-Object{[IO.Path]::GetFullPath([string]$_.Root).TrimEnd('\')-eq$root-and$_.Category-eq$entry.Category-and(Test-WindCleanupName ([IO.Path]::GetFileName($path)) @($_.Patterns))})
|
||||
$out=[pscustomobject]@{Category=$entry.Category;Path=$path;Status='Checking';Reason=$null};$report.Entries+=@($out);&$save $report
|
||||
|
||||
+10
-2
@@ -30,6 +30,7 @@ function Get-WindCatalog {
|
||||
if($registryPath-notmatch'^(SOFTWARE|SYSTEM)\\[A-Za-z0-9 _.-]+(?:\\[A-Za-z0-9 _.-]+)*$'-or$registryPath-match'(^|\\)\.\.?($|\\)'){throw "Invalid policy registry path: $id"}
|
||||
if($name-notmatch'^[A-Za-z0-9_]+$'-or$targets.ContainsKey($target)){throw "Invalid or duplicate policy registry target: $id"}
|
||||
foreach($profile in @('Standard','Strict')){$value=$item.$profile;if($null-eq$value){continue};if($value-isnot[ValueType]-or[double]$value-ne[int]$value-or[int]$value-lt0){throw "Invalid $profile policy value: $id"}}
|
||||
if($item.PSObject.Properties['HomeSupported']-and$item.HomeSupported-isnot[bool]){throw "HomeSupported must be a Boolean: $id"}
|
||||
if($null-ne$item.EnterpriseValue-and($item.EnterpriseValue-isnot[ValueType]-or[double]$item.EnterpriseValue-ne[int]$item.EnterpriseValue-or[int]$item.EnterpriseValue-lt0)){throw "Invalid Enterprise policy value: $id"}
|
||||
if($null-eq$item.Standard-and$null-eq$item.Strict-and$null-eq$item.EnterpriseValue-and$id-notin@('updates-auto','updates-options')){throw "Policy has no selectable value: $id"}
|
||||
$source=$null;if(-not[uri]::TryCreate([string]$item.Source,[UriKind]::Absolute,[ref]$source)-or$source.Scheme-ne'https'){throw "Policy requires an HTTPS source: $id"}
|
||||
@@ -116,6 +117,7 @@ function Get-WindPlan {
|
||||
if ($catalog.Id -notcontains $item) { throw "Unknown policy ID: $item" }
|
||||
}
|
||||
$supportedEdition = $Context.Edition -in @('Professional','ProfessionalN','ProfessionalEducation','ProfessionalEducationN','ProfessionalWorkstation','ProfessionalWorkstationN','Enterprise','EnterpriseN','Education','EducationN','EnterpriseS','EnterpriseSN')
|
||||
$homeEdition = $Context.Edition -in @('Core','CoreN','CoreSingleLanguage','CoreCountrySpecific')
|
||||
$supportedBuild = $Context.Build -in @(26100,26200)
|
||||
foreach ($policy in $catalog) {
|
||||
if ($Id.Count -gt 0 -and $Id -notcontains $policy.Id) { continue }
|
||||
@@ -138,9 +140,15 @@ function Get-WindPlan {
|
||||
$status = 'Conflict'; $reason = 'Existing per-app exceptions need review; default deny would not cover all apps.'
|
||||
}
|
||||
}
|
||||
if (-not $supportedEdition -or -not $supportedBuild) {
|
||||
$homeSupported=$policy.PSObject.Properties['HomeSupported']-and[bool]$policy.HomeSupported
|
||||
if (-not $supportedBuild) {
|
||||
$status = 'Unsupported'
|
||||
$reason = 'This edition/build is audit-only. Registry presence does not establish policy enforcement; use Guide.'
|
||||
$reason = 'This Windows build is not reviewed for policy application. Registry presence does not establish enforcement.'
|
||||
} elseif (-not $supportedEdition -and-not($homeEdition-and$homeSupported)) {
|
||||
$status = 'Unsupported'
|
||||
$reason = if($homeEdition){'Microsoft documents this policy for managed Pro/Enterprise/Education editions, not Windows Home. Use the Home guide.'}else{'This Windows edition is not reviewed for policy application.'}
|
||||
} elseif($homeEdition-and$homeSupported) {
|
||||
$reason = 'Microsoft documents this direct registry control independently of the managed-edition CSP. Behavioral verification remains required on Home.'
|
||||
}
|
||||
} catch {
|
||||
$status = 'ReadError'; $reason = 'Could not read policy state. No protection can be inferred.'
|
||||
|
||||
@@ -27,7 +27,7 @@ function Invoke-WindSensorApply {
|
||||
$plan=@(Get-WindSensorPlan -InstanceId $InstanceId);if(@($plan|Where-Object Status -eq 'AlreadyUnavailable').Count){throw 'One or more selected devices are already unavailable; select only enabled devices.'}
|
||||
if(-not $PSCmdlet.ShouldProcess('Selected PnP devices','Disable exact camera, microphone or sensor devices')){return $plan}
|
||||
$j=[pscustomobject]@{Schema=1;Kind='WindSensors';CreatedUtc=[DateTime]::UtcNow.ToString('o');Status='Prepared';Entries=@($plan|Where-Object Status -eq 'WouldDisable'|ForEach-Object{[pscustomobject]@{InstanceId=$_.InstanceId;Name=$_.Name;Status='Pending'}})}
|
||||
$save={param($x)Write-MsPrivateJsonAtomic -InputObject $x -Path $JournalPath -Depth 7}.GetNewClosure();&$save $j
|
||||
$save=New-MsPrivateJsonWriter -Path $JournalPath -Depth 7;&$save $j
|
||||
try{foreach($e in $j.Entries){$fresh=@(Get-WindSensorInventory|Where-Object InstanceId -ceq $e.InstanceId);if($fresh.Count-ne 1-or$fresh[0].Status-ne'OK'){throw "Device state changed: $($e.Name)"};$e.Status='Disabling';&$save $j;Disable-PnpDevice -InstanceId $e.InstanceId -Confirm:$false -ErrorAction Stop|Out-Null;$after=Get-PnpDevice -InstanceId $e.InstanceId -ErrorAction Stop;if($after.Status-eq'OK'){throw "Device still reports enabled after disable: $($e.Name)"};$e.Status='Disabled';&$save $j};$j.Status='Applied';&$save $j;$j}catch{$j.Status='ApplyFailed';&$save $j;throw}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ function Invoke-WindNetworkApply {
|
||||
$backup=$JournalPath+'.firewall.wfw'
|
||||
if($Profile-eq'Isolate'){&$Backend.ExportPolicy $backup}
|
||||
$journal=[pscustomobject]@{Schema=1;Kind='MsPrivateNetwork';CreatedUtc=[DateTime]::UtcNow.ToString('o');Profile=$Profile;Status='Prepared';RuleGroup=$catalog.RuleGroup;KeywordIds=@();Profiles=$before;RollbackTask=('ms-private Network Recovery '+[guid]::NewGuid().ToString('N'));BackupPath=if($Profile-eq'Isolate'){$backup}else{$null}}
|
||||
$save={param($x) Write-MsPrivateJsonAtomic -InputObject $x -Path $JournalPath -Depth 8}.GetNewClosure()
|
||||
$save=New-MsPrivateJsonWriter -Path $JournalPath -Depth 8
|
||||
& $save $journal
|
||||
try{
|
||||
if($Profile -eq 'BlockOptionalTelemetry'){
|
||||
|
||||
@@ -40,7 +40,7 @@ function Invoke-WindRecallApply {
|
||||
if(-not $PSCmdlet.ShouldProcess('Windows Recall','Disable Recall and delete existing snapshots')){return $plan}
|
||||
$s=$plan.State
|
||||
$j=[pscustomobject]@{Schema=1;Kind='WindRecall';CreatedUtc=[DateTime]::UtcNow.ToString('o');Status='Prepared';Before=[pscustomobject]@{Policy=[pscustomobject]@{Exists=$s.PolicyExists;Kind=$s.PolicyKind;Value=$s.PolicyValue};FeatureState=$s.FeatureState};RemoveFeature=[bool]$RemoveFeature;Irreversible='Existing Recall snapshots deleted by policy cannot be restored.'}
|
||||
$save={param($x)Write-MsPrivateJsonAtomic -InputObject $x -Path $JournalPath -Depth 7}.GetNewClosure();&$save $j
|
||||
$save=New-MsPrivateJsonWriter -Path $JournalPath -Depth 7;&$save $j
|
||||
try{
|
||||
Set-WindRecallPolicyState ([pscustomobject]@{Exists=$true;Value=1})
|
||||
$after=Get-WindRecallState;if(-not $after.PolicyExists -or $after.PolicyValue -ne 1){throw 'Recall policy readback failed.'}
|
||||
|
||||
+4
-2
@@ -126,7 +126,8 @@ function Get-WindSetupSnapshot {
|
||||
$options=$Settings.AppOptions
|
||||
$sensors=@();if($Settings.DisableSensors){$sensors=@(Get-WindSensorInventory|Where-Object Status -eq 'OK')}
|
||||
$recallPlan=if($Settings.Recall){Get-WindRecallPlan -RemoveFeature:$Settings.RemoveRecall}else{$null}
|
||||
[pscustomobject]@{Apps=@(Get-WindAppPlan -Inventory $inventory @options);Policies=@(Get-WindPlan -Context (Get-WindContext) -Store (New-WindRegistryStore) -Profile $Settings.Profile -Updates $Settings.Updates);Sensors=$sensors;Recall=$recallPlan}
|
||||
$context=Get-WindContext
|
||||
[pscustomobject]@{Context=$context;Apps=@(Get-WindAppPlan -Inventory $inventory @options);Policies=@(Get-WindPlan -Context $context -Store (New-WindRegistryStore) -Profile $Settings.Profile -Updates $Settings.Updates);Sensors=$sensors;Recall=$recallPlan}
|
||||
}
|
||||
function Show-WindSetupReview {
|
||||
param($Snapshot,$Settings)
|
||||
@@ -135,7 +136,8 @@ function Show-WindSetupReview {
|
||||
foreach ($a in @($Snapshot.Apps | Where-Object Status -ne 'Keep')) {$items+=New-WindMenuItem $a.Id ("[$($a.Status)] $($a.Name)") ("$($a.Scope) scope. $($a.Reason). App data may be removed by the uninstaller.") $true}
|
||||
foreach($s in @($Snapshot.Sensors)){$items+=New-WindMenuItem ('sensor:'+ $s.InstanceId) ("[WouldDisable] $($s.Kind): $($s.Name)") $s.Risk $true}
|
||||
if($Snapshot.Recall){foreach($r in $Snapshot.Recall.Items){$items+=New-WindMenuItem ('recall:'+ $r.Id) ("[$($r.Status)] $($r.Action)") $r.Impact $true}}
|
||||
Select-WindMenu -Title 'Review your plan' -Subtitle ("$($Settings.Preset) | $($Settings.Profile) | Apps: $(@($Snapshot.Apps|Where-Object Status -eq 'Remove').Count) | Sensors: $(@($Snapshot.Sensors).Count) | Network: $($Settings.Network)") -Items $items -Searchable | Out-Null
|
||||
$available=@($Snapshot.Policies|Where-Object {$_.Status-in@('WouldChange','Configured')}).Count;$unsupported=@($Snapshot.Policies|Where-Object {$_.Status-eq'Unsupported'}).Count
|
||||
Select-WindMenu -Title 'Review your plan' -Subtitle ("$($Snapshot.Context.Edition) $($Snapshot.Context.Build) | $available policies available / $unsupported unsupported | Apps: $(@($Snapshot.Apps|Where-Object Status -eq 'Remove').Count) | Network: $($Settings.Network)") -Items $items -Searchable | Out-Null
|
||||
}
|
||||
function Set-WindSetupPreset {
|
||||
param($Settings,$Preset)
|
||||
|
||||
@@ -22,6 +22,23 @@ try{
|
||||
$saved=Get-Content -LiteralPath $jsonPath -Raw|ConvertFrom-Json
|
||||
Assert-True ($saved.Value-eq2-and-not@(Get-ChildItem -LiteralPath $ioRoot -Filter '*.tmp' -Force).Count-and-not@(Get-ChildItem -LiteralPath $ioRoot -Filter '*.previous' -Force).Count) 'Atomic JSON replacement left stale state or temporary files.'
|
||||
|
||||
$probePath=Join-Path $ioRoot 'Probe.ps1';[IO.File]::WriteAllText($probePath,"param([string]`$Value) [IO.File]::WriteAllText((Join-Path `$PSScriptRoot 'probe.txt'),`$Value)",[Text.UTF8Encoding]::new($false))
|
||||
$invoker=New-MsPrivateScriptInvoker -Root $ioRoot
|
||||
$action=({& $invoker 'Probe.ps1' @{Value='nested-module'}}.GetNewClosure())
|
||||
$invokerRun=Invoke-MsPrivateGroupPlan -Groups @([pscustomobject]@{Id='Invoker';Label='Captured script invoker';DependsOn=@();Action=$action})
|
||||
Assert-True ($invokerRun.Status-eq'Completed'-and[IO.File]::ReadAllText((Join-Path $ioRoot 'probe.txt'))-eq'nested-module') 'Captured child-script invoker failed across execution-module scope.'
|
||||
$threw=$false;try{& $invoker '..\outside.ps1' @{}|Out-Null}catch{$threw=$true};Assert-True $threw 'Child-script invoker accepted path traversal.'
|
||||
|
||||
$writerPath=Join-Path $ioRoot 'captured-writer.json';$writer=New-MsPrivateJsonWriter -Path $writerPath -Depth 4
|
||||
$writerAction=({& $writer ([pscustomobject]@{Value='captured-writer'})}.GetNewClosure())
|
||||
$writerRun=Invoke-MsPrivateGroupPlan -Groups @([pscustomobject]@{Id='Writer';Label='Captured JSON writer';DependsOn=@();Action=$writerAction})
|
||||
$writerSaved=Get-Content -LiteralPath $writerPath -Raw|ConvertFrom-Json
|
||||
Assert-True ($writerRun.Status-eq'Completed'-and$writerSaved.Value-eq'captured-writer') 'Captured atomic JSON writer failed across execution-module scope.'
|
||||
|
||||
$cleanupReport=Join-Path $ioRoot 'cleanup.json';$cleanupPlan=[pscustomobject]@{Schema=2;AgeDays=7;CutoffUtc=[DateTime]::UtcNow.AddDays(-7).ToString('o');Categories=@('Temp');Files=@()}
|
||||
$cleanupResult=Invoke-WindCleanup -Plan $cleanupPlan -ReportPath $cleanupReport -Confirm:$false
|
||||
Assert-True ($cleanupResult.Status-eq'Completed'-and(Test-Path -LiteralPath $cleanupReport)) 'Cleanup could not create and finalize its atomic report.'
|
||||
|
||||
$network=Get-Content -LiteralPath (Join-Path $root 'config/network.json') -Raw|ConvertFrom-Json;$network.OptionalTelemetry[1].Fqdn=$network.OptionalTelemetry[0].Fqdn;$path=Join-Path $ioRoot 'network.json';$network|ConvertTo-Json -Depth 8|Set-Content -LiteralPath $path
|
||||
$threw=$false;try{Get-WindNetworkCatalog -Path $path|Out-Null}catch{$threw=$true};Assert-True $threw 'Network catalog accepted a duplicate FQDN.'
|
||||
$network=Get-Content -LiteralPath (Join-Path $root 'config/network.json') -Raw|ConvertFrom-Json;$network.OptionalTelemetry[0].Fqdn='ctldl.windowsupdate.com';$path=Join-Path $ioRoot 'network-protected.json';$network|ConvertTo-Json -Depth 8|Set-Content -LiteralPath $path
|
||||
@@ -70,6 +87,15 @@ $run=Invoke-MsPrivateGroupPlan -Groups $groups
|
||||
Assert-True ($run.Status-eq'PartialFailure'-and($run.Entries | Where-Object { $_.Id -eq 'First' }).Status-eq'Failed') 'Operation runner did not record a failed group.'
|
||||
Assert-True (($run.Entries | Where-Object { $_.Id -eq 'Dependent' }).Status-eq'SkippedDependency'-and$events-notcontains'dependent') 'Operation runner executed a blocked dependency.'
|
||||
Assert-True (($run.Entries | Where-Object { $_.Id -eq 'Independent' }).Status-eq'Succeeded'-and$events-contains'independent') 'Operation runner did not continue with an independent group.'
|
||||
$runReportRoot=Join-Path ([IO.Path]::GetTempPath()) ('ms-private-run-report-'+[guid]::NewGuid().ToString('N'))
|
||||
try{
|
||||
$runReportPath=Join-Path $runReportRoot 'run.json'
|
||||
$savedRun=Invoke-MsPrivateGroupPlan -Groups @([pscustomobject]@{Id='WriteReport';Label='Write report';DependsOn=@();Action={}}) -ReportPath $runReportPath
|
||||
$savedRunDisk=Get-Content -LiteralPath $runReportPath -Raw|ConvertFrom-Json
|
||||
Assert-True ($savedRun.Status-eq'Completed'-and$savedRunDisk.Status-eq'Completed'-and$savedRunDisk.Entries[0].Status-eq'Succeeded') 'Operation runner could not write and finalize a run report.'
|
||||
$emptyRunPath=Join-Path $runReportRoot 'empty.json';$emptyRun=Invoke-MsPrivateGroupPlan -Groups @() -ReportPath $emptyRunPath
|
||||
Assert-True ($emptyRun.Status-eq'Completed'-and@($emptyRun.Entries).Count-eq0-and(Test-Path -LiteralPath $emptyRunPath)) 'Empty operation plan did not complete as a clean no-op.'
|
||||
}finally{if(Test-Path -LiteralPath $runReportRoot){Remove-Item -LiteralPath $runReportRoot -Recurse -Force}}
|
||||
|
||||
$calls=[Collections.Generic.List[string]]::new()
|
||||
$backend=@{
|
||||
|
||||
+7
-5
@@ -45,12 +45,14 @@ function New-Plan { param($Harness,[string[]]$Ids=@('advertising-id','clipboard-
|
||||
function Apply-Test { param($Harness,$Plan) Invoke-WindTransaction -Plan $Plan -Context $Harness.Context -Store $Harness.Store -Save $Harness.Save -InstallationId 'test-installation' }
|
||||
function Restore-Test { param($Harness,$Journal) Restore-WindTransaction -Journal $Journal -Store $Harness.Store -Save $Harness.Save -InstallationId 'test-installation' }
|
||||
|
||||
Run-Test 'Home is unsupported even if a matching policy value exists' {
|
||||
Run-Test 'Home applies only explicitly documented direct registry controls' {
|
||||
$h=New-Harness; $h.Context.Edition='Core'
|
||||
$plan=New-Plan $h
|
||||
Assert-True (@($plan | Where-Object Status -NE 'Unsupported').Count -eq 0) 'Home policies must not be called enforced.'
|
||||
Assert-Throws { Apply-Test $h $plan }
|
||||
Assert-True ($h.State.Writes -eq 0 -and $h.State.Saves -eq 0) 'Unsupported apply touched state.'
|
||||
$plan=@(Get-WindPlan -Context $h.Context -Store $h.Store -Id @('advertising-id','implicit-text-collection','windows-error-reporting'))
|
||||
Assert-True (($plan|Where-Object Id -EQ 'advertising-id').Status-eq'Unsupported') 'Managed-edition policy was offered on Home.'
|
||||
$eligible=@($plan|Where-Object Status -EQ 'WouldChange')
|
||||
Assert-True ($eligible.Id-contains'implicit-text-collection'-and$eligible.Id-contains'windows-error-reporting') 'Documented direct registry controls were not offered on Home.'
|
||||
$j=Apply-Test $h $eligible
|
||||
Assert-True ($j.Status-eq'Applied'-and$h.State.Writes-eq2) 'Home direct registry controls were not applied transactionally.'
|
||||
}
|
||||
Run-Test 'Unknown build is audit-only' {
|
||||
$h=New-Harness; $h.Context.Build=99999
|
||||
|
||||
Reference in new issue
Block a user