$ErrorActionPreference='Stop';$repo=Split-Path $PSScriptRoot -Parent . (Join-Path $repo 'scripts/Configuration.ps1') . (Join-Path $repo 'scripts/PowerShellLogging.ps1') $script:count=0;$script:ScriptRoot=$repo;$script:writes=0;$root=Join-Path ([IO.Path]::GetTempPath()) ('wela-pslogging-tests-'+[guid]::NewGuid().ToString('N'));$null=New-Item -ItemType Directory $root function Assert($Value,$Message){if(-not $Value){throw "FAIL: $Message"};$script:count++} function Reject([scriptblock]$Action,[string]$Pattern){$message='';try{&$Action}catch{$message=$_.Exception.Message};Assert ($message -match $Pattern) "Expected rejection $Pattern; got $message"} function CloneFixture($Value){ConvertTo-WelaPsLoggingKey $Value|ConvertFrom-Json} function Row($Path,$Values=@(),$Children=@()){[pscustomobject]@{Path=$Path;Values=@($Values);Children=@($Children);Access='original-acl'}} function Fixture { [pscustomobject][ordered]@{Operator=[pscustomobject]@{Sid='S-1-5-21-1';ImpersonationLevel='None'};Host=[pscustomobject]@{Computer='fixture';Build=20348};Engine=[pscustomobject]@{Target='Windows PowerShell 5.1'};Sources=@('sha');Machine=[pscustomobject]@{Exists=$true;Keys=@((Row '' @() @('ModuleLogging','ScriptBlockLogging','Transcription')),(Row 'ModuleLogging' @([pscustomobject]@{Name='EnableModuleLogging';Type='DWord';Value=0}) @('ModuleNames')),(Row 'ModuleLogging\ModuleNames' @([pscustomobject]@{Name='existing';Type='String';Value='Existing.Module'})),(Row 'ScriptBlockLogging' @([pscustomobject]@{Name='EnableScriptBlockLogging';Type='DWord';Value=0},[pscustomobject]@{Name='EnableScriptBlockInvocationLogging';Type='DWord';Value=1})),(Row 'Transcription' @([pscustomobject]@{Name='EnableTranscripting';Type='DWord';Value=1})))};CurrentUser=@('preserved-user');PowerShellCoreMachine=@('preserved-core');PowerShellCoreUser=@('preserved-core-user');ProtectedEventLogging=@('preserved-protected');Channel=@('preserved-channel')} } function Mutate($Before,$Definition){ $after=CloneFixture $Before;$after.Machine.Exists=$true $allowed=@('');$p='';foreach($segment in $Definition.Path.Split('\')){$p=if($p){$p+'\'+$segment}else{$segment};$allowed+=$p} foreach($path in $allowed){if(-not @($after.Machine.Keys|Where-Object Path -ieq $path).Count){$after.Machine.Keys+=Row $path}} foreach($key in $after.Machine.Keys){$key.Children=@($after.Machine.Keys|Where-Object {$_.Path -and $(if($_.Path.Contains('\')){$_.Path.Substring(0,$_.Path.LastIndexOf('\'))}else{''}) -ceq $key.Path}|ForEach-Object {$_.Path.Split('\')[-1]}|Sort-Object)} $row=@($after.Machine.Keys|Where-Object Path -ieq $Definition.Path)[0];$row.Values=@($row.Values|Where-Object Name -ine $Definition.Name)+[pscustomobject]@{Name=$Definition.Name;Type=$Definition.Type;Value=$Definition.Value};$row.Values=@($row.Values|Sort-Object Name);$after.Machine.Keys=@($after.Machine.Keys|Sort-Object Path);return $after } function Get-WelaPsLoggingSnapshot {CloneFixture $script:observed} function Set-WelaPsLoggingValue {param($Definition)$script:writes++;if($script:failWrite){throw 'injected native write failure'};$script:observed=Mutate $script:observed $Definition;if($script:corrupt){$script:observed.CurrentUser=@('changed-user')}} function Reset {$script:observed=Fixture;$script:writes=0;$script:failWrite=$false;$script:corrupt=$false} try { foreach($action in @('Plan','Configure')){Reject {Assert-WelaPsLoggingSelection $action @() @()} 'explicit'} Reject {Assert-WelaPsLoggingSelection Configure @('Module') @()} 'ModuleName' Reject {Assert-WelaPsLoggingSelection Plan @('ScriptBlock') @('x')} 'Module selection' foreach($name in @('','C:\module','Mod*','../a','a?','a\b',('x'*129))){Reject {Assert-WelaPsLoggingSelection Plan @('Module') @($name)} 'literal'} Reject {Assert-WelaPsLoggingSelection Plan @('Module','module') @('a')} 'unique' Reject {Assert-WelaPsLoggingSelection Plan @('Module') @('A','a')} 'unique' Assert-WelaPsLoggingSelection Plan @('Module') @('*');Assert $true 'Explicit all-module accepted' Assert-WelaPsLoggingSelection Audit @() @();Assert $true 'Unselected Audit accepted' $definitions=@(Get-WelaPsLoggingDefinitions @('Module','ScriptBlock') @('Microsoft.PowerShell.Utility')) Assert ($definitions.Count -eq 3 -and $definitions[0].Type -eq 'String' -and $definitions[1].Name -eq 'EnableModuleLogging' -and $definitions[2].Name -eq 'EnableScriptBlockLogging') 'Name-before-enable ordering' Reset;$before=CloneFixture $script:observed;$report=Invoke-WelaPowerShellLogging -Action Audit Assert ($report.ExitCode -eq 0 -and $script:writes -eq 0 -and $report.ReadyRuleCredit -eq 0) 'Audit is read-only with zero readiness credit' $plan=Invoke-WelaPowerShellLogging -Action Plan -Control Module,ScriptBlock -ModuleName Microsoft.PowerShell.Utility Assert ($plan.Plan.Controls.Count -eq 3 -and @($plan.Plan.Controls|Where-Object Status -eq ChangeRequired).Count -eq 3) 'Plan identifies all selected values' $dry=Invoke-WelaPowerShellLogging -Action Configure -Control Module,ScriptBlock -ModuleName Microsoft.PowerShell.Utility -DryRun -BackupPath (Join-Path $root 'dry') Assert ($dry.ExitCode -eq 0 -and $dry.Skipped -eq 3 -and $script:writes -eq 0 -and -not (Test-Path (Join-Path $root 'dry'))) 'Dry run creates no journal or write' $run=Invoke-WelaPowerShellLogging -Action Configure -Control Module,ScriptBlock -ModuleName Microsoft.PowerShell.Utility -Auto -BackupPath (Join-Path $root 'run') Assert ($run.ExitCode -eq 0 -and $script:writes -eq 3 -and @($run.Results|Where-Object Status -eq Applied).Count -eq 3) 'Three exact writes applied' $journal=@(Get-Content (Join-Path $root 'run/before.jsonl')|ForEach-Object {$_|ConvertFrom-Json}) Assert ($journal.Count -eq 3 -and (ConvertTo-WelaPsLoggingKey $journal[0].Before) -ceq (ConvertTo-WelaPsLoggingKey $before)) 'Complete original snapshot journaled before any mutation' Assert ((Get-WelaPsLoggingValue $script:observed.Machine 'ModuleLogging\ModuleNames' 'existing').Value -ceq 'Existing.Module') 'Other module names retained' Assert ((Get-WelaPsLoggingValue $script:observed.Machine 'ScriptBlockLogging' EnableScriptBlockInvocationLogging).Value -eq 1) 'Invocation logging retained' Assert ((ConvertTo-WelaPsLoggingKey $script:observed.CurrentUser) -ceq (ConvertTo-WelaPsLoggingKey $before.CurrentUser)) 'User policy retained' $again=Invoke-WelaPowerShellLogging -Action Configure -Control Module,ScriptBlock -ModuleName Microsoft.PowerShell.Utility -Auto -BackupPath (Join-Path $root 'again') Assert ($again.ExitCode -eq 0 -and $script:writes -eq 3 -and @($again.Results|Where-Object Status -eq AlreadyCompliant).Count -eq 3) 'Repeat is idempotent' Reset;$script:observed.Machine=[pscustomobject]@{Exists=$false;Keys=@()} $absent=Invoke-WelaPowerShellLogging -Action Configure -Control Module,ScriptBlock -ModuleName Microsoft.PowerShell.Utility -Auto -BackupPath (Join-Path $root 'absent') Assert ($absent.ExitCode -eq 0 -and $script:writes -eq 3) 'Absent root creates only declared ancestors' foreach($case in @(@{Type='String';Value='0'},@{Type='DWord';Value=2})){ Reset;$value=Get-WelaPsLoggingValue $script:observed.Machine 'ScriptBlockLogging' EnableScriptBlockLogging;$value.Type=$case.Type;$value.Value=$case.Value $bad=Invoke-WelaPowerShellLogging -Action Configure -Control ScriptBlock -Auto -BackupPath (Join-Path $root ([guid]::NewGuid().ToString('N'))) Assert ($bad.ExitCode -eq 1 -and $script:writes -eq 0) 'Wrong type/unknown DWORD refused before write' } Reset;$badName=Get-WelaPsLoggingValue $script:observed.Machine 'ModuleLogging\ModuleNames' existing;$badName.Type='DWord';$badName.Value=1 $bad=Invoke-WelaPowerShellLogging -Action Configure -Control Module -ModuleName Microsoft.PowerShell.Utility -Auto -BackupPath (Join-Path $root 'badname') Assert ($bad.ExitCode -eq 1 -and -not (Test-Path (Join-Path $root 'badname'))) 'Unknown module value fails entire preflight' foreach($countBefore in @(127,128)){ Reset;$row=@($script:observed.Machine.Keys|Where-Object Path -eq 'ModuleLogging\ModuleNames')[0] $row.Values=@(1..$countBefore|ForEach-Object{[pscustomobject]@{Name=('Existing'+$_);Type='String';Value=('Existing'+$_)}}) $capacityPath=Join-Path $root ('value-capacity-'+$countBefore);$captured=ConvertTo-WelaPsLoggingKey $script:observed $capacity=Invoke-WelaPowerShellLogging -Action Configure -Control Module -ModuleName NewModule -DryRun -BackupPath $capacityPath Assert (($capacity.ExitCode -eq 0) -eq ($countBefore -eq 127)) '127 names permit one addition; 128 names refuse before the first write' Assert ($script:writes -eq 0 -and -not (Test-Path $capacityPath) -and (ConvertTo-WelaPsLoggingKey $script:observed) -ceq $captured) 'Capacity preflight preserves the snapshot without writes or journals' } foreach($countBefore in @(63,64)){ Reset;$script:observed.Machine.Keys=@((Row '' @() @('ModuleLogging')),(Row 'ModuleLogging' @([pscustomobject]@{Name='EnableModuleLogging';Type='DWord';Value=0}))) foreach($index in 1..($countBefore-2)){$name='Existing'+$index;$script:observed.Machine.Keys+=Row $name;$script:observed.Machine.Keys[0].Children+=$name} $capacity=Invoke-WelaPowerShellLogging -Action Plan -Control Module -ModuleName NewModule Assert (($capacity.ExitCode -eq 0) -eq ($countBefore -eq 63)) '63 keys permit the missing selected key; 64 keys refuse predictable readback overflow' } Reset;$row=@($script:observed.Machine.Keys|Where-Object Path -eq Transcription)[0];$row.Values+=[pscustomobject]@{Name='LargeUnrelated';Type='String';Value=''} $space=1048576-(ConvertTo-WelaPsLoggingKey $script:observed.Machine).Length;$row.Values[-1].Value='x'*$space Assert ((ConvertTo-WelaPsLoggingKey $script:observed.Machine).Length -eq 1048576) 'Character-cap boundary fixture fits the current reader exactly' $capacity=Invoke-WelaPowerShellLogging -Action Configure -Control Module -ModuleName NewModule -Auto -BackupPath (Join-Path $root 'character-capacity') Assert ($capacity.ExitCode -eq 1 -and $script:writes -eq 0 -and -not (Test-Path (Join-Path $root 'character-capacity'))) 'Predictable serialized-value growth beyond character cap is refused before writes' $literalSnapshot=Fixture;$row=@($literalSnapshot.Machine.Keys|Where-Object Path -eq Transcription)[0] $row.Values+=@(1..64|ForEach-Object{[pscustomobject]@{Name=('LiteralDate'+$_);Type='String';Value='2026-09-21T00:00:00.000Z'}}) $row.Values+=[pscustomobject]@{Name='LiteralBytes';Type='Binary';Value=[byte[]]@(0,127,255)},[pscustomobject]@{Name='LiteralStrings';Type='MultiString';Value=[string[]]@('2026-09-21T00:00:00.000Z','')};$row.Values+=[pscustomobject]@{Name='LiteralFiller';Type='String';Value=''};$space=1048576-(ConvertTo-WelaPsLoggingKey $literalSnapshot.Machine).Length;$row.Values[-1].Value='x'*$space $literalBefore=ConvertTo-WelaPsLoggingKey $literalSnapshot Reject {Assert-WelaPsLoggingCapacity $literalSnapshot @(Get-WelaPsLoggingDefinitions @('Module') @('NewModule'))} 'character capacity' Assert ((ConvertTo-WelaPsLoggingKey $literalSnapshot) -ceq $literalBefore -and $row.Values[1].Value -is [string]) 'Capacity projection preserves literal ISO strings and the caller snapshot' Assert (($row.Values|Where-Object Name -eq LiteralBytes).Value -is [byte[]] -and ($row.Values|Where-Object Name -eq LiteralStrings).Value -is [string[]]) 'Projection retains typed binary and multi-string caller data' Reset;$script:failWrite=$true;$failed=Invoke-WelaPowerShellLogging -Action Configure -Control Module,ScriptBlock -ModuleName Microsoft.PowerShell.Utility -Auto -BackupPath (Join-Path $root 'writefailure') Assert ($failed.ExitCode -eq 1 -and $script:writes -eq 1 -and @($failed.Results|Where-Object Status -eq Skipped).Count -eq 2) 'Native failure stops and explicitly reports later writes' Reset;$script:corrupt=$true;$failed=Invoke-WelaPowerShellLogging -Action Configure -Control Module,ScriptBlock -ModuleName Microsoft.PowerShell.Utility -Auto -BackupPath (Join-Path $root 'corrupt') Assert ($failed.ExitCode -eq 1 -and $script:writes -eq 1) 'Preservation failure stops later writes' Reset;$snapshot=CloneFixture $script:observed;$definition=$definitions[0];$after=Mutate $snapshot $definition;Assert-WelaPsLoggingTransition $snapshot $after $definition;Assert $true 'Exact additive transition accepted' foreach($property in @('Operator','Host','Sources','CurrentUser','PowerShellCoreMachine','PowerShellCoreUser','ProtectedEventLogging','Channel')){$changed=CloneFixture $after;$changed.$property='drift';Reject {Assert-WelaPsLoggingTransition $snapshot $changed $definition} 'Unselected state'} $changed=CloneFixture $after;$changed.Machine.Keys[0].Access='new-acl';Reject {Assert-WelaPsLoggingTransition $snapshot $changed $definition} 'descriptor' $changed=CloneFixture $after;$changed.Machine.Keys=@($changed.Machine.Keys|Where-Object Path -ne Transcription);Reject {Assert-WelaPsLoggingTransition $snapshot $changed $definition} 'disappeared' $changed=CloneFixture $after;$changed.Machine.Keys+=Row 'extra';Reject {Assert-WelaPsLoggingTransition $snapshot $changed $definition} 'unrequested' $changed=CloneFixture $after;(Get-WelaPsLoggingValue $changed.Machine 'Transcription' EnableTranscripting).Value=0;Reject {Assert-WelaPsLoggingTransition $snapshot $changed $definition} 'Unrelated policy values' $changed=CloneFixture $after;$changed.Machine.Keys[0].Children+= 'extra';Reject {Assert-WelaPsLoggingTransition $snapshot $changed $definition} 'subkeys' Reset;$script:promptBefore=CloneFixture $script:observed function Read-Host {param($Prompt)$script:observed.Host.Computer='changed-during-prompt';'Y'} $drift=Invoke-WelaPowerShellLogging -Action Configure -Control ScriptBlock -BackupPath (Join-Path $root 'drift') Assert ($drift.ExitCode -eq 1 -and $script:writes -eq 0) 'Prompt-time drift refused before mutation' Remove-Item Function:\Read-Host Reset;Set-Content -LiteralPath (Join-Path $root 'existing.json') -Value 'keep';Reject {Invoke-WelaPowerShellLogging -ResultsPath (Join-Path $root 'existing.json')} 'new file';Assert ((Get-Content -Raw (Join-Path $root 'existing.json')).Trim() -ceq 'keep') 'Existing report preserved' foreach($options in @(@{Auto=$true},@{DryRun=$true},@{BackupPath='x'})){Reject {Invoke-WelaPowerShellLogging @options} 'require PowerShellLoggingAction Configure'} Write-Host "PASS: $script:count scoped PowerShell logging assertions." } finally {Remove-Item -LiteralPath $root -Recurse -Force}