Fix deferred writers and release checksums
Windows tests / test-and-build (push) Canceled after 0s

This commit is contained in:
0x000 committed 2026-09-24 13:55:18 -04:00
1 parent 4dfec83063
commit 0274e7b398
24 files changed
+60 -34

No files matched your search

+4 -4
View File
@@ -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.4
run: .\Build-Executable.ps1 -OutputDirectory .\dist\ci -Version 0.0.5
- uses: actions/upload-artifact@v4
with:
name: ms-private-0.0.4
name: ms-private-0.0.5
path: |
dist/ci/ms-private-0.0.4.exe
dist/ci/ms-private-0.0.4.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
View File
@@ -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.'
+3 -2
View File
@@ -1,5 +1,5 @@
#Requires -Version 5.1
[CmdletBinding()]param([string]$OutputDirectory,[ValidatePattern('^\d+\.\d+\.\d+$')][string]$Version='0.0.4')
[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 -1
View File
@@ -1,5 +1,5 @@
#Requires -Version 5.1
[CmdletBinding()]param([string]$OutputDirectory,[string]$Version='0.0.4')
[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
View File
@@ -1,6 +1,6 @@
#Requires -Version 5.1
[CmdletBinding()]
param([string]$OutputDirectory,[ValidatePattern('^\d+\.\d+\.\d+$')][string]$Version='0.0.4')
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
View File
@@ -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
View File
@@ -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}
+2 -2
View File
@@ -23,7 +23,7 @@
## Run
Download `ms-private-0.0.4.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.
@@ -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 117 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
View File
@@ -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
View File
@@ -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"
+1 -1
View File
@@ -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.4</Version>
<Version>0.0.5</Version>
<UniqueId>64d76b08-f58d-499c-aadc-1d72d620e704</UniqueId>
<Overhaul>false</Overhaul>
<SupportedBuilds>
+1 -1
View File
@@ -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.4. 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
View File
@@ -1,6 +1,6 @@
# ms-private 0.0.4 roadmap
# ms-private 0.0.5 roadmap
Version 0.0.4 is a published 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.
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
+2 -2
View File
@@ -23,11 +23,11 @@ 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.4.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
- 117 tests pass in PowerShell 7 and Windows PowerShell 5.1: 25 policy/transaction, 29 app/menu, 19 offline-bundle and 44 feature/package/security/service/account/preset/operation-runner checks. The runner writes a real report path during regression testing, and the GUI child-script invoker is exercised across the same nested module boundary used by Apply.
- 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.
+2 -2
View File
@@ -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.4.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. |
@@ -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.4.exe.sha256` and launch `ms-private-0.0.4.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.
+1 -1
View File
@@ -2,7 +2,7 @@
## Completed checks
Release-candidate update, 2026-09-24: 117 tests pass in each of Windows PowerShell 5.1 and PowerShell 7 (25 policy/transaction, 29 app/menu, 19 offline-bundle, 44 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 operation-runner report creation/finalization, captured GUI child-script invocation across the nested execution-module boundary, 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.
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.
@@ -19,9 +19,9 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
<AssemblyName>ms-private</AssemblyName>
<RootNamespace>MsPrivate.Launcher</RootNamespace>
<Version>0.0.4</Version>
<AssemblyVersion>0.0.4.0</AssemblyVersion>
<FileVersion>0.0.4.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>
+1 -1
View File
@@ -17,7 +17,7 @@ function New-MsPrivateScriptInvoker {
function Invoke-MsPrivateGroupPlan {
param(
[Parameter(Mandatory)][object[]]$Groups,
[Parameter(Mandatory)][AllowEmptyCollection()][object[]]$Groups,
[scriptblock]$OnProgress,
[string]$ReportPath
)
+12 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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}
}
+1 -1
View File
@@ -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'){
+1 -1
View File
@@ -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.'}
+12
View File
@@ -29,6 +29,16 @@ try{
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
@@ -83,6 +93,8 @@ try{
$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()