Prepare ms-private 0.0.1 release candidate
This commit is contained in:
1 parent
7aacc0b63f
commit
a515538438
75 files changed
+3957
-3484
No files matched your search
@@ -0,0 +1,68 @@
|
||||
#Requires -Version 5.1
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)][ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})][string]$Path,
|
||||
[Parameter(Mandatory)][ValidatePattern('^\d+\.\d+\.\d+$')][string]$Version
|
||||
)
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference='Stop'
|
||||
$resolved=[IO.Path]::GetFullPath($Path)
|
||||
$bytes=[IO.File]::ReadAllBytes($resolved)
|
||||
if($bytes.Length-lt 1024){throw 'Executable is unexpectedly small.'}
|
||||
if($bytes[0]-ne0x4d-or$bytes[1]-ne0x5a){throw 'DOS header is missing.'}
|
||||
$pe=[BitConverter]::ToInt32($bytes,0x3c)
|
||||
if($pe-lt0-or($pe+96)-ge$bytes.Length-or[BitConverter]::ToUInt32($bytes,$pe)-ne0x00004550){throw 'PE header is invalid.'}
|
||||
if([BitConverter]::ToUInt16($bytes,$pe+4)-ne0x8664){throw 'Executable is not AMD64.'}
|
||||
$optional=$pe+24
|
||||
if([BitConverter]::ToUInt16($bytes,$optional)-ne0x20b){throw 'Executable is not PE32+.'}
|
||||
if([BitConverter]::ToUInt16($bytes,$optional+68)-ne2){throw 'Executable is not a Windows GUI application.'}
|
||||
|
||||
function Read-U16([byte[]]$Data,[int]$Offset){[BitConverter]::ToUInt16($Data,$Offset)}
|
||||
function Read-U32([byte[]]$Data,[int]$Offset){[BitConverter]::ToUInt32($Data,$Offset)}
|
||||
function Convert-Rva([byte[]]$Data,[int]$SectionTable,[int]$SectionCount,[uint32]$Rva){
|
||||
for($index=0;$index-lt$SectionCount;$index++){
|
||||
$section=$SectionTable+($index*40);$virtualSize=Read-U32 $Data ($section+8);$virtualAddress=Read-U32 $Data ($section+12);$rawSize=Read-U32 $Data ($section+16);$raw=Read-U32 $Data ($section+20)
|
||||
$span=[Math]::Max([long]$virtualSize,[long]$rawSize)
|
||||
if([long]$Rva-ge[long]$virtualAddress-and[long]$Rva-lt([long]$virtualAddress+$span)){return [int]([long]$raw+([long]$Rva-[long]$virtualAddress))}
|
||||
}
|
||||
throw 'Resource RVA is outside the PE sections.'
|
||||
}
|
||||
function Get-ResourceEntries([byte[]]$Data,[int]$Directory){
|
||||
$count=(Read-U16 $Data ($Directory+12))+(Read-U16 $Data ($Directory+14));$rows=@()
|
||||
for($index=0;$index-lt$count;$index++){$offset=$Directory+16+($index*8);$rows+=,[pscustomobject]@{Name=Read-U32 $Data $offset;Target=Read-U32 $Data ($offset+4)}}
|
||||
$rows
|
||||
}
|
||||
function Get-IdEntry([byte[]]$Data,[int]$Directory,[uint32]$Id){
|
||||
$match=@(Get-ResourceEntries $Data $Directory|Where-Object{([int64]$_.Name-band0x80000000L)-eq0-and($_.Name-band0xffff)-eq$Id})
|
||||
if($match.Count-ne1){throw "PE resource ID $Id was not found exactly once."};$match[0]
|
||||
}
|
||||
$sectionCount=Read-U16 $bytes ($pe+6);$optionalSize=Read-U16 $bytes ($pe+20);$sectionTable=$optional+$optionalSize
|
||||
$resourceRva=Read-U32 $bytes ($optional+128)
|
||||
if($resourceRva-eq0){throw 'PE resource table is missing.'}
|
||||
$resourceBase=Convert-Rva $bytes $sectionTable $sectionCount $resourceRva
|
||||
$type=Get-IdEntry $bytes $resourceBase 24
|
||||
if(([int64]$type.Target-band0x80000000L)-eq0){throw 'Manifest type does not reference a resource directory.'}
|
||||
$nameDirectory=$resourceBase+[int]([int64]$type.Target-band0x7fffffffL)
|
||||
$name=Get-IdEntry $bytes $nameDirectory 1
|
||||
if(([int64]$name.Target-band0x80000000L)-eq0){throw 'Manifest name does not reference a language directory.'}
|
||||
$languageDirectory=$resourceBase+[int]([int64]$name.Target-band0x7fffffffL)
|
||||
$languages=@(Get-ResourceEntries $bytes $languageDirectory)
|
||||
if($languages.Count-lt1-or([int64]$languages[0].Target-band0x80000000L)-ne0){throw 'Manifest language entry is invalid.'}
|
||||
$dataEntry=$resourceBase+[int]([int64]$languages[0].Target-band0x7fffffffL)
|
||||
$manifestRva=Read-U32 $bytes $dataEntry;$manifestSize=Read-U32 $bytes ($dataEntry+4)
|
||||
$manifestOffset=Convert-Rva $bytes $sectionTable $sectionCount $manifestRva
|
||||
if($manifestSize-eq0-or([long]$manifestOffset+$manifestSize)-gt$bytes.Length){throw 'Embedded application manifest is invalid.'}
|
||||
$manifestBytes=[byte[]]::new($manifestSize);[Array]::Copy($bytes,$manifestOffset,$manifestBytes,0,$manifestSize)
|
||||
$manifest=[Text.Encoding]::UTF8.GetString($manifestBytes)
|
||||
if($manifest-notmatch'requestedExecutionLevel\s+level="requireAdministrator"'){throw 'Executable does not request administrator rights.'}
|
||||
|
||||
$info=[Diagnostics.FileVersionInfo]::GetVersionInfo($resolved)
|
||||
if($info.FileVersion-notlike"$Version*"){throw "Unexpected file version: $($info.FileVersion)"}
|
||||
$checksumPath=$resolved+'.sha256'
|
||||
if(-not(Test-Path -LiteralPath $checksumPath -PathType Leaf)){throw 'Adjacent SHA-256 file is missing.'}
|
||||
$expected=(([IO.File]::ReadAllText($checksumPath)-split'\s+')[0]).ToLowerInvariant()
|
||||
$actual=(Get-FileHash -LiteralPath $resolved -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
if($expected-ne$actual){throw 'Executable SHA-256 does not match the adjacent checksum.'}
|
||||
$signature=Get-AuthenticodeSignature -LiteralPath $resolved
|
||||
if($signature.Status-notin@('NotSigned','Valid')){throw "Unexpected Authenticode status: $($signature.Status)"}
|
||||
Write-Output "Executable inspection passed: AMD64 GUI, requireAdministrator, version $Version, SHA-256 $actual, signature $($signature.Status)."
|
||||
@@ -0,0 +1,12 @@
|
||||
#Requires -Version 5.1
|
||||
$ErrorActionPreference='Stop'
|
||||
$root=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||
$files=@(Get-ChildItem -LiteralPath $root -Filter '*.ps1' -File)
|
||||
foreach ($directory in @('src','tests')) {$files+=@(Get-ChildItem -LiteralPath (Join-Path $root $directory) -File | Where-Object Extension -in @('.ps1','.psm1'))}
|
||||
foreach ($file in $files) {
|
||||
$tokens=$null;$errors=$null
|
||||
[System.Management.Automation.Language.Parser]::ParseFile($file.FullName,[ref]$tokens,[ref]$errors) | Out-Null
|
||||
if ($errors.Count) {throw ($errors | Out-String)}
|
||||
}
|
||||
foreach ($suite in @('Run-Tests.ps1','Run-AppTests.ps1','Run-OfflineUpdateTests.ps1','Run-FeatureTests.ps1')) {& (Join-Path $PSScriptRoot $suite)}
|
||||
Write-Output 'All checks passed. No live policy or application mutations were performed.'
|
||||
+102
-5
@@ -3,6 +3,7 @@ Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference='Stop'
|
||||
Import-Module (Join-Path $PSScriptRoot '../src/Wind.Apps.psm1') -Force
|
||||
Import-Module (Join-Path $PSScriptRoot '../src/Wind.Tui.psm1') -Force
|
||||
Import-Module (Join-Path $PSScriptRoot '../src/MsPrivate.Presets.psm1') -Force
|
||||
$script:passed=0
|
||||
function Assert-True { param([bool]$Condition,[string]$Message) if (-not $Condition) { throw $Message } }
|
||||
function Assert-Throws { param([scriptblock]$Action) $threw=$false; try { & $Action | Out-Null } catch { $threw=$true }; Assert-True $threw 'Expected an exception.' }
|
||||
@@ -37,6 +38,29 @@ Run-Test 'Personal app opt-in and bundled exclusions' {
|
||||
Assert-True (($plan | Where-Object Id -eq 'editor').Status -eq 'Remove') 'Personal selection ignored.'
|
||||
Assert-True (($plan | Where-Object Id -eq 'app-1').Status -eq 'Keep') 'Exclusion ignored.'
|
||||
}
|
||||
Run-Test 'Aggressive presets select removable personal apps and Store' {
|
||||
$store=New-App 'Microsoft.WindowsStore' 'Appx' 'store';$store.Protected=$true
|
||||
$apps=@((New-App 'PersonalEditor' 'Desktop' 'editor'),(New-App 'Microsoft.WindowsNotepad' 'Appx' 'notepad'),$store,(New-App 'Bitdefender' 'Desktop' 'security'),(New-App 'Microsoft Edge WebView2 Runtime' 'Desktop' 'webview'))
|
||||
$plan=@(Get-WindAppPlan -Inventory $apps -RemovePersonal -RemoveStore)
|
||||
Assert-True (($plan|Where-Object {$_.Id -eq 'editor'}).Status-eq'Remove') 'Aggressive plan kept a removable personal app.'
|
||||
Assert-True (($plan|Where-Object {$_.Id -eq 'store'}).Status-eq'Remove') 'Explicit Store removal was ignored.'
|
||||
Assert-True (($plan|Where-Object {$_.Id -eq 'notepad'}).Status-eq'Remove') 'Removable Microsoft.Windows app was overprotected.'
|
||||
Assert-True (@($plan|Where-Object{$_.Id-in@('security','webview')-and$_.Status-ne'Keep'}).Count-eq0) 'Aggressive plan selected a protected dependency or security product.'
|
||||
}
|
||||
Run-Test 'Aggressive presets skip ambiguous desktop registrations without blocking the run' {
|
||||
$apps=@(New-App 'Duplicate Tool' 'Desktop' 'dup-a';New-App 'Duplicate Tool' 'Desktop' 'dup-b')
|
||||
$plan=@(Get-WindAppPlan -Inventory $apps -RemovePersonal)
|
||||
Assert-True (@($plan|Where-Object {$_.Status -ne 'Keep'}).Count-eq0) 'An ambiguous automatic selection was left actionable.'
|
||||
Assert-True (@($plan|Where-Object {$_.Protected -and $_.Reason -like 'Skipped:*'}).Count-eq2) 'Ambiguous entries were not reported as protected skips.'
|
||||
}
|
||||
Run-Test 'Fresh Install and aggressive presets carry the complete destructive selection' {
|
||||
$catalog=Get-MsPrivatePresetCatalog
|
||||
foreach($id in @('FreshInstall','Medium','Strong','Maximum')){$p=Get-MsPrivatePreset $id;Assert-True ($p.RemovePersonal-and$p.RemoveStore-and$p.RemoveOneDrive-and$p.RemoveEdge-and$p.Recall-and$p.Accounts-and$p.Services) "Incomplete aggressive preset: $id"}
|
||||
Assert-True ((Get-MsPrivatePreset 'Maximum').Network-eq'Isolate') 'Maximum preset lacks timed network isolation.'
|
||||
Assert-True (@($catalog.Presets).Count-eq6) 'Unexpected preset count.'
|
||||
$safe=Get-MsPrivatePreset 'EstablishedSafe'
|
||||
Assert-True (-not$safe.Services-and-not$safe.Accounts-and-not$safe.RemoveBundled-and-not$safe.RemovePersonal-and-not$safe.RemoveStore-and-not$safe.RemoveOneDrive-and-not$safe.RemoveEdge-and-not$safe.Recall-and$safe.Network-eq'Observe'-and-not@($safe.Browsers).Count) 'EstablishedSafe selected a destructive or installation action.'
|
||||
}
|
||||
Run-Test 'Protected dependencies cannot be opted into removal' {
|
||||
foreach ($name in @('Microsoft Edge WebView2 Runtime','Microsoft.DesktopAppInstaller','Microsoft.WindowsStore','Microsoft.VCLibs.140.00','Bitdefender','Microsoft.GamingServices','NVIDIA Display Driver','Mullvad VPN','VeraCrypt')) {
|
||||
$app=New-App $name
|
||||
@@ -116,10 +140,83 @@ Run-Test 'Forged protected plan is rejected before any action' {
|
||||
Assert-Throws {Run-Changes $h}
|
||||
Assert-True ($h.State.Events.Count -eq 0) 'Forged plan executed.'
|
||||
}
|
||||
Run-Test 'Menu selection rejects invalid input and supports combinations' {
|
||||
Assert-True ((@(ConvertFrom-WindChoice '1,3,1' 3) -join ',') -eq '0,2') 'Multi-selection incorrect.'
|
||||
Assert-True (@(ConvertFrom-WindChoice 'none' 3).Count -eq 0) 'None incorrect.'
|
||||
Assert-True (@(ConvertFrom-WindChoice 'all' 3).Count -eq 3) 'All incorrect.'
|
||||
foreach ($bad in @('0','4','-1','1;whoami','1.5')) {Assert-Throws {ConvertFrom-WindChoice $bad 3}}
|
||||
Run-Test 'Arrow navigation wraps and Space toggles independently of Enter' {
|
||||
$items=@((New-WindMenuItem 'zen' 'Zen'),(New-WindMenuItem 'tor' 'Tor'))
|
||||
$s=New-WindMenuState @('zen')
|
||||
Update-WindMenuState $s $items 'UpArrow' -Multiple
|
||||
Assert-True ($s.Cursor -eq 1) 'Up did not wrap.'
|
||||
Update-WindMenuState $s $items 'Spacebar' -Multiple
|
||||
Assert-True ($s.Checked.Count -eq 2 -and $s.Action -eq '') 'Space accepted prematurely.'
|
||||
Update-WindMenuState $s $items 'Enter' -Multiple
|
||||
Assert-True ($s.Action -eq 'Accept') 'Enter did not accept.'
|
||||
Update-WindMenuState $s $items 'Escape' -Multiple
|
||||
Assert-True ($s.Action -eq 'Cancel') 'Escape did not cancel.'
|
||||
}
|
||||
Run-Test 'Filtering preserves checked apps and supports empty results' {
|
||||
$items=@((New-WindMenuItem 'zen' 'Zen'),(New-WindMenuItem 'tor' 'Tor'))
|
||||
$s=New-WindMenuState @('zen')
|
||||
Update-WindMenuState $s $items 'T' ([char]'t') -Multiple -Searchable
|
||||
Assert-True (@(Get-WindVisibleItems $items $s).Count -eq 1) 'Filter did not narrow list.'
|
||||
Update-WindMenuState $s $items 'Spacebar' -Multiple
|
||||
Assert-True ($s.Checked.Count -eq 2) 'Filter lost prior selections.'
|
||||
Update-WindMenuState $s $items 'X' ([char]'x') -Searchable
|
||||
Update-WindMenuState $s $items 'Enter'
|
||||
Assert-True ($s.Action -eq '') 'Empty result accepted.'
|
||||
Update-WindMenuState $s $items 'Backspace' -Searchable
|
||||
Assert-True ($s.Filter -eq 't') 'Backspace failed.'
|
||||
}
|
||||
Run-Test 'Protected display rows never toggle or activate' {
|
||||
$items=@(New-WindMenuItem 'protected' 'System component' '' $true);$s=New-WindMenuState
|
||||
Update-WindMenuState $s $items 'Spacebar' -Multiple
|
||||
Update-WindMenuState $s $items 'Enter'
|
||||
Assert-True ($s.Checked.Count -eq 0 -and $s.Action -eq '') 'Disabled item activated.'
|
||||
}
|
||||
Run-Test 'Frames fit small terminals and page long app lists' {
|
||||
$items=@(1..80 | ForEach-Object {New-WindMenuItem "app-$_" ('Long application name '+('x'*80)) ('Detailed information '*20)})
|
||||
$s=New-WindMenuState; $s.Cursor=79
|
||||
foreach ($width in @(40,80,120)) {
|
||||
$frame=@(Get-WindMenuFrame -Title 'Apps' -Subtitle 'Select' -Items $items -State $s -Width $width -Height 24 -Multiple -Searchable)
|
||||
Assert-True ($frame.Count -lt 24 -and @($frame | Where-Object {$_.Text.Length -ge $width}).Count -eq 0) 'Frame overflow.'
|
||||
Assert-True (@($frame | Where-Object Active).Count -eq 1) 'Focus lost off-page.'
|
||||
}
|
||||
}
|
||||
Run-Test 'Empty inventory can still install selected browsers' {
|
||||
$h=New-Harness @();$r=Run-Changes $h
|
||||
Assert-True ($r.Status -eq 'Completed' -and $h.State.Browser) 'Empty inventory broke install.'
|
||||
}
|
||||
Run-Test 'JSON catalogs flatten correctly in Windows PowerShell 5.1' {
|
||||
$browsers=@(Get-WindBrowserCatalog)
|
||||
Assert-True ($browsers.Count -eq 5 -and @($browsers | Where-Object Name -eq 'Zen').Count -eq 1) 'Nested catalog array.'
|
||||
}
|
||||
Run-Test 'Bundle parents suppress duplicate child app rows' {
|
||||
$packages=@([pscustomobject]@{PackageFamilyName='family';IsBundle=$true;Name='parent'},[pscustomobject]@{PackageFamilyName='family';IsBundle=$false;Name='child'},[pscustomobject]@{PackageFamilyName='other';IsBundle=$false;Name='single'})
|
||||
$parents=@(Select-WindAppxParents $packages)
|
||||
Assert-True ($parents.Count -eq 2 -and $parents.Name -notcontains 'child') 'Bundle child duplicated.'
|
||||
}
|
||||
Run-Test 'Journal callback resolves inside nested module scopes' {
|
||||
$root=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||
$path=Join-Path $root ('reports/scope-'+[guid]::NewGuid().ToString('N')+'.json')
|
||||
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($path)) | Out-Null
|
||||
$nested=New-Module -ScriptBlock {}
|
||||
try {
|
||||
$h=New-Harness
|
||||
& $nested {
|
||||
param($Root,$Path,$Harness)
|
||||
Import-Module (Join-Path $Root 'src/Wind.Core.psm1')
|
||||
Import-Module (Join-Path $Root 'src/Wind.Apps.psm1')
|
||||
$writer=Get-Command Save-WindJournal -CommandType Function
|
||||
$save={param($report) & $writer -Journal $report -Path $Path}.GetNewClosure()
|
||||
Invoke-WindAppChanges -Plan $Harness.Plan -Backend $Harness.Backend -Save $save -Confirm:$false | Out-Null
|
||||
} $root $path $h
|
||||
Assert-True ((Get-Content -LiteralPath $path -Raw | ConvertFrom-Json).Status -eq 'Completed') 'Nested journal writer failed.'
|
||||
} finally {if ([IO.File]::Exists($path)) {[IO.File]::Delete($path)};Remove-Module $nested -ErrorAction SilentlyContinue}
|
||||
}
|
||||
Run-Test 'Group changes override prior group checkboxes while preserving personal choices' {
|
||||
$inventory=@((New-App),(New-App 'PersonalEditor' 'Desktop' 'editor'))
|
||||
$options=@{ExtraId=@('app-1','editor');ExcludeId=@();SkipBundled=$false;KeepOneDrive=$false;KeepEdge=$false}
|
||||
Set-WindRemovalGroups -Options $options -Inventory $inventory -Bundled $false -OneDrive $true -Edge $true
|
||||
$plan=@(Get-WindAppPlan -Inventory $inventory @options)
|
||||
Assert-True (($plan | Where-Object Id -eq 'app-1').Status -eq 'Keep') 'Group removal toggle ignored.'
|
||||
Assert-True (($plan | Where-Object Id -eq 'editor').Status -eq 'Remove') 'Personal choice lost.'
|
||||
}
|
||||
Write-Output "$script:passed app/menu tests passed. Only synthetic inventories and backends were used."
|
||||
@@ -0,0 +1,135 @@
|
||||
#Requires -Version 5.1
|
||||
$ErrorActionPreference='Stop';Set-StrictMode -Version Latest
|
||||
$root=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||
Import-Module (Join-Path $root 'src/Wind.Network.psm1') -Force
|
||||
Import-Module (Join-Path $root 'src/Wind.Cleanup.psm1') -Force
|
||||
Import-Module (Join-Path $root 'src/Wind.Lock.psm1') -Force
|
||||
Import-Module (Join-Path $root 'src/Wind.Security.psm1') -Force
|
||||
Import-Module (Join-Path $root 'src/MsPrivate.Services.psm1') -Force
|
||||
Import-Module (Join-Path $root 'src/MsPrivate.Accounts.psm1') -Force
|
||||
Import-Module (Join-Path $root 'src/Wind.Core.psm1') -Force
|
||||
Import-Module (Join-Path $root 'src/MsPrivate.IO.psm1') -Force
|
||||
Import-Module (Join-Path $root 'src/MsPrivate.Presets.psm1') -Force
|
||||
Import-Module (Join-Path $root 'src/MsPrivate.Execution.psm1') -Force
|
||||
$script:passed=0
|
||||
function Assert-True([bool]$Condition,[string]$Message){if(-not$Condition){throw $Message};$script:passed++}
|
||||
|
||||
$ioRoot=Join-Path ([IO.Path]::GetTempPath()) ('ms-private-feature-'+[guid]::NewGuid().ToString('N'))
|
||||
try{
|
||||
$jsonPath=Join-Path $ioRoot 'state.json'
|
||||
Write-MsPrivateJsonAtomic -InputObject ([pscustomobject]@{Value=1}) -Path $jsonPath
|
||||
Write-MsPrivateJsonAtomic -InputObject ([pscustomobject]@{Value=2}) -Path $jsonPath
|
||||
$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.'
|
||||
|
||||
$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
|
||||
$threw=$false;try{Get-WindNetworkCatalog -Path $path|Out-Null}catch{$threw=$true};Assert-True $threw 'Network catalog accepted a protected Windows Update endpoint.'
|
||||
|
||||
$services=Get-Content -LiteralPath (Join-Path $root 'config/services.json') -Raw|ConvertFrom-Json;$services.Services[1].Id=$services.Services[0].Id;$path=Join-Path $ioRoot 'services.json';$services|ConvertTo-Json -Depth 8|Set-Content -LiteralPath $path
|
||||
$threw=$false;try{Get-MsPrivateServiceCatalog -Path $path|Out-Null}catch{$threw=$true};Assert-True $threw 'Service catalog accepted a duplicate ID.'
|
||||
|
||||
$accounts=Get-Content -LiteralPath (Join-Path $root 'config/accounts.json') -Raw|ConvertFrom-Json;$accounts.CredentialTargetPatterns=@($accounts.CredentialTargetPatterns)+@('[');$path=Join-Path $ioRoot 'accounts.json';$accounts|ConvertTo-Json -Depth 8|Set-Content -LiteralPath $path
|
||||
$threw=$false;try{Get-MsPrivateAccountCatalog -Path $path|Out-Null}catch{$threw=$true};Assert-True $threw 'Account catalog accepted an invalid credential pattern.'
|
||||
|
||||
$policies=@(Get-WindCatalog);$policies[1].Id=$policies[0].Id;$path=Join-Path $ioRoot 'policies.json';$policies|ConvertTo-Json -Depth 8|Set-Content -LiteralPath $path
|
||||
$threw=$false;try{Get-WindCatalog -Path $path|Out-Null}catch{$threw=$true};Assert-True $threw 'Policy catalog accepted a duplicate ID.'
|
||||
$policies=@(Get-WindCatalog);$policies[0]|Add-Member -NotePropertyName Hive -NotePropertyValue 'Users' -Force;$path=Join-Path $ioRoot 'policies-hive.json';$policies|ConvertTo-Json -Depth 8|Set-Content -LiteralPath $path
|
||||
$threw=$false;try{Get-WindCatalog -Path $path|Out-Null}catch{$threw=$true};Assert-True $threw 'Policy catalog accepted an untrusted registry hive.'
|
||||
$presets=Get-Content -LiteralPath (Join-Path $root 'config/presets.json') -Raw|ConvertFrom-Json;$presets.Presets[1].RemoveStore=$false;$path=Join-Path $ioRoot 'presets.json';$presets|ConvertTo-Json -Depth 8|Set-Content -LiteralPath $path
|
||||
$threw=$false;try{Get-MsPrivatePresetCatalog -Path $path|Out-Null}catch{$threw=$true};Assert-True $threw 'Preset catalog accepted an incomplete aggressive preset.'
|
||||
|
||||
$now=[datetime]'2026-09-22T12:00:00Z'
|
||||
$fresh=Get-MsPrivateInstallContext -NowUtc $now -Probe {[pscustomobject]@{InstallDate=$now.AddDays(-2);ProfileCreationUtc=$now.AddDays(-1)}}
|
||||
Assert-True ($fresh.Classification-eq'FreshInstall'-and$fresh.RecommendedPreset-eq'FreshInstall') 'A fresh OS and profile did not select FreshInstall.'
|
||||
$upgraded=Get-MsPrivateInstallContext -NowUtc $now -Probe {[pscustomobject]@{InstallDate=$now.AddDays(-2);ProfileCreationUtc=$now.AddDays(-100)}}
|
||||
Assert-True ($upgraded.Classification-eq'Established'-and$upgraded.RecommendedPreset-eq'EstablishedSafe') 'An older profile after a recent OS date was misclassified as fresh.'
|
||||
$unknown=Get-MsPrivateInstallContext -NowUtc $now -Probe {throw 'unavailable'}
|
||||
Assert-True ($unknown.Classification-eq'Unknown'-and$unknown.RecommendedPreset-eq'EstablishedSafe') 'Unknown installation age did not fail safe.'
|
||||
}finally{if(Test-Path -LiteralPath $ioRoot){Remove-Item -LiteralPath $ioRoot -Recurse -Force}}
|
||||
|
||||
$observe=Get-WindNetworkPlan -Profile Observe
|
||||
Assert-True ($observe.Items.Count-eq 1-and$observe.Items[0].Status-eq'Ready') 'Observe plan must be non-mutating.'
|
||||
$threw=$false;try{Get-WindNetworkPlan -Profile Observe -RollbackMinutes 1|Out-Null}catch{$threw=$true};Assert-True $threw 'Network rollback lower bound was not enforced.'
|
||||
|
||||
$supportedRecall=Resolve-MsPrivateRecallDisposition -Requested $true -Plan ([pscustomobject]@{Items=@([pscustomobject]@{Id='recall-policy';Status='WouldChange'},[pscustomobject]@{Id='recall-feature';Status='Configured'})})
|
||||
Assert-True ($supportedRecall.Applicable-and-not$supportedRecall.Conflict-and-not$supportedRecall.Skipped.Count) 'Supported Recall plan was not marked applicable.'
|
||||
$unsupportedRecall=Resolve-MsPrivateRecallDisposition -Requested $true -Plan ([pscustomobject]@{Items=@([pscustomobject]@{Id='recall-policy';Status='Unsupported'})})
|
||||
Assert-True (-not$unsupportedRecall.Applicable-and-not$unsupportedRecall.Conflict-and$unsupportedRecall.Skipped.Count-eq1) 'Unsupported Recall action was not reported as skipped.'
|
||||
$conflictingRecall=Resolve-MsPrivateRecallDisposition -Requested $true -Plan ([pscustomobject]@{Items=@([pscustomobject]@{Id='recall-policy';Status='Conflict'})})
|
||||
Assert-True (-not$conflictingRecall.Applicable-and$conflictingRecall.Conflict) 'Conflicting Recall plan did not remain a hard stop.'
|
||||
|
||||
$events=[Collections.Generic.List[string]]::new()
|
||||
$groups=@(
|
||||
[pscustomobject]@{Id='First';Label='First';DependsOn=@();Action=({$events.Add('first')|Out-Null;throw 'expected failure'}.GetNewClosure())},
|
||||
[pscustomobject]@{Id='Dependent';Label='Dependent';DependsOn=@('First');Action=({$events.Add('dependent')|Out-Null}.GetNewClosure())},
|
||||
[pscustomobject]@{Id='Independent';Label='Independent';DependsOn=@();Action=({$events.Add('independent')|Out-Null}.GetNewClosure())}
|
||||
)
|
||||
$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.'
|
||||
|
||||
$calls=[Collections.Generic.List[string]]::new()
|
||||
$backend=@{
|
||||
ReadProfiles={@([pscustomobject]@{Name='Domain';DefaultOutboundAction='Allow'},[pscustomobject]@{Name='Private';DefaultOutboundAction='Allow'},[pscustomobject]@{Name='Public';DefaultOutboundAction='Allow'})}
|
||||
SetOutbound={param($name,$action)$calls.Add("set:${name}:$action")}
|
||||
RemoveRules={param($g)$calls.Add("rules:$g")}
|
||||
RemoveKeyword={param($id)$calls.Add("keyword:$id")}
|
||||
RemoveRollback={param($n)$calls.Add("task:$n")}
|
||||
}
|
||||
$j=[pscustomobject]@{Schema=1;Kind='MsPrivateNetwork';Status='Applied';Profile='Isolate';RuleGroup='ms-private';KeywordIds=@();RollbackTask='ms-private Network Recovery 0123456789abcdef0123456789abcdef';Profiles=@([pscustomobject]@{Name='Domain';DefaultOutboundAction='Allow'},[pscustomobject]@{Name='Private';DefaultOutboundAction='Allow'},[pscustomobject]@{Name='Public';DefaultOutboundAction='Allow'})}
|
||||
Restore-WindNetwork -Journal $j -Backend $backend -Save {param($x)}|Out-Null
|
||||
Assert-True ($calls.Count-eq 4) 'Isolation restore did not restore three profiles and remove the task.'
|
||||
|
||||
Assert-True (Test-WindVeraCryptVolume 'X:') 'Valid VeraCrypt drive rejected.'
|
||||
Assert-True (-not(Test-WindVeraCryptVolume 'C:\Windows')) 'Unsafe VeraCrypt target accepted.'
|
||||
$threw=$false;try{Get-WindCleanupPlan -Root @((Join-Path $env:USERPROFILE 'Documents'))|Out-Null}catch{$threw=$true};Assert-True $threw 'Cleanup accepted a non-temp root.'
|
||||
$cleanupCatalog=@(Get-WindPrivacyCleanupCatalog)
|
||||
Assert-True (-not@($cleanupCatalog|Where-Object{$_.Root-match'(?i)winevt|System32\\LogFiles|Defender'}).Count) 'Cleanup catalog included protected event/security logs.'
|
||||
Assert-True (-not@($cleanupCatalog|Where-Object{$_.Category-eq'WindReports'-and$_.Patterns-contains'*.journal.json'}).Count) 'Cleanup catalog included recovery journals.'
|
||||
$browserPlan=Get-WindCleanupPlan -AgeDays 0 -Category BrowserCache
|
||||
Assert-True ($browserPlan.Categories-contains'BrowserCache') 'Browser-cache cleanup category was not accepted.'
|
||||
$browserRoots=@(Get-WindBrowserCacheCatalog);Assert-True (-not@($browserRoots|Where-Object{$_.Root-notmatch'(?i)\\(cache2|startupCache|Cache|Code Cache|GPUCache|DawnCache|GrShaderCache|ShaderCache|CacheStorage)$'}).Count) 'Browser cleanup included a non-cache profile path.'
|
||||
$security=Get-WindSecurityStatus;Assert-True ($security.RecoveryKeysCaptured-eq$false) 'Security audit must never capture recovery keys.'
|
||||
Assert-True ($security.SensitiveValuesCaptured-eq$false-and$security.Schema-eq2) 'Expanded security audit must remain secret-free and versioned.'
|
||||
|
||||
$serviceCatalog=Get-MsPrivateServiceCatalog
|
||||
Assert-True (-not@($serviceCatalog.Services|Where-Object{$_.Name-in@($serviceCatalog.ProtectedServices)}).Count) 'Protected security/update service entered the privacy disable catalog.'
|
||||
$serviceStates=@{DiagTrack=[pscustomobject]@{Exists=$true;StartExists=$true;Start=2;FlagsExists=$false;Flags=$null}}
|
||||
$serviceCalls=[Collections.Generic.List[string]]::new();$serviceBackend=@{
|
||||
ReadTemplate={param($name)if($serviceStates.ContainsKey($name)){$serviceStates[$name]}else{[pscustomobject]@{Exists=$false;StartExists=$false;Start=$null;FlagsExists=$false;Flags=$null}}}
|
||||
WriteTemplate={param($name,$state)$serviceStates[$name]=[pscustomobject]@{Exists=$state.Exists;StartExists=$state.StartExists;Start=$state.Start;FlagsExists=$state.FlagsExists;Flags=$state.Flags};$serviceCalls.Add("write:${name}:$($state.Start)")}
|
||||
ReadInstances={param($name,$instances)if($name-eq'DiagTrack'){@([pscustomobject]@{Name='DiagTrack';Status='Running'})}else{@()}}
|
||||
StopInstance={param($name)$serviceCalls.Add("stop:$name")};StartInstance={param($name)$serviceCalls.Add("start:$name")}
|
||||
}
|
||||
$servicePlan=@(Get-MsPrivateServicePlan -Profile Strict -Backend $serviceBackend);$serviceJournal=Invoke-MsPrivateServiceApply -Plan $servicePlan -Backend $serviceBackend -Save {param($x)}
|
||||
Assert-True ($serviceStates.DiagTrack.Start-eq4-and$serviceCalls-contains'stop:DiagTrack') 'Strict service plan did not disable and stop the telemetry service.'
|
||||
$restored=Restore-MsPrivateServices -Journal $serviceJournal -Backend $serviceBackend -Save {param($x)}
|
||||
Assert-True ($serviceStates.DiagTrack.Start-eq2-and$restored.Status-eq'Restored') 'Service journal did not restore original startup state.'
|
||||
$forged=@(Get-MsPrivateServicePlan -Profile Strict -Backend $serviceBackend);$forged[0].Name='wuauserv';$threw=$false
|
||||
try{Invoke-MsPrivateServiceApply -Plan $forged -Backend $serviceBackend -Save {param($x)}|Out-Null}catch{$threw=$true}
|
||||
Assert-True $threw 'Service engine accepted a forged protected-service name.'
|
||||
|
||||
$accountCalls=[Collections.Generic.List[string]]::new();$accountBackend=@{
|
||||
ListPackages={@([pscustomobject]@{Name='Microsoft.WindowsStore';PackageFullName='store_full'},[pscustomobject]@{Name='Microsoft.AAD.BrokerPlugin';PackageFullName='broker_full'})}
|
||||
ResetPackage={param($name)$accountCalls.Add("reset:$name")}
|
||||
ListCredentials={@([pscustomobject]@{TargetName='MicrosoftAccount:[email protected]';Type=[uint32]1},[pscustomobject]@{TargetName='unrelated-server';Type=[uint32]1})}
|
||||
DeleteCredential={param($target,$type)$accountCalls.Add("delete:${target}:$type")}
|
||||
}
|
||||
$consumerPlan=@(Get-MsPrivateAccountPlan -Scope Consumer -Backend $accountBackend)
|
||||
Assert-True (-not @($consumerPlan|Where-Object {$_.Id -eq 'web-account-broker'}).Count) 'Consumer account scope included the shared identity broker.'
|
||||
$accountReport=Invoke-MsPrivateAccountPurge -Scope AllAppAccounts -Backend $accountBackend -Save {param($x)}
|
||||
Assert-True ($accountReport.Status-eq'Completed'-and$accountCalls-contains'reset:store_full'-and$accountCalls-contains'reset:broker_full') 'Account purge did not reset exact installed package identities.'
|
||||
Assert-True (@($accountCalls|Where-Object{$_-like'delete:MicrosoftAccount:*'}).Count-eq1-and-not@($accountCalls|Where-Object{$_-like'*unrelated-server*'}).Count) 'Account purge deleted outside its credential allowlist.'
|
||||
Assert-True (-not@($accountReport.Entries|Where-Object{$_.PSObject.Properties['Key']}).Count) 'Account report retained a credential target.'
|
||||
$accountReportPath=Join-Path ([IO.Path]::GetTempPath()) ('ms-private-account-test-'+[guid]::NewGuid().ToString('N')+'.json')
|
||||
try{Save-MsPrivateAccountReport -Report $accountReport -Path $accountReportPath;Save-MsPrivateAccountReport -Report $accountReport -Path $accountReportPath;$roundTrip=Get-Content -LiteralPath $accountReportPath -Raw|ConvertFrom-Json;Assert-True ($roundTrip.Status-eq'Completed') 'Account report atomic replacement failed.'}finally{[IO.File]::Delete($accountReportPath)}
|
||||
|
||||
[xml]$ame=Get-Content -LiteralPath (Join-Path $root 'ame/src/playbook.conf') -Raw
|
||||
Assert-True ($ame.Playbook.Overhaul-eq'false') 'AME wrapper must not claim an OS overhaul.'
|
||||
Assert-True (-not($ame.Playbook.Requirements.Requirement -contains 'DefenderDisabled')) 'AME wrapper must preserve Defender.'
|
||||
$yaml=Get-Content -LiteralPath (Join-Path $root 'ame/src/Configuration/main.yml') -Raw
|
||||
Assert-True ($yaml -match 'ms-private\\\\Launch\.cmd') 'AME launcher action missing.'
|
||||
Write-Output "$script:passed feature tests passed. No OS setting was changed."
|
||||
+29
-10
@@ -13,7 +13,8 @@ function New-Harness {
|
||||
Read = {
|
||||
param($policy)
|
||||
if ($state.ReadFail) { throw 'Injected read error' }
|
||||
$key=$policy.Path+'|'+$policy.Name
|
||||
$hive=if($policy.PSObject.Properties['Hive']){[string]$policy.Hive}else{'LocalMachine'}
|
||||
$key=$hive+'|'+$policy.Path+'|'+$policy.Name
|
||||
if ($state.Values.ContainsKey($key)) {
|
||||
$v=$state.Values[$key]
|
||||
return [pscustomobject]@{ Exists=$true; Kind=$v.Kind; Value=$v.Value }
|
||||
@@ -25,7 +26,8 @@ function New-Harness {
|
||||
$state.Writes++
|
||||
$fail=$state.FailName -eq $policy.Name
|
||||
if ($fail -and -not $state.FailAfterWrite) { $state.FailName=''; throw 'Injected write error' }
|
||||
$key=$policy.Path+'|'+$policy.Name
|
||||
$hive=if($policy.PSObject.Properties['Hive']){[string]$policy.Hive}else{'LocalMachine'}
|
||||
$key=$hive+'|'+$policy.Path+'|'+$policy.Name
|
||||
if ($value.Exists) { $state.Values[$key]=[pscustomobject]@{ Kind=$value.Kind; Value=$value.Value } }
|
||||
else { $state.Values.Remove($key) }
|
||||
if ($fail) { $state.FailName=''; throw 'Injected error after write' }
|
||||
@@ -60,6 +62,13 @@ Run-Test 'Edition-aware diagnostic level' {
|
||||
$h.Context.Edition='Enterprise'
|
||||
Assert-True ((New-Plan $h @('diagnostics'))[0].Desired -eq 0) 'Enterprise should use supported minimum.'
|
||||
}
|
||||
Run-Test 'User-scoped and edition-scoped policies are planned honestly' {
|
||||
$h=New-Harness
|
||||
Assert-True ((New-Plan $h @('tailored-experiences'))[0].Desired -eq 1) 'Current-user tailored-experience policy missing.'
|
||||
Assert-True (@(New-Plan $h @('windows-spotlight')).Count -eq 0) 'Pro must not claim the Enterprise-only Spotlight policy.'
|
||||
$h.Context.Edition='Enterprise'
|
||||
Assert-True ((New-Plan $h @('windows-spotlight'))[0].Desired -eq 1) 'Enterprise Spotlight policy was not selected.'
|
||||
}
|
||||
Run-Test 'Update choices are explicit and preserve update services' {
|
||||
$h=New-Harness
|
||||
$keep=@(Get-WindPlan -Context $h.Context -Store $h.Store)
|
||||
@@ -74,13 +83,13 @@ Run-Test 'Unknown ID fails instead of silently ignoring a typo' {
|
||||
}
|
||||
Run-Test 'Non-DWORD pre-existing value is never overwritten' {
|
||||
$h=New-Harness; $p=Get-WindCatalog | Where-Object Id -EQ 'advertising-id'
|
||||
$h.State.Values[$p.Path+'|'+$p.Name]=[pscustomobject]@{ Kind='String'; Value='custom' }
|
||||
$h.State.Values['LocalMachine|'+$p.Path+'|'+$p.Name]=[pscustomobject]@{ Kind='String'; Value='custom' }
|
||||
Assert-Throws { Apply-Test $h (New-Plan $h) }
|
||||
Assert-True ($h.State.Writes -eq 0) 'Conflicting type was overwritten.'
|
||||
}
|
||||
Run-Test 'Microphone per-app overrides block misleading default-deny claim' {
|
||||
$h=New-Harness; $p=Get-WindCatalog | Where-Object Id -EQ 'microphone-apps'
|
||||
$h.State.Values[$p.Path+'|LetAppsAccessMicrophone_ForceAllowTheseApps']=[pscustomobject]@{ Kind='MultiString'; Value=@('example.app') }
|
||||
$h.State.Values['LocalMachine|'+$p.Path+'|LetAppsAccessMicrophone_ForceAllowTheseApps']=[pscustomobject]@{ Kind='MultiString'; Value=@('example.app') }
|
||||
Assert-True ((New-Plan $h @('microphone-apps'))[0].Status -eq 'Conflict') 'Exception was missed.'
|
||||
}
|
||||
Run-Test 'Unreadable registry is a read error, not missing or configured' {
|
||||
@@ -89,12 +98,12 @@ Run-Test 'Unreadable registry is a read error, not missing or configured' {
|
||||
}
|
||||
Run-Test 'Apply and restore preserve absence and arbitrary original DWORD' {
|
||||
$h=New-Harness; $p=Get-WindCatalog | Where-Object Id -EQ 'advertising-id'
|
||||
$h.State.Values[$p.Path+'|'+$p.Name]=[pscustomobject]@{ Kind='DWord'; Value=7 }
|
||||
$h.State.Values['LocalMachine|'+$p.Path+'|'+$p.Name]=[pscustomobject]@{ Kind='DWord'; Value=7 }
|
||||
$j=Apply-Test $h (New-Plan $h)
|
||||
Assert-True ($j.Status -eq 'Applied' -and $h.State.Values.Count -eq 2) 'Apply incomplete.'
|
||||
$r=Restore-Test $h ($j | ConvertTo-Json -Depth 12 | ConvertFrom-Json)
|
||||
Assert-True ($r.Status -eq 'Restored' -and $h.State.Values.Count -eq 1) 'Restore did not preserve absence.'
|
||||
Assert-True ($h.State.Values[$p.Path+'|'+$p.Name].Value -eq 7) 'Original value lost.'
|
||||
Assert-True ($h.State.Values['LocalMachine|'+$p.Path+'|'+$p.Name].Value -eq 7) 'Original value lost.'
|
||||
}
|
||||
Run-Test 'Reapplying configured policies makes zero registry writes' {
|
||||
$h=New-Harness; Apply-Test $h (New-Plan $h) | Out-Null; $count=$h.State.Writes
|
||||
@@ -119,10 +128,10 @@ Run-Test 'Failure after mutation also rolls back the current setting' {
|
||||
Run-Test 'External edits cause restore conflict and are preserved' {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h)
|
||||
$p=Get-WindCatalog | Where-Object Id -EQ 'advertising-id'
|
||||
$h.State.Values[$p.Path+'|'+$p.Name].Value=9
|
||||
$h.State.Values['LocalMachine|'+$p.Path+'|'+$p.Name].Value=9
|
||||
$r=Restore-Test $h $j
|
||||
Assert-True ($r.Status -eq 'RestoreIncomplete') 'Conflict not reported.'
|
||||
Assert-True ($h.State.Values[$p.Path+'|'+$p.Name].Value -eq 9) 'External edit overwritten.'
|
||||
Assert-True ($h.State.Values['LocalMachine|'+$p.Path+'|'+$p.Name].Value -eq 9) 'External edit overwritten.'
|
||||
}
|
||||
Run-Test 'Crash with Writing journal status remains recoverable' {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h); $j.Entries[0].Status='Writing'
|
||||
@@ -153,9 +162,9 @@ Run-Test 'Journal rejects wrong installation and invalid target' {
|
||||
}
|
||||
Run-Test 'State change between planning and apply is not overwritten' {
|
||||
$h=New-Harness; $plan=New-Plan $h; $p=Get-WindCatalog | Where-Object Id -EQ 'advertising-id'
|
||||
$h.State.Values[$p.Path+'|'+$p.Name]=[pscustomobject]@{ Kind='DWord'; Value=88 }
|
||||
$h.State.Values['LocalMachine|'+$p.Path+'|'+$p.Name]=[pscustomobject]@{ Kind='DWord'; Value=88 }
|
||||
Assert-Throws { Apply-Test $h $plan }
|
||||
Assert-True ($h.State.Writes -eq 0 -and $h.State.Values[$p.Path+'|'+$p.Name].Value -eq 88) 'Stale plan wrote registry.'
|
||||
Assert-True ($h.State.Writes -eq 0 -and $h.State.Values['LocalMachine|'+$p.Path+'|'+$p.Name].Value -eq 88) 'Stale plan wrote registry.'
|
||||
}
|
||||
Run-Test 'Journal failure after a successful write triggers recovery' {
|
||||
$h=New-Harness; $h.State.FailSaveAt=3
|
||||
@@ -184,4 +193,14 @@ Run-Test 'Durable journal creation and replacement round-trip' {
|
||||
}
|
||||
}
|
||||
|
||||
Run-Test 'Switching Strict to Standard releases strict-only restrictions' {
|
||||
$h=New-Harness
|
||||
$strict=@(Get-WindPlan -Context $h.Context -Store $h.Store -Profile Strict)
|
||||
Apply-Test $h $strict | Out-Null
|
||||
$standard=@(Get-WindPlan -Context $h.Context -Store $h.Store -Profile Standard)
|
||||
Assert-True (($standard | Where-Object Id -eq 'clipboard-history').Desired -eq 1) 'Standard left history disabled.'
|
||||
Assert-True (($standard | Where-Object Id -eq 'delivery-optimization').Desired -eq 0) 'Standard retained strict DO mode.'
|
||||
Apply-Test $h $standard | Out-Null
|
||||
Assert-True (@(Get-WindPlan -Context $h.Context -Store $h.Store -Profile Standard | Where-Object Status -ne 'Configured').Count -eq 0) 'Mode change incomplete.'
|
||||
}
|
||||
Write-Output "$script:passed tests passed. No Windows policy settings were modified."
|
||||
Reference in new issue
Block a user