mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-11-01 18:58:16 +01:00
Merge dev and preserve independent recovery and WEC commands
This commit is contained in:
commit
e068bd8f52
31 files changed
+1563
-14
No files matched your search
@@ -1,5 +1,6 @@
|
||||
# Mocked registry/audit policy; no Windows policy changes.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Import-Module (Join-Path $PSScriptRoot '../modules/AuditProfiles.psm1') -Force
|
||||
. (Join-Path $PSScriptRoot '../scripts/Configuration.ps1')
|
||||
$script:assertions = 0
|
||||
$script:paths = @()
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = Split-Path $PSScriptRoot -Parent
|
||||
$script:ScriptRoot = $repo
|
||||
Import-Module (Join-Path $repo 'modules/AuditProfiles.psm1') -Force
|
||||
. (Join-Path $repo 'scripts/Configuration.ps1')
|
||||
$script:checks=0
|
||||
function Assert($Condition,[string]$Message) { if (-not $Condition) { throw "FAIL: $Message" }; $script:checks++ }
|
||||
function Rule([string]$Enabled='True',[string]$Inbound='Require',[string]$Outbound='Request',[string]$Health='OK') {
|
||||
[pscustomobject]@{Name='owned';Enabled=$Enabled;InboundSecurity=$Inbound;OutboundSecurity=$Outbound;PrimaryStatus=$Health}
|
||||
}
|
||||
$script:rule=Rule
|
||||
$positive=Get-WelaIpsecPrerequisite -ReadRules {$script:rule} -ReadAssociations {}
|
||||
Assert ($positive.Status -eq 'Applicable' -and $positive.Rules[0].Qualifies) 'healthy effective securing rule qualifies'
|
||||
$none=Get-WelaIpsecPrerequisite -ReadRules {} -ReadAssociations {}
|
||||
Assert ($none.Status -eq 'NotObservedWithinScope' -and $none.Limitations -match 'legacy IPsec') 'empty complete inventory is scope-limited absence'
|
||||
foreach ($candidate in @((Rule False),(Rule False Require Request Inactive),(Rule True None None))) {
|
||||
$script:rule=$candidate
|
||||
$evidence=Get-WelaIpsecPrerequisite -ReadRules {$script:rule} -ReadAssociations {}
|
||||
Assert ($evidence.Status -eq 'NotObservedWithinScope' -and -not $evidence.Rules[0].Qualifies) 'disabled and exemption-only policies do not qualify'
|
||||
}
|
||||
foreach ($candidate in @((Rule True Require Request Error),(Rule True Require Request Unknown),(Rule True Require Request Inactive),(Rule Maybe),([pscustomobject]@{Name='missing'}))) {
|
||||
$script:rule=$candidate
|
||||
Assert ((Get-WelaIpsecPrerequisite -ReadRules {$script:rule} -ReadAssociations {}).Status -eq 'Unknown') 'invalid or unhealthy policy stays unknown'
|
||||
}
|
||||
$script:rule=Rule
|
||||
Assert ((Get-WelaIpsecPrerequisite -ReadRules {$script:rule; throw 'denied midway'} -ReadAssociations {}).Status -eq 'Unknown') 'partial failed enumeration never qualifies'
|
||||
Assert ((Get-WelaIpsecPrerequisite -ReadRules {$script:rule} -ReadAssociations {throw 'denied'}).Status -eq 'Unknown') 'failed independent SA observation prevents complete positive evidence'
|
||||
Assert ((Get-WelaIpsecPrerequisite -ReadRules {$script:rule;$script:rule} -ReadAssociations {}).Status -eq 'Unknown') 'duplicate rule identities rejected'
|
||||
Assert ((Get-WelaIpsecPrerequisite -ReadRules {1..4097} -ReadAssociations {}).Status -eq 'Unknown') 'native inventory cap remains unknown'
|
||||
$sa=Get-WelaIpsecPrerequisite -ReadRules {} -ReadAssociations {[pscustomobject]@{Name='1';LocalEndpoint='192.0.2.1';RemoteEndpoint='192.0.2.2'}}
|
||||
Assert ($sa.Status -eq 'Applicable' -and $sa.MainModeAssociations.Count -eq 1) 'valid native SA is independently positive evidence'
|
||||
Assert ((Get-WelaIpsecPrerequisite -ReadRules {} -ReadAssociations {[pscustomobject]@{Name='1';LocalEndpoint='unknown';RemoteEndpoint='192.0.2.2'}}).Status -eq 'Unknown') 'malformed SA does not qualify'
|
||||
Assert ((Get-WelaIpsecPrerequisite -Offline -ReadRules {throw 'must not run'} -ReadAssociations {throw 'must not run'}).Status -eq 'Unknown') 'offline never queries this host'
|
||||
$script:zero=@{}; foreach ($policy in (Import-WelaAuditProfiles).catalog) {$script:zero[$policy.guid]=0}
|
||||
$profile='microsoft-stronger-reviewed-2026-09';$guid='0CCE9218-69AE-11D9-BED3-505054503030'
|
||||
function Plan([switch]$Optional,[switch]$Observe) { Get-WelaAuditProfilePlan -Profile $profile -Role MemberServer -Build 26100 -Current $script:zero -IncludeOptional:$Optional -ObserveIpsec:$Observe -ReadIpsec {$script:evidence} }
|
||||
$script:evidence=$positive
|
||||
$plan=Plan -Optional
|
||||
$row=@($plan.policies|Where-Object id -eq 'IPsec Main Mode')[0]
|
||||
Assert ($row.conditionalPrerequisite.Status -eq 'Unknown' -and $null -eq $row.targetMask) 'offline conditional plan has no applicable target'
|
||||
$plan=Plan -Observe
|
||||
Assert (($plan.policies|Where-Object id -eq 'IPsec Main Mode').action -eq 'Optional (not selected)') 'positive evidence never substitutes for explicit selection'
|
||||
$plan=Plan -Observe -Optional
|
||||
Assert (($plan.policies|Where-Object id -eq 'IPsec Main Mode').targetMask -eq 3) 'live selected positive plan retains exact SF mask'
|
||||
$script:evidence=$none;$plan=Plan -Observe -Optional
|
||||
Assert (($plan.policies|Where-Object id -eq 'IPsec Main Mode').action -like 'Preserve*') 'scope-limited absence explicitly preserves'
|
||||
function Single-Plan {
|
||||
$p=Plan -Optional -Observe
|
||||
$p.policies=@($p.policies|Where-Object id -eq 'IPsec Main Mode')
|
||||
$p
|
||||
}
|
||||
$script:evidence=$positive;$plan=Single-Plan
|
||||
$script:state=$script:zero.Clone();$script:writes=0;$script:reads=0
|
||||
$contextReader={ [pscustomobject]@{Role='MemberServer';Build=26100} }
|
||||
$writer={param($Guid,$Mask) $script:writes++;$script:state[$Guid]=$Mask}
|
||||
$reader={$script:state.Clone()}
|
||||
$result=Invoke-WelaAuditProfilePlan $plan -ReadContext $contextReader -ReadPolicy $reader -WritePolicy $writer -ReadIpsec {$positive} -Confirm:$false
|
||||
Assert ($result.success -and $script:writes -eq 1 -and $result.results[0].prerequisiteObservations.Count -eq 2) 'direct executor observes and rechecks before write'
|
||||
$script:state[$guid]=0;$script:writes=0
|
||||
$result=Invoke-WelaAuditProfilePlan $plan -ReadContext $contextReader -ReadPolicy $reader -WritePolicy $writer -ReadIpsec {$none} -Confirm:$false
|
||||
Assert ($result.success -and $script:writes -eq 0 -and $result.results[0].status -eq 'Skipped') 'unobserved condition never writes'
|
||||
$result=Invoke-WelaAuditProfilePlan $plan -ReadContext $contextReader -ReadPolicy $reader -WritePolicy $writer -ReadIpsec {$script:reads++;if($script:reads -eq 1){$positive}else{$none}} -Confirm:$false
|
||||
Assert (-not $result.success -and $script:writes -eq 0 -and $result.results[0].prerequisiteObservations.Count -eq 2) 'last-moment condition drift blocks direct executor'
|
||||
$plan.profile='microsoft-sct-server2025-2602'
|
||||
$result=Invoke-WelaAuditProfilePlan $plan -ReadContext $contextReader -ReadPolicy $reader -WritePolicy $writer -ReadIpsec {throw 'unrelated query'} -Confirm:$false
|
||||
Assert ($result.success -and $script:writes -eq 1) 'other profile intent is unaffected'
|
||||
$plan=Single-Plan;$plan|Add-Member NoteProperty CustomProfileSource ([pscustomobject]@{})
|
||||
Assert (-not (Test-WelaIpsecConditionalPolicy $plan $plan.policies[0])) 'custom profile intent is not reclassified by its id'
|
||||
|
||||
# Public configure adapter: real runner and durable journal, injected native boundaries.
|
||||
function Get-WelaRegistryState {param($Path,$Name) [pscustomobject]@{ValueExists=$true;Type='DWord';Value=1} }
|
||||
function Get-WelaAuditPrecedenceSource { $null }
|
||||
function Get-WelaNativeAuditPolicy {param($Guid) $script:state[$Guid] }
|
||||
function Invoke-WelaNative {param($FilePath,$Arguments)
|
||||
Assert (Test-Path -LiteralPath (Join-Path $script:backup 'before.jsonl')) 'journal precedes native write'
|
||||
$script:writes++;$script:state[$guid]=3
|
||||
}
|
||||
function Read-Host {param($Prompt) $script:evidence=$none; 'y' }
|
||||
$root=Join-Path ([IO.Path]::GetTempPath()) ('wela-ipsec-'+[guid]::NewGuid().ToString('N'))
|
||||
try {
|
||||
$script:evidence=$positive;$plan=Single-Plan;$script:state[$guid]=0;$script:writes=0
|
||||
$script:backup=Join-Path $root 'race';$ctx=New-WelaConfigurationContext -BackupPath $script:backup
|
||||
Set-WelaProfileAuditControls $ctx $plan -ReadIpsec {$script:evidence}
|
||||
$result=Complete-WelaConfiguration $ctx -Plan $plan
|
||||
$row=@($result.Results|Where-Object Id -eq 'AuditPolicy/IPsec Main Mode')[0]
|
||||
Assert ($row.Status -eq 'Failed' -and $script:writes -eq 0 -and $row.PrerequisiteObservations[-1].Status -eq 'NotObservedWithinScope') 'public runner rechecks after prompt/journal and retains negative evidence'
|
||||
$script:evidence=$positive;$script:backup=Join-Path $root 'positive';$ctx=New-WelaConfigurationContext -Auto -BackupPath $script:backup
|
||||
Set-WelaProfileAuditControls $ctx $plan -ReadIpsec {$script:evidence}
|
||||
$result=Complete-WelaConfiguration $ctx -Plan $plan
|
||||
$row=@($result.Results|Where-Object Id -eq 'AuditPolicy/IPsec Main Mode')[0]
|
||||
Assert ($row.Status -eq 'Applied' -and $script:writes -eq 1 -and $row.PrerequisiteObservations.Count -eq 5) 'public runner keeps plan/read/prewrite/readback/final native prerequisite observations'
|
||||
$script:evidence=$none;$script:backup=Join-Path $root 'negative';$ctx=New-WelaConfigurationContext -Auto -BackupPath $script:backup
|
||||
Set-WelaProfileAuditControls $ctx $plan -ReadIpsec {$script:evidence}
|
||||
$result=Complete-WelaConfiguration $ctx -Plan $plan
|
||||
Assert ($result.Skipped -eq 1 -and $script:writes -eq 1) 'negative public prerequisite is visible even when audit mask already matches'
|
||||
} finally {if(Test-Path $root){Remove-Item $root -Recurse -Force}}
|
||||
Write-Host "Passed $script:checks IPsec prerequisite assertions. No native mutations."
|
||||
@@ -0,0 +1,109 @@
|
||||
param([switch]$AllowDisposablePolicyWrite,[switch]$AllowDisposableIpsecRule)
|
||||
$ErrorActionPreference='Stop'
|
||||
if ($env:OS -ne 'Windows_NT') {throw 'Native Windows fixture required.'}
|
||||
if (-not $AllowDisposablePolicyWrite -or -not $AllowDisposableIpsecRule) {throw 'Disposable audit-policy and owned IPsec-rule opt-in are both required.'}
|
||||
$repo=Split-Path $PSScriptRoot -Parent;$script:ScriptRoot=$repo
|
||||
Import-Module (Join-Path $repo 'modules/AuditProfiles.psm1') -Force
|
||||
Import-Module NetSecurity -ErrorAction Stop
|
||||
. (Join-Path $repo 'scripts/Configuration.ps1')
|
||||
$script:checks=0
|
||||
function Assert($Condition,[string]$Message) {if(-not $Condition){throw "FAIL: $Message"};$script:checks++}
|
||||
$identity=[Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
Assert ([Security.Principal.WindowsPrincipal]::new($identity).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) 'fixture is elevated'
|
||||
$root=Join-Path $env:RUNNER_TEMP ('wela-ipsec-'+[guid]::NewGuid().ToString('N'))
|
||||
$null=New-Item $root -ItemType Directory
|
||||
$name='wela-ipsec-'+[guid]::NewGuid().ToString('N')
|
||||
$guid='0CCE9218-69AE-11D9-BED3-505054503030'
|
||||
$before=Get-WelaEffectiveAuditPolicy
|
||||
$precedencePath='HKLM:\SYSTEM\CurrentControlSet\Control\Lsa'
|
||||
$precedence=Get-WelaRegistryState $precedencePath SCENoApplyLegacyAuditPolicy
|
||||
$beforeRules=@(Get-NetIPsecRule -PolicyStore ActiveStore -ErrorAction Stop|Select-Object Name,Enabled,InboundSecurity,OutboundSecurity,PrimaryStatus|Sort-Object Name|ConvertTo-Json -Depth 5 -Compress)
|
||||
$engine=(Get-Process -Id $PID).Path
|
||||
$created=$false;$cleanup=$false
|
||||
try {
|
||||
$baseline=Get-WelaIpsecPrerequisite
|
||||
$baseline|ConvertTo-Json -Depth 10|Set-Content (Join-Path $root 'baseline.json') -Encoding UTF8
|
||||
Assert ($baseline.Status -ne 'Unknown') "both native sources are readable: $($baseline.Diagnostic)"
|
||||
# Both endpoints are documentation-only addresses; no packets or negotiations are generated.
|
||||
$null=New-NetIPsecRule -Name $name -DisplayName $name -PolicyStore PersistentStore -Profile Any -Enabled False -LocalAddress 192.0.2.250 -RemoteAddress 192.0.2.251 -InboundSecurity Request -OutboundSecurity Request -ErrorAction Stop
|
||||
$created=$true
|
||||
$evidence=Get-WelaIpsecPrerequisite
|
||||
$evidence|ConvertTo-Json -Depth 10|Set-Content (Join-Path $root 'disabled.json') -Encoding UTF8
|
||||
Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop|Select-Object *|Export-Clixml (Join-Path $root 'disabled-native.xml')
|
||||
Get-NetIPsecRule -PolicyStore ActiveStore -ErrorAction Stop|Select-Object *|Export-Clixml (Join-Path $root 'disabled-active-native.xml')
|
||||
$owned=@($evidence.Rules|Where-Object Name -eq $name)
|
||||
Assert ((Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop).Enabled -eq 'False') 'owned persistent rule is actually disabled'
|
||||
Assert ($evidence.Status -ne 'Unknown' -and @($owned|Where-Object Qualifies).Count -eq 0) "disabled rule does not qualify (ActiveStore may omit it): $($evidence.Diagnostic)"
|
||||
Set-NetIPsecRule -Name $name -PolicyStore PersistentStore -Enabled True -InboundSecurity None -OutboundSecurity None -ErrorAction Stop
|
||||
$evidence=Get-WelaIpsecPrerequisite
|
||||
$owned=@($evidence.Rules|Where-Object Name -eq $name)
|
||||
$evidence|ConvertTo-Json -Depth 10|Set-Content (Join-Path $root 'exemption.json') -Encoding UTF8
|
||||
Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop|Select-Object *|Export-Clixml (Join-Path $root 'exemption-native.xml')
|
||||
Assert ($evidence.Status -ne 'Unknown' -and @($owned|Where-Object Qualifies).Count -eq 0) "real exemption-only rule does not qualify: $($evidence.Diagnostic)"
|
||||
# Converting to an exemption clears its authentication-set references. Recreate
|
||||
# only this owned fixture so New-NetIPsecRule supplies valid native defaults.
|
||||
Remove-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop
|
||||
$created=$false
|
||||
$null=New-NetIPsecRule -Name $name -DisplayName $name -PolicyStore PersistentStore -Profile Any -Enabled True -LocalAddress 192.0.2.250 -RemoteAddress 192.0.2.251 -InboundSecurity Request -OutboundSecurity Request -ErrorAction Stop
|
||||
$created=$true
|
||||
$evidence=Get-WelaIpsecPrerequisite
|
||||
$evidence|ConvertTo-Json -Depth 10|Set-Content (Join-Path $root 'positive.json') -Encoding UTF8
|
||||
Get-NetIPsecRule -Name $name -PolicyStore ActiveStore -ErrorAction Stop|Select-Object *|Export-Clixml (Join-Path $root 'positive-native.xml')
|
||||
$owned=@($evidence.Rules|Where-Object Name -eq $name)
|
||||
Assert ($evidence.Status -eq 'Applicable' -and $owned.Count -eq 1 -and $owned[0].Qualifies) "real enabled securing ActiveStore rule establishes scoped applicability: $($evidence.Diagnostic)"
|
||||
$planPath=Join-Path $root 'plan.json'
|
||||
& $engine -NoProfile -File (Join-Path $repo 'WELA.ps1') plan -Profile microsoft-stronger-reviewed-2026-09 -IncludeOptional -SaclMode Skip -PlanPath $planPath
|
||||
Assert ($LASTEXITCODE -eq 0) 'public live plan succeeds'
|
||||
$plan=Get-Content $planPath -Raw|ConvertFrom-Json
|
||||
$row=@($plan.policies|Where-Object id -eq 'IPsec Main Mode')[0]
|
||||
Assert ($row.conditionalPrerequisite.Status -eq 'Applicable' -and $row.targetMask -eq 3) 'public plan contains native evidence and selected SF mask'
|
||||
$dryPath=Join-Path $root 'dry.json'
|
||||
& $engine -NoProfile -File (Join-Path $repo 'WELA.ps1') configure -Profile microsoft-stronger-reviewed-2026-09 -IncludeOptional -SaclMode Skip -DryRun -Auto -ResultsPath $dryPath
|
||||
Assert ($LASTEXITCODE -eq 0) 'public configure dry-run succeeds'
|
||||
$current=Get-WelaEffectiveAuditPolicy
|
||||
Assert (@($before.Keys|Where-Object {$before[$_] -ne $current[$_]}).Count -eq 0) 'dry-run preserves all59 effective masks'
|
||||
$dry=Get-Content $dryPath -Raw|ConvertFrom-Json
|
||||
$row=@($dry.Results|Where-Object Id -eq 'AuditPolicy/IPsec Main Mode')[0]
|
||||
Assert ($row.PrerequisiteObservations.Count -ge 2 -and $row.Status -in @('Skipped','AlreadyCompliant')) 'public dry-run retains native prerequisite evidence'
|
||||
|
||||
# Actual public configure must produce a write for this control, then read it back.
|
||||
Set-WelaEffectiveAuditPolicy -Guid $guid -Mask 0 -Mode exact
|
||||
$resultPath=Join-Path $root 'configure.json'
|
||||
& $engine -NoProfile -File (Join-Path $repo 'WELA.ps1') configure -Profile microsoft-stronger-reviewed-2026-09 -IncludeOptional -SaclMode Skip -Auto -BackupPath (Join-Path $root 'backup') -ResultsPath $resultPath
|
||||
Assert ($LASTEXITCODE -eq 0) 'actual public configure succeeds'
|
||||
$result=Get-Content $resultPath -Raw|ConvertFrom-Json
|
||||
$row=@($result.Results|Where-Object Id -eq 'AuditPolicy/IPsec Main Mode')[0]
|
||||
Assert ($row.Status -eq 'Applied' -and $row.After -eq 3 -and $row.PrerequisiteObservations.Count -eq 5) 'actual gated policy write retains all five native observations'
|
||||
Assert (@($row.PrerequisiteObservations|Where-Object Status -ne Applicable).Count -eq 0) 'every configure boundary has positive native evidence'
|
||||
$journal=@(Get-Content (Join-Path $root 'backup/before.jsonl')|ConvertFrom-Json)
|
||||
Assert (@($journal|Where-Object {$_.Id -eq 'AuditPolicy/IPsec Main Mode' -and $_.Before -eq 0 -and $_.Desired.Mask -eq 3}).Count -eq 1) 'real public recovery journal retains exact policy transition'
|
||||
|
||||
# Native drift after prompt: exercise the real configuration callback and native reader.
|
||||
Set-WelaEffectiveAuditPolicy -Guid $guid -Mask 0 -Mode exact
|
||||
$plan.policies=@($plan.policies|Where-Object id -eq 'IPsec Main Mode')
|
||||
function Read-Host {param($Prompt) Remove-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop; $script:created=$false; 'y'}
|
||||
$ctx=New-WelaConfigurationContext -BackupPath (Join-Path $root 'drift-backup')
|
||||
Set-WelaProfileAuditControls $ctx $plan
|
||||
$drift=Complete-WelaConfiguration $ctx -Plan $plan -ResultsPath (Join-Path $root 'drift.json')
|
||||
$row=@($drift.Results|Where-Object Id -eq 'AuditPolicy/IPsec Main Mode')[0]
|
||||
if($baseline.Status -eq 'NotObservedWithinScope') {
|
||||
Assert ($row.Status -eq 'Failed' -and (Get-WelaEffectiveAuditPolicy)[$guid] -eq 0) 'real rule disappearance after prompt blocks auditpol write'
|
||||
} else {
|
||||
Assert ($row.Status -eq 'Applied') 'independent baseline prerequisite remains applicable after owned-rule removal'
|
||||
}
|
||||
} finally {
|
||||
if(@(Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction SilentlyContinue).Count){Remove-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop}
|
||||
foreach($id in $before.Keys){Set-WelaEffectiveAuditPolicy -Guid $id -Mask $before[$id] -Mode exact}
|
||||
if($precedence.ValueExists){Set-ItemProperty -LiteralPath $precedencePath -Name SCENoApplyLegacyAuditPolicy -Type $precedence.Type -Value $precedence.Value -ErrorAction Stop}
|
||||
else {Remove-ItemProperty -LiteralPath $precedencePath -Name SCENoApplyLegacyAuditPolicy -ErrorAction SilentlyContinue}
|
||||
$after=Get-WelaEffectiveAuditPolicy
|
||||
Assert (@($before.Keys|Where-Object {$before[$_] -ne $after[$_]}).Count -eq 0) 'all59 original masks restored'
|
||||
$afterPrecedence=Get-WelaRegistryState $precedencePath SCENoApplyLegacyAuditPolicy
|
||||
Assert (($precedence|ConvertTo-Json -Compress) -ceq ($afterPrecedence|ConvertTo-Json -Compress)) 'typed precedence/absence restored'
|
||||
$afterRules=@(Get-NetIPsecRule -PolicyStore ActiveStore -ErrorAction Stop|Select-Object Name,Enabled,InboundSecurity,OutboundSecurity,PrimaryStatus|Sort-Object Name|ConvertTo-Json -Depth 5 -Compress)
|
||||
Assert (($beforeRules -join '') -ceq ($afterRules -join '')) 'native rule inventory restored exactly'
|
||||
Assert (@(Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction SilentlyContinue).Count -eq 0) 'owned persistent rule removed'
|
||||
$cleanup=$true
|
||||
[pscustomobject]@{CleanupVerified=$cleanup;Checks=$script:checks;Engine=$PSVersionTable.PSVersion.ToString();Computer=$env:COMPUTERNAME;NoTrafficGenerated=$true}|ConvertTo-Json|Set-Content (Join-Path $root 'cleanup.json') -Encoding UTF8
|
||||
}
|
||||
Write-Host "Passed $script:checks native IPsec checks; artifacts: $root"
|
||||
@@ -0,0 +1,21 @@
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=Split-Path $PSScriptRoot -Parent
|
||||
$engine=(Get-Process -Id $PID).Path
|
||||
$script:checks=0
|
||||
function Check-Cli {
|
||||
param([string[]]$Arguments,[bool]$Success,[string]$Match)
|
||||
$old=$ErrorActionPreference;$ErrorActionPreference='Continue'
|
||||
try{$output=(& $engine -NoProfile -File (Join-Path $repo 'WELA.ps1') @Arguments 2>&1 | Out-String);$code=$LASTEXITCODE}finally{$ErrorActionPreference=$old}
|
||||
if(($Success -and $code -ne 0) -or (-not $Success -and $code -eq 0) -or $output -notmatch $Match){throw "CLI guard failed: $($Arguments -join ' '), exit $code : $output"}
|
||||
$script:checks++
|
||||
}
|
||||
Check-Cli @('smb-runtime','-Help') $true 'smb-runtime'
|
||||
Check-Cli @('smb-runtime','-Profile','test','-Help') $false 'dedicated options'
|
||||
Check-Cli @('help','-SmbRuntimeAction','Activate') $false 'SmbRuntime options require'
|
||||
Check-Cli @('smb-runtime','-SmbAction','Configure','-Help') $false 'dedicated options'
|
||||
Check-Cli @('smb-runtime','-DryRun') $false 'DryRun is supported only'
|
||||
Check-Cli @('smb-runtime','-SmbRuntimeAction','Activate','-DryRun','-Help') $true 'smb-runtime'
|
||||
Check-Cli @('smb-runtime','-BackupPath','unused','-Help') $false 'dedicated options'
|
||||
Write-Host "PASS: $script:checks public SMB runtime CLI guards"
|
||||
# Expected child failures are assertions, not the enclosing Actions step result.
|
||||
$global:LASTEXITCODE=0
|
||||
@@ -0,0 +1,130 @@
|
||||
$ErrorActionPreference='Stop'
|
||||
$script:ScriptRoot=Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $script:ScriptRoot 'scripts/Configuration.ps1')
|
||||
. (Join-Path $script:ScriptRoot 'scripts/SmbAuditing.ps1')
|
||||
. (Join-Path $script:ScriptRoot 'scripts/WefArrival.ps1')
|
||||
. (Join-Path $script:ScriptRoot 'scripts/SmbRuntimeActivation.ps1')
|
||||
$script:checks=0
|
||||
function Assert($Condition,[string]$Message){if(-not $Condition){throw "FAIL: $Message"};$script:checks++}
|
||||
function Reject([scriptblock]$Code,[string]$Message){$failed=$false;try{& $Code}catch{$failed=$true};Assert $failed $Message}
|
||||
Reject {Set-WelaSmbRuntimeFlag 'LanmanWorkstation/EnableInsecureGuestLogons'} 'security parameter refused by actual setter adapter'
|
||||
Reject {Set-WelaSmbRuntimeFlag 'LanmanServer/auditinsecureguestlogon'} 'mis-cased control refused'
|
||||
$nativeModuleBase=[IO.Path]::GetFullPath([IO.Path]::GetTempPath())
|
||||
$command=[pscustomobject]@{Name='Set-SmbServerConfiguration';ModuleName='SmbServerConfiguration';CommandType='Function';Module=[pscustomobject]@{ModuleBase=$nativeModuleBase};Parameters=@{}}
|
||||
foreach($definition in @(Get-WelaSmbAuditDefinitions | Where-Object Component -eq LanmanServer)){$command.Parameters[$definition.Name]=[pscustomobject]@{ParameterType=[bool]}}
|
||||
Assert-WelaSmbRuntimeCommand $command Server Set $nativeModuleBase
|
||||
Assert $true 'actual nested native CDXML module metadata accepted'
|
||||
$command.ModuleName='Other'
|
||||
Reject {Assert-WelaSmbRuntimeCommand $command Server Set $nativeModuleBase} 'foreign module refused'
|
||||
$command.ModuleName='SmbServerConfiguration'
|
||||
Reject {Assert-WelaSmbRuntimeCommand $command Server Set ($nativeModuleBase+'other')} 'unexpected module directory refused'
|
||||
$command.Parameters.AuditInsecureGuestLogon.ParameterType=[string]
|
||||
Reject {Assert-WelaSmbRuntimeCommand $command Server Set $nativeModuleBase} 'mistyped native parameter refused'
|
||||
$command.Parameters.Remove('AuditInsecureGuestLogon')
|
||||
Reject {Assert-WelaSmbRuntimeCommand $command Server Set $nativeModuleBase} 'missing native parameter refused'
|
||||
function FixtureConfiguration {
|
||||
param([string]$Side='Server')
|
||||
$component=if($Side -eq 'Server'){'LanmanServer'}else{'LanmanWorkstation'}
|
||||
$properties=@(Get-WelaSmbAuditDefinitions | Where-Object Component -eq $component | ForEach-Object {[pscustomobject]@{Name=$_.Name;Value=$false;CimType='Boolean'}})
|
||||
$properties+=[pscustomobject]@{Name='RequireSecuritySignature';Value=$true;CimType='Boolean'}
|
||||
[pscustomobject]@{CimClass=[pscustomobject]@{CimClassName="MSFT_Smb${Side}Configuration"};CimInstanceProperties=$properties}
|
||||
}
|
||||
$native=FixtureConfiguration
|
||||
$config=ConvertTo-WelaSmbRuntimeConfiguration $native Server
|
||||
Assert ($config.RequireSecuritySignature.Value -eq $true -and $config.AuditInsecureGuestLogon.Value -eq $false) 'native typed security and audit properties retained'
|
||||
$native.CimInstanceProperties[0].Value='False'
|
||||
Reject {ConvertTo-WelaSmbRuntimeConfiguration $native Server} 'string audit Boolean rejected'
|
||||
$native=FixtureConfiguration;$native.CimInstanceProperties[0].CimType='String'
|
||||
Reject {ConvertTo-WelaSmbRuntimeConfiguration $native Server} 'wrong native CIM type rejected'
|
||||
$native=FixtureConfiguration;$native.CimClass.CimClassName='MSFT_AnotherConfiguration'
|
||||
Reject {ConvertTo-WelaSmbRuntimeConfiguration $native Server} 'wrong native class rejected'
|
||||
$native=FixtureConfiguration;$native.CimInstanceProperties+=[pscustomobject]@{Name='Mystery';Value=[pscustomobject]@{a=1};CimType='Instance'}
|
||||
Reject {ConvertTo-WelaSmbRuntimeConfiguration $native Server} 'unknown unrelated configuration remains unverified'
|
||||
|
||||
$root=Join-Path ([IO.Path]::GetTempPath()) ('wela-smb-activation-'+[guid]::NewGuid().ToString('N'))
|
||||
$null=New-Item -ItemType Directory -Path $root
|
||||
$script:receiptWriter=${function:Write-WelaSmbRuntimeReceipt}
|
||||
function Reset-Fixture {
|
||||
$policies=[ordered]@{}
|
||||
foreach($definition in Get-WelaSmbAuditDefinitions){$policies["$($definition.Component)/$($definition.Name)"]=[pscustomobject]@{Policy=[pscustomobject]@{KeyExists=$false;ValueExists=$false;Type=$null;Value=$null}}}
|
||||
$script:fixture=[pscustomobject][ordered]@{Computer='fixture';Host=[pscustomobject]@{Build=26100};Commands='native';Sources='hash';Policies=[pscustomobject]$policies;Configurations=[pscustomobject]@{Server=(ConvertTo-WelaSmbRuntimeConfiguration (FixtureConfiguration Server) Server);Client=(ConvertTo-WelaSmbRuntimeConfiguration (FixtureConfiguration Client) Client)}}
|
||||
$script:writes=0;$script:reads=0;$script:driftRead=0;$script:failWrite=0;$script:securityDrift=$false;$script:receiptFail=$false;$script:promptDrift=$false
|
||||
$script:out=Join-Path $root ([guid]::NewGuid().ToString('N'))
|
||||
}
|
||||
function Get-WelaSmbRuntimeState {
|
||||
$script:reads++
|
||||
if($script:reads -eq $script:driftRead){$script:fixture.Sources='changed'}
|
||||
Get-WelaSmbRuntimeKey $script:fixture | ConvertFrom-Json
|
||||
}
|
||||
function Write-WelaSmbRuntimeReceipt {
|
||||
param($Root,$Name,$Value)
|
||||
if($script:receiptFail -and $Name -eq '1-pending.json'){throw 'Injected durable-write failure'}
|
||||
& $script:receiptWriter $Root $Name $Value
|
||||
}
|
||||
function Set-WelaSmbRuntimeFlag {
|
||||
param($Id)
|
||||
$script:writes++
|
||||
Assert (Test-Path (Join-Path $script:out "$($script:writes)-pending.json")) 'pending receipt exists before setter'
|
||||
if($script:writes -eq $script:failWrite){throw 'Injected native setter failure'}
|
||||
$parts=$Id.Split('/');$side=if($parts[0] -eq 'LanmanServer'){'Server'}else{'Client'}
|
||||
$script:fixture.Configurations.$side.($parts[1]).Value=$true
|
||||
if($script:securityDrift){$script:fixture.Configurations.Server.RequireSecuritySignature.Value=$false}
|
||||
}
|
||||
function Read-Host {param($Prompt) if($script:promptDrift){$script:fixture.Sources='changed at prompt'};'y'}
|
||||
try {
|
||||
Reset-Fixture
|
||||
$plan=Invoke-WelaSmbRuntimeActivation
|
||||
Assert ($plan.Status -eq 'Planned' -and $plan.Controls.Count -eq 6 -and $script:writes -eq 0) 'default Plan is six read-only audit controls'
|
||||
Assert (-not (Test-Path $script:out)) 'Plan creates no evidence directory'
|
||||
$dry=Invoke-WelaSmbRuntimeActivation -Action Activate -DryRun -OutputPath $script:out
|
||||
Assert ($dry.Status -eq 'DryRun' -and $script:writes -eq 0 -and -not (Test-Path $script:out)) 'DryRun does not write'
|
||||
Reject {Invoke-WelaSmbRuntimeActivation -Action Plan -Auto} 'irrelevant Plan consent rejected'
|
||||
Reject {Invoke-WelaSmbRuntimeActivation -Action Plan -DryRun} 'invalid dry run action rejected'
|
||||
$id='LanmanServer/AuditInsecureGuestLogon'
|
||||
foreach($value in @(0,'1',2)) {
|
||||
Reset-Fixture;$script:fixture.Policies.$id.Policy=[pscustomobject]@{KeyExists=$true;ValueExists=$true;Type='DWord';Value=$value}
|
||||
$report=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath $script:out
|
||||
Assert ($report.ExitCode -eq 1 -and $script:writes -eq 0 -and -not (Test-Path $script:out)) 'conflicting or mistyped policy stops all mutations'
|
||||
}
|
||||
Reset-Fixture;$script:fixture.Policies.$id.Policy=[pscustomobject]@{KeyExists=$true;ValueExists=$true;Type='String';Value=1}
|
||||
Assert ((Invoke-WelaSmbRuntimeActivation).ExitCode -eq 1) 'wrong registry kind blocks'
|
||||
Reset-Fixture;$script:fixture.Policies.$id.Policy=[pscustomobject]@{KeyExists=$true;ValueExists=$true;Type='DWord';Value=1}
|
||||
Assert ((Invoke-WelaSmbRuntimeActivation).ExitCode -eq 0) 'existing enabled policy is compatible'
|
||||
|
||||
Reset-Fixture
|
||||
$report=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath $script:out
|
||||
Assert ($report.ExitCode -eq 0 -and $report.Status -eq 'RuntimeAuditingActive' -and $script:writes -eq 6) "six native activations succeed: $($report.Diagnostic)"
|
||||
Assert (@($report.Results | Where-Object Status -eq Activated).Count -eq 6) 'all six report confirmed activation'
|
||||
Assert ((Get-ChildItem -LiteralPath $script:out -File).Count -eq 14) 'plan, six pending, six confirmed, final result retained'
|
||||
Assert ($report.After.Configurations.Server.RequireSecuritySignature.Value -eq $true) 'security property preserved'
|
||||
Assert ($report.ReadyRuleCredit -eq 0 -and $report.EventGeneration -eq 'Not tested') 'activation grants no event or rule proof'
|
||||
$prior=Get-Content -Raw -LiteralPath (Join-Path $script:out 'result.json')
|
||||
$second=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath $script:out
|
||||
Assert ($second.ExitCode -eq 1 -and (Get-Content -Raw -LiteralPath (Join-Path $script:out 'result.json')) -ceq $prior) 'existing evidence is never overwritten'
|
||||
$script:out=Join-Path $root ([guid]::NewGuid().ToString('N'));$script:writes=0
|
||||
$repeat=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath $script:out
|
||||
Assert ($repeat.ExitCode -eq 0 -and $script:writes -eq 0 -and @($repeat.Results | Where-Object Status -eq AlreadyActive).Count -eq 6) 'idempotence requires no setters'
|
||||
|
||||
Reset-Fixture;$script:failWrite=2
|
||||
$partial=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath $script:out
|
||||
Assert ($partial.ExitCode -eq 1 -and $script:writes -eq 2) 'partial native failure stops remaining writes'
|
||||
Assert ($partial.Results[0].Status -eq 'Activated' -and $partial.Results[1].Status -eq 'Failed' -and $partial.Results[2].Status -eq 'Skipped') 'partial outcomes preserved'
|
||||
Assert ((Test-Path (Join-Path $script:out '1-confirmed.json')) -and -not (Test-Path (Join-Path $script:out '2-confirmed.json'))) 'failed operation is never confirmed'
|
||||
foreach($read in @(2,3,20)) {
|
||||
Reset-Fixture;$script:driftRead=$read
|
||||
$drift=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath $script:out
|
||||
Assert ($drift.ExitCode -eq 1) 'fresh/prewrite/final source drift fails closed'
|
||||
if($read -lt 4){Assert ($script:writes -eq 0) 'prewrite drift performs no setter'}
|
||||
}
|
||||
Reset-Fixture;$script:promptDrift=$true
|
||||
$drift=Invoke-WelaSmbRuntimeActivation -Action Activate -OutputPath $script:out
|
||||
Assert ($drift.ExitCode -eq 1 -and $script:writes -eq 0) 'prompt-time drift refused'
|
||||
Reset-Fixture;$script:securityDrift=$true
|
||||
$drift=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath $script:out
|
||||
Assert ($drift.ExitCode -eq 1 -and $script:writes -eq 1 -and -not (Test-Path (Join-Path $script:out '1-confirmed.json'))) 'unrelated security delta prevents confirmation'
|
||||
Reset-Fixture;$script:receiptFail=$true
|
||||
$failed=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath $script:out
|
||||
Assert ($failed.ExitCode -eq 1 -and $script:writes -eq 0) 'failed durable intent blocks setter'
|
||||
Assert (Test-Path (Join-Path $script:out 'result.json')) 'partial diagnostic survives pending-write failure'
|
||||
Write-Host "PASS: $script:checks SMB runtime activation assertions"
|
||||
}finally{Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue}
|
||||
@@ -0,0 +1,85 @@
|
||||
# Mutates only six audit flags on disposable GitHub-hosted Windows VMs. Never run on production.
|
||||
$ErrorActionPreference='Stop'
|
||||
if($env:OS -ne 'Windows_NT' -or $env:GITHUB_ACTIONS -ne 'true' -or $env:WELA_DISPOSABLE_SMB_ACTIVATION -ne 'true') {throw 'Explicit disposable GitHub Windows test opt-in is required.'}
|
||||
$script:ScriptRoot=Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $script:ScriptRoot 'scripts/Configuration.ps1')
|
||||
. (Join-Path $script:ScriptRoot 'scripts/SmbAuditing.ps1')
|
||||
. (Join-Path $script:ScriptRoot 'scripts/WefArrival.ps1')
|
||||
. (Join-Path $script:ScriptRoot 'scripts/SmbRuntimeActivation.ps1')
|
||||
$computer=Get-CimInstance Win32_ComputerSystem
|
||||
$os=Get-CimInstance Win32_OperatingSystem
|
||||
if($computer.PartOfDomain -or $computer.DomainRole -ne 2 -or $os.ProductType -ne 3 -or [int]$os.BuildNumber -notin @(20348,26100)){throw 'Fixture requires an isolated member-class Server 2022/2025 host.'}
|
||||
$evidence=Join-Path $env:RUNNER_TEMP ('wela-smb-runtime-'+[guid]::NewGuid().ToString('N'))
|
||||
$null=New-Item -ItemType Directory -Path $evidence
|
||||
$reportPath=Join-Path $evidence 'activation'
|
||||
$cleanup=[ordered]@{Build=[int]$os.BuildNumber;Engine=$PSVersionTable.PSVersion.ToString();OriginalCaptured=$false;AuditFlagsRestored=$false;FullContextRestored=$false;NativeActivation=$false;UnsupportedRefusal=$false}
|
||||
$original=$null
|
||||
try {
|
||||
if([int]$os.BuildNumber -eq 20348) {
|
||||
$report=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath $reportPath
|
||||
if($report.ExitCode -ne 1 -or $report.Diagnostic -notlike '*NotApplicable*' -or (Test-Path $reportPath)){throw 'Server 2022 activation was not refused before writes.'}
|
||||
$report | ConvertTo-Json -Depth 24 | Set-Content -LiteralPath (Join-Path $evidence 'refusal.json') -Encoding UTF8
|
||||
$global:LASTEXITCODE=0
|
||||
$null=& (Join-Path $script:ScriptRoot 'WELA.ps1') smb-runtime -SmbRuntimeAction Activate -SmbRuntimeOutputPath $reportPath -Auto
|
||||
if($LASTEXITCODE -ne 1 -or (Test-Path $reportPath)){throw 'Public CLI did not refuse unsupported Server 2022.'}
|
||||
$cleanup.UnsupportedRefusal=$true
|
||||
Write-Host 'PASS: actual Server 2022 native and public-CLI refusal, no output or setters.'
|
||||
}else{
|
||||
$original=Get-WelaSmbRuntimeState
|
||||
if(@(Get-WelaSmbRuntimePlan $original | Where-Object Status -eq BlockedPolicy).Count){throw 'Fixture will not overwrite a conflicting policy.'}
|
||||
$cleanup.OriginalCaptured=$true
|
||||
$original | ConvertTo-Json -Depth 24 | Set-Content -LiteralPath (Join-Path $evidence 'original.json') -Encoding UTF8
|
||||
foreach($definition in Get-WelaSmbAuditDefinitions) {
|
||||
$side=if($definition.Component -eq 'LanmanServer'){'Server'}else{'Client'}
|
||||
$command="SmbShare\Set-Smb${side}Configuration"
|
||||
$parameters=@{Force=$true;Confirm=$false;ErrorAction='Stop'};$parameters[$definition.Name]=$false
|
||||
$null=& $command @parameters
|
||||
}
|
||||
$prepared=Get-WelaSmbRuntimeState
|
||||
$expected=Get-WelaSmbRuntimeKey $original | ConvertFrom-Json
|
||||
foreach($definition in Get-WelaSmbAuditDefinitions) {
|
||||
$side=if($definition.Component -eq 'LanmanServer'){'Server'}else{'Client'}
|
||||
$expected.Configurations.$side.($definition.Name).Value=$false
|
||||
}
|
||||
if((Get-WelaSmbRuntimeKey $prepared) -cne (Get-WelaSmbRuntimeKey $expected)){throw 'Fixture preparation changed other settings or did not make audit flags False.'}
|
||||
$dry=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -DryRun -OutputPath $reportPath
|
||||
if($dry.ExitCode -ne 0 -or (Test-Path $reportPath) -or (Get-WelaSmbRuntimeKey (Get-WelaSmbRuntimeState)) -cne (Get-WelaSmbRuntimeKey $prepared)){throw 'Native dry-run changed context or wrote output.'}
|
||||
$global:LASTEXITCODE=0
|
||||
$cli=@(& (Join-Path $script:ScriptRoot 'WELA.ps1') smb-runtime -SmbRuntimeAction Activate -SmbRuntimeOutputPath $reportPath -Auto)
|
||||
if($LASTEXITCODE -ne 0){throw "Public CLI exited $LASTEXITCODE"}
|
||||
$report=Get-Content -Raw -LiteralPath (Join-Path $reportPath 'result.json') | ConvertFrom-Json
|
||||
if($report.ExitCode -ne 0 -or $report.Status -ne 'RuntimeAuditingActive' -or @($report.Results | Where-Object Status -eq Activated).Count -ne 6){throw "Native six-flag activation failed: $($report.Diagnostic)"}
|
||||
$active=Get-WelaSmbRuntimeState
|
||||
foreach($definition in Get-WelaSmbAuditDefinitions) {
|
||||
$side=if($definition.Component -eq 'LanmanServer'){'Server'}else{'Client'}
|
||||
$expected.Configurations.$side.($definition.Name).Value=$true
|
||||
}
|
||||
if((Get-WelaSmbRuntimeKey $active) -cne (Get-WelaSmbRuntimeKey $expected)){throw 'Activation did not preserve every unrelated configuration field and policy tuple.'}
|
||||
$repeat=Invoke-WelaSmbRuntimeActivation -Action Activate -Auto -OutputPath (Join-Path $evidence 'idempotent')
|
||||
if($repeat.ExitCode -ne 0 -or @($repeat.Results | Where-Object Status -eq AlreadyActive).Count -ne 6){throw 'Native idempotence failed.'}
|
||||
if(@(Get-ChildItem -LiteralPath $repeat.OutputPath -Filter '*-pending.json').Count){throw 'Idempotent run unexpectedly journaled a setter.'}
|
||||
$cleanup.NativeActivation=$true
|
||||
Write-Host 'PASS: actual Server 2025 public-CLI activation of all six native Boolean audit flags, dry-run, idempotence and preservation of all unrelated native configuration.'
|
||||
}
|
||||
}finally{
|
||||
if($original) {
|
||||
$failures=@()
|
||||
foreach($definition in Get-WelaSmbAuditDefinitions) {
|
||||
try {
|
||||
$side=if($definition.Component -eq 'LanmanServer'){'Server'}else{'Client'}
|
||||
$command="SmbShare\Set-Smb${side}Configuration"
|
||||
$parameters=@{Force=$true;Confirm=$false;ErrorAction='Stop'};$parameters[$definition.Name]=[bool]$original.Configurations.$side.($definition.Name).Value
|
||||
$null=& $command @parameters
|
||||
}catch{$failures+=$_.Exception.Message}
|
||||
}
|
||||
$restored=Get-WelaSmbRuntimeState
|
||||
$restored | ConvertTo-Json -Depth 24 | Set-Content -LiteralPath (Join-Path $evidence 'restored.json') -Encoding UTF8
|
||||
$cleanup.AuditFlagsRestored=$failures.Count -eq 0
|
||||
$cleanup.FullContextRestored=(Get-WelaSmbRuntimeKey $restored) -ceq (Get-WelaSmbRuntimeKey $original)
|
||||
$cleanup | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $evidence 'acceptance.json') -Encoding UTF8
|
||||
if(-not $cleanup.AuditFlagsRestored -or -not $cleanup.FullContextRestored){throw "Native SMB fixture cleanup mismatch: $($failures -join '; ')"}
|
||||
Write-Host 'PASS: exact native audit flags and full configuration/policy/source context restored.'
|
||||
}else{$cleanup | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $evidence 'acceptance.json') -Encoding UTF8}
|
||||
Write-Host "Native SMB evidence: $evidence"
|
||||
}
|
||||
$global:LASTEXITCODE=0
|
||||
@@ -0,0 +1,17 @@
|
||||
$ErrorActionPreference='Stop';$repo=Split-Path $PSScriptRoot -Parent
|
||||
$engine=(Get-Process -Id $PID).Path;$count=0
|
||||
$cases=@(
|
||||
@{Args=@('wec-state','-Help');Code=0;Pattern='Disable interrupts'},
|
||||
@{Args=@('configure','-WecStateAction','Apply','-Auto');Code=1;Pattern='require wec-state'},
|
||||
@{Args=@('wec-state','-Help','-Profile','wela-2.2.0');Code=1;Pattern='only dedicated'},
|
||||
@{Args=@('wec-state','-Help','-WefAction','Configure');Code=1;Pattern='only dedicated'},
|
||||
@{Args=@('wec-state','-Help','-WecUpdateAction','Apply');Code=1;Pattern='only dedicated'},
|
||||
@{Args=@('wec-state','-Help','-Auto');Code=1;Pattern='only dedicated'},
|
||||
@{Args=@('wec-state','-Help','-DryRun');Code=1;Pattern='only dedicated'},
|
||||
@{Args=@('wec-state','-Help','-ResultsPath','not-created');Code=1;Pattern='only dedicated'},
|
||||
@{Args=@('wec-state','-WecStateAction','Apply','-WecStateOutputPath','not-created');Code=1;Pattern='reviewed plan'},
|
||||
@{Args=@('wec-state','-WecStateOutputPath','not-created');Code=1;Pattern='Plan requires'}
|
||||
)
|
||||
foreach($case in $cases){$prior=$ErrorActionPreference;try{$ErrorActionPreference='Continue';$output=@(&$engine -NoLogo -NoProfile -NonInteractive -File "$repo/WELA.ps1" @($case.Args) 2>&1);$code=$LASTEXITCODE}finally{$ErrorActionPreference=$prior};if(($case.Code -eq 0 -and $code -ne 0) -or ($case.Code -ne 0 -and $code -eq 0) -or ($output -join "`n") -notmatch $case.Pattern){throw "CLI failure: $($case.Args -join ' ') -> $code / $($output -join ' ')"};$count++}
|
||||
Write-Host "WEC state CLI: $count checks passed."
|
||||
$global:LASTEXITCODE=0
|
||||
@@ -0,0 +1,114 @@
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=Split-Path $PSScriptRoot -Parent
|
||||
Import-Module "$repo/modules/WefSubscriptions.psm1" -Force
|
||||
Import-Module "$repo/modules/AuditProfiles.psm1" -Force
|
||||
. "$repo/scripts/Configuration.ps1"
|
||||
. "$repo/scripts/WefArrival.ps1"
|
||||
. "$repo/scripts/WecUpdate.ps1"
|
||||
. "$repo/scripts/WecState.ps1"
|
||||
Initialize-WelaWecStateNative
|
||||
$count=0
|
||||
function Assert($Value,$Message){if(-not $Value){throw $Message};$script:count++}
|
||||
function Reject([scriptblock]$Action,[string]$Pattern){$message='';try{&$Action|Out-Null}catch{$message=$_.Exception.Message};Assert ($message -match $Pattern) "Expected $Pattern; got $message"}
|
||||
$root=Join-Path ([IO.Path]::GetTempPath()) ('wela-wec-state-'+[guid]::NewGuid().ToString('N'));$null=New-Item -ItemType Directory $root
|
||||
$sid='S-1-5-21-11-22-33-1001';$id='WELA Native Security Example'
|
||||
$base=[IO.File]::ReadAllText("$repo/config/wef-examples/native-security.xml").Replace('<Enabled>true</Enabled>','<Enabled>false</Enabled>').Replace('<AllowedSourceDomainComputers></AllowedSourceDomainComputers>',('<AllowedSourceDomainComputers>'+(Get-WelaWefAuthorization @($sid))+'</AllowedSourceDomainComputers>'))
|
||||
$script:xml=$base;$script:saves=0;$script:reads=0;$script:contextReads=0;$script:mode='ok';$script:journal=''
|
||||
function Get-WelaWecStateContext {
|
||||
$script:contextReads++;$token='11'*56
|
||||
if($script:mode -eq 'token-drift' -and $script:contextReads -gt 1){$token='22'*56}
|
||||
[pscustomobject][ordered]@{Computer='TEST';HostKey='20348';Reader=[pscustomobject]@{Sid='S-1-5-21-1-2-3-1000';TokenStatistics=$token};Service='Running';DestinationLog=[pscustomobject]@{Enabled=($script:mode -ne 'disabled-destination')}}
|
||||
}
|
||||
function Read-WelaWecStateDefinition {
|
||||
param($Id,$SourceSids)
|
||||
$script:reads++
|
||||
if($script:mode -eq 'drift' -and $script:reads -eq 2){$script:xml=$script:xml.Replace('MinLatency','Normal')}
|
||||
if($script:mode -eq 'denied'){throw 'Native access denied'}
|
||||
Get-WelaWecStateDefinition $script:xml $SourceSids
|
||||
}
|
||||
function Read-WelaWecStateRuntime {param($Id);[pscustomobject]@{Status='Unknown';Diagnostic='Runtime unavailable';ReadyRuleCredit=0}}
|
||||
function New-WelaWecStateEdit {
|
||||
param($Before)
|
||||
$edit=[pscustomobject]@{SaveAttempted=$false}
|
||||
$edit|Add-Member ScriptMethod Save {param($Enabled)
|
||||
Assert (Test-Path -LiteralPath $script:journal) 'Durable pending record precedes native save'
|
||||
$pending=ConvertFrom-WelaArrivalJson ([IO.File]::ReadAllText($script:journal))
|
||||
Assert ($pending.Status -ceq 'Pending' -and $pending.DesiredEnabled -eq $Enabled) 'Pending receipt names exact desired state'
|
||||
if($script:mode -eq 'native-refusal'){throw 'Native view changed before save'}
|
||||
$this.SaveAttempted=$true;$script:saves++
|
||||
if($script:mode -eq 'failure'){throw 'native save failed'}
|
||||
if($script:mode -eq 'false-success'){return}
|
||||
$doc=Read-WelaWefXml $script:xml;$doc.Subscription.Enabled=$Enabled.ToString().ToLowerInvariant()
|
||||
if($script:mode -eq 'preservation'){$doc.Subscription.ReadExistingEvents='true'}
|
||||
$script:xml=$doc.OuterXml
|
||||
if($script:mode -eq 'evidence-tamper'){[IO.File]::AppendAllText($script:journal,' ')}
|
||||
}
|
||||
$edit|Add-Member ScriptMethod Dispose {}
|
||||
$edit
|
||||
}
|
||||
try {
|
||||
$before=Get-WelaWecStateDefinition $base @($sid)
|
||||
Assert (-not $before.Enabled -and $before.Id -ceq $id) 'Disabled original parsed'
|
||||
$enabled=Get-WelaWecStateDefinition ($base.Replace('<Enabled>false','<Enabled>true')) @($sid)
|
||||
Assert ($enabled.Enabled -and $enabled.PreservedKey -ceq $before.PreservedKey -and $enabled.WholeKey -cne $before.WholeKey) 'Only Enabled excluded from preservation comparison'
|
||||
Reject {Get-WelaWecStateDefinition $base @('S-1-1-0')} 'SID'
|
||||
Reject {Get-WelaWecStateDefinition ($base.Replace('Path="Security"','Path="Microsoft-Windows-Sysmon/Operational"')) @($sid)} 'Sysmon'
|
||||
Reject {Get-WelaWecStateDefinition ($base.Replace('SourceInitiated','CollectorInitiated')) @($sid)} 'source-initiated'
|
||||
Reject {Get-WelaWecStateDefinition ($base.Replace('<Enabled>false</Enabled>','<Enabled>false</Enabled><Enabled>true</Enabled>')) @($sid)} 'duplicate'
|
||||
Reject {Invoke-WelaWecState -Id $id -SourceSids @($sid) -OutputPath (Join-Path $root 'invalid')} 'Plan requires'
|
||||
Reject {Invoke-WelaWecState -Action Apply -PlanPath missing -PlanHash ('a'*64) -State Enabled -OutputPath (Join-Path $root 'invalid')} 'only'
|
||||
foreach($scenario in @('ok','drift','token-drift','failure','false-success','preservation','hash','stale','context','duplicate-key','wrong-type','source-hash','denied','native-refusal','evidence-tamper')){
|
||||
$script:xml=$base;$script:mode='ok';$script:reads=0;$script:contextReads=0;$script:saves=0
|
||||
$planResult=Invoke-WelaWecState -Id $id -SourceSids @($sid) -State Enabled -OutputPath (Join-Path $root ($scenario+'-plan'))
|
||||
Assert ($planResult.ExitCode -eq 0 -and $planResult.Status -eq 'ReviewRequired') "Plan created: $($planResult.Diagnostic)"
|
||||
Assert ($script:saves -eq 0 -and -not $planResult.BeforeEnabled -and $planResult.DesiredEnabled) 'Plan is read only and states exact transition'
|
||||
$planPath=Join-Path $planResult.OutputPath 'plan.json';$hash=$planResult.PlanHash
|
||||
$script:mode=$scenario;$script:reads=0;$script:contextReads=0
|
||||
if($scenario -eq 'hash'){$hash='b'*64}
|
||||
if($scenario -eq 'stale'){$script:xml=$base.Replace('MinLatency','Normal')}
|
||||
if($scenario -in @('context','duplicate-key','wrong-type','source-hash')){
|
||||
$text=[IO.File]::ReadAllText($planPath)
|
||||
switch($scenario){
|
||||
context {$text=$text.Replace('TEST','OTHER')}
|
||||
duplicate-key {$text=$text.Replace('"SchemaVersion":','"SchemaVersion": 1, "SchemaVersion":')}
|
||||
wrong-type {$text=$text -replace '"DesiredEnabled":\s*true','"DesiredEnabled": "true"'}
|
||||
source-hash {$text=$text.Replace('scripts/WecState.ps1','scripts/Untrusted.ps1')}
|
||||
}
|
||||
[IO.File]::WriteAllText($planPath,$text);$hash=(Get-FileHash $planPath).Hash.ToLowerInvariant()
|
||||
}
|
||||
$out=Join-Path $root ($scenario+'-apply');$script:journal=Join-Path $out 'before-save.json'
|
||||
$result=Invoke-WelaWecState Apply -PlanPath $planPath -PlanHash $hash -OutputPath $out
|
||||
Assert (($result.ExitCode -eq 0) -eq ($scenario -eq 'ok')) "Scenario $scenario : $($result.Diagnostic)"
|
||||
Assert ($result.ReadyRuleCredit -eq 0 -and $result.BookmarkContinuity -eq 'Not established') 'No delivery/bookmark/Sigma credit'
|
||||
Assert (Test-Path (Join-Path $out 'manifest.json')) 'Result retained'
|
||||
if($scenario -in @('drift','token-drift','hash','stale','context','duplicate-key','wrong-type','source-hash','denied','native-refusal')){Assert ($script:saves -eq 0 -and -not $result.NativeSaveAttempted) 'Rejected before native save'}
|
||||
if($scenario -in @('failure','false-success','preservation','evidence-tamper')){Assert ($result.Status -eq 'SaveAttemptedUnverified' -and $result.NativeSaveAttempted) 'Partial failure remains explicit'}
|
||||
if($scenario -eq 'ok'){
|
||||
$after=Get-WelaWecStateDefinition $script:xml @($sid)
|
||||
Assert ($after.PreservedKey -ceq $before.PreservedKey -and $after.Enabled -and $result.Status -eq 'StateChangedAndVerified') 'Only Enabled changed'
|
||||
Assert ($result.RuntimeAfter.Status -eq 'Unknown') 'Unknown runtime does not become healthy or invalidate observed configuration'
|
||||
}
|
||||
}
|
||||
$script:mode='disabled-destination';$script:xml=$base;$script:saves=0
|
||||
$blocked=Invoke-WelaWecState -Id $id -SourceSids @($sid) -State Enabled -OutputPath (Join-Path $root 'disabled-destination-plan')
|
||||
Assert ($blocked.Status -eq 'Refused' -and $blocked.Diagnostic -match 'ForwardedEvents' -and $script:saves -eq 0) 'Disabled destination is rejected before planning activation'
|
||||
$disabled=Invoke-WelaWecState -Id $id -SourceSids @($sid) -State Disabled -OutputPath (Join-Path $root 'disabled-destination-disable')
|
||||
Assert ($disabled.ExitCode -eq 0) 'Disabled destination does not block a reviewed disable plan'
|
||||
foreach($desired in @('Enabled','Disabled')){
|
||||
$script:mode='ok';$script:xml=if($desired -eq 'Disabled'){$base}else{$base.Replace('<Enabled>false','<Enabled>true')};$script:saves=0
|
||||
$planResult=Invoke-WelaWecState -Id $id -SourceSids @($sid) -State $desired -OutputPath (Join-Path $root ($desired+'-same-plan'))
|
||||
$result=Invoke-WelaWecState Apply -PlanPath (Join-Path $planResult.OutputPath 'plan.json') -PlanHash $planResult.PlanHash -OutputPath (Join-Path $root ($desired+'-same-apply'))
|
||||
Assert ($result.Status -eq 'AlreadyMatches' -and $result.ExitCode -eq 0 -and $script:saves -eq 0) 'Idempotent enabled/disabled state never saves/reactivates'
|
||||
}
|
||||
$script:xml=$base.Replace('<Enabled>false','<Enabled>true');$script:saves=0
|
||||
$planResult=Invoke-WelaWecState -Id $id -SourceSids @($sid) -State Disabled -OutputPath (Join-Path $root 'disable-plan')
|
||||
$out=Join-Path $root 'disable-apply';$script:journal=Join-Path $out 'before-save.json'
|
||||
$result=Invoke-WelaWecState Apply -PlanPath (Join-Path $planResult.OutputPath 'plan.json') -PlanHash $planResult.PlanHash -OutputPath $out
|
||||
Assert ($result.ExitCode -eq 0 -and $result.BeforeEnabled -and -not $result.DesiredEnabled -and (Get-WelaWecStateDefinition $script:xml @($sid)).WholeKey -ceq $before.WholeKey) 'Explicit disable restores exact original XML semantics'
|
||||
Reject {Invoke-WelaWecState -Id $id -SourceSids @($sid) -State Disabled -OutputPath $root} 'new directory'
|
||||
$one=Get-WelaWecStateContext;$two=Get-WelaWecStateContext;$two.Reader.TokenStatistics=('22'*8)+$two.Reader.TokenStatistics.Substring(16)
|
||||
Assert ((Get-WelaWecStateReviewKey $one) -ceq (Get-WelaWecStateReviewKey $two)) 'Different token objects in the same logon can use a reviewed plan'
|
||||
$two.Reader.TokenStatistics='22'*56
|
||||
Assert ((Get-WelaWecStateReviewKey $one) -cne (Get-WelaWecStateReviewKey $two)) 'Different actual logon cannot reuse a reviewed plan'
|
||||
}finally{Remove-Item -LiteralPath $root -Recurse -Force}
|
||||
Write-Host "WEC state tests passed: $count assertions."
|
||||
@@ -0,0 +1,112 @@
|
||||
param([switch]$AllowDisposableSubscription)
|
||||
$ErrorActionPreference='Stop'
|
||||
if([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not $AllowDisposableSubscription -or $env:GITHUB_ACTIONS -ne 'true' -or $env:RUNNER_ENVIRONMENT -ne 'github-hosted'){throw 'Explicit disposable GitHub-hosted Windows subscription opt-in required.'}
|
||||
$repo=Split-Path $PSScriptRoot -Parent
|
||||
Import-Module "$repo/modules/WefSubscriptions.psm1" -Force
|
||||
Import-Module "$repo/modules/AuditProfiles.psm1" -Force
|
||||
. "$repo/scripts/Configuration.ps1"
|
||||
. "$repo/scripts/ControlApplicability.ps1"
|
||||
. "$repo/scripts/WefArrival.ps1"
|
||||
. "$repo/scripts/WecUpdate.ps1"
|
||||
. "$repo/scripts/WecRuntime.ps1"
|
||||
. "$repo/scripts/WecState.ps1"
|
||||
$count=0
|
||||
function Assert($Value,$Message){if(-not $Value){throw $Message};$script:count++}
|
||||
function Key($Value){ConvertTo-Json -InputObject $Value -Depth 16 -Compress}
|
||||
function ServiceState {Get-CimInstance Win32_Service -Filter "Name='Wecsvc'"|Select-Object Name,State,StartMode}
|
||||
function ChannelState {
|
||||
$c=[Diagnostics.Eventing.Reader.EventLogConfiguration]::new('ForwardedEvents')
|
||||
try {[pscustomobject]@{Enabled=$c.IsEnabled;Mode=[string]$c.LogMode;MaximumBytes=$c.MaximumSizeInBytes;Path=$c.LogFilePath;SecurityDescriptor=$c.SecurityDescriptor}}finally{$c.Dispose()}
|
||||
}
|
||||
function Set-ChannelEnabled([bool]$Enabled){$c=[Diagnostics.Eventing.Reader.EventLogConfiguration]::new('ForwardedEvents');try{$c.IsEnabled=$Enabled;$c.SaveChanges()}finally{$c.Dispose()}}
|
||||
function Subscriptions {@((Invoke-WelaNative 'wecutil.exe' @('es')).Output|ForEach-Object {$_.ToString().Trim()}|Where-Object {$_})}
|
||||
function Invoke-Cli {
|
||||
param([string[]]$Arguments,[string]$Output,[bool]$Success=$true)
|
||||
$engine=(Get-Process -Id $PID).Path
|
||||
$prior=$ErrorActionPreference;try{$ErrorActionPreference='Continue';$text=@(&$engine -NoLogo -NoProfile -NonInteractive -File "$repo/WELA.ps1" wec-state @Arguments -WecStateOutputPath $Output 2>&1);$code=$LASTEXITCODE}finally{$ErrorActionPreference=$prior}
|
||||
Assert (($code -eq 0) -eq $Success) "Public CLI exit $code : $($text -join ' ')"
|
||||
$manifest=Join-Path $Output 'manifest.json';Assert (Test-Path $manifest) 'Public command emitted actual durable result'
|
||||
Get-Content -LiteralPath $manifest -Raw|ConvertFrom-Json
|
||||
}
|
||||
$beforeService=ServiceState;$serviceKey='HKLM:\SYSTEM\CurrentControlSet\Services\Wecsvc';$beforeDelayed=Get-WelaRegistryState $serviceKey DelayedAutoStart
|
||||
if($beforeService.State -notin @('Running','Stopped') -or $beforeService.StartMode -notin @('Auto','Manual','Disabled')){throw 'Stable Wecsvc state required.'}
|
||||
$nonce=[guid]::NewGuid().ToString('N');$id='WELA-State-Test-'+$nonce;$description='Owned state '+([string][char]0x65e5)+([string][char]0x672c)+([string][char]0x8a9e)+' '+$nonce;$changedDescription=$description
|
||||
$sid='S-1-5-21-111111111-222222222-333333333-1234'
|
||||
$root=Join-Path $env:RUNNER_TEMP ('wela-wec-state-'+$nonce);$null=New-Item -ItemType Directory $root
|
||||
$created=$false;$beforeIds=$null;$primary=$null;$beforeChannel=ChannelState
|
||||
try {
|
||||
if($beforeService.StartMode -eq 'Disabled'){Set-Service Wecsvc -StartupType Manual}
|
||||
if($beforeService.State -eq 'Stopped'){Start-Service Wecsvc}
|
||||
if(-not $beforeChannel.Enabled){Set-ChannelEnabled $true}
|
||||
$duringChannel=ChannelState
|
||||
Assert ($duringChannel.Enabled) 'Disposable fixture enabled only destination channel prerequisite'
|
||||
[pscustomobject]@{Destination=$duringChannel;WinRM=(Get-CimInstance Win32_Service -Filter "Name='WinRM'"|Select-Object Name,State,StartMode);Wecsvc=(ServiceState)}|ConvertTo-Json -Depth 8|Set-Content -LiteralPath (Join-Path $root 'fixture-prerequisites.json') -Encoding UTF8
|
||||
$beforeIds=@(Subscriptions);if($beforeIds -contains $id){throw 'Unique ID already exists.'}
|
||||
$query='<QueryList><Query Id="0" Path="Application"><Select Path="Application">*[System[(EventID=1)]]</Select></Query></QueryList>'
|
||||
$xml=@"
|
||||
<Subscription xmlns="http://schemas.microsoft.com/2006/03/windows/events/subscription"><SubscriptionId>$id</SubscriptionId><SubscriptionType>SourceInitiated</SubscriptionType><Description>$description</Description><Enabled>false</Enabled><Uri>http://schemas.microsoft.com/wbem/wsman/1/windows/EventLog</Uri><ConfigurationMode>Normal</ConfigurationMode><Query><![CDATA[$query]]></Query><ReadExistingEvents>false</ReadExistingEvents><TransportName>HTTP</TransportName><ContentFormat>Events</ContentFormat><Locale Language="en-US"/><LogFile>ForwardedEvents</LogFile><AllowedSourceDomainComputers>$(Get-WelaWefAuthorization @($sid))</AllowedSourceDomainComputers></Subscription>
|
||||
"@
|
||||
$xmlPath=Join-Path $root 'owned.xml';[IO.File]::WriteAllText($xmlPath,$xml);$created=$true;$null=Invoke-WelaNative 'wecutil.exe' @('cs',$xmlPath)
|
||||
$before=Read-WelaWecStateDefinition $id @($sid);$duringService=ServiceState
|
||||
Assert (-not $before.Enabled -and $before.Description -ceq $description) 'Real owned disabled subscription preserves Unicode description'
|
||||
Initialize-WelaWecStateNative
|
||||
$missing=$false;try{$unexpected=[Wela.WecState.Edit]::new($id+'-absent');$unexpected.Dispose()}catch{$missing=$true}
|
||||
Assert ($missing -and @(Subscriptions) -notcontains ($id+'-absent')) 'Native existing-only open never creates missing subscription'
|
||||
$enablePlan=$null
|
||||
foreach($state in @('Disabled','Enabled','Enabled','Disabled','Disabled')){
|
||||
$index=$count;$out=Join-Path $root ("plan-$index")
|
||||
$prior=Read-WelaWecStateDefinition $id @($sid)
|
||||
$plan=Invoke-Cli -Arguments @('-WecStateId',$id,'-WecStateSourceSid',$sid,'-WecStateDesired',$state) -Output $out
|
||||
Assert ($plan.Status -eq 'ReviewRequired' -and -not $plan.NativeSaveAttempted) 'Public plan never changes Enabled'
|
||||
Assert ((Read-WelaWecStateDefinition $id @($sid)).WholeKey -ceq $prior.WholeKey) 'Plan preserved complete native subscription'
|
||||
$planPath=Join-Path $out 'plan.json'
|
||||
$apply=Invoke-Cli -Arguments @('-WecStateAction','Apply','-WecStatePlanPath',$planPath,'-WecStatePlanHash',$plan.PlanHash) -Output (Join-Path $root ("apply-$index"))
|
||||
$expected=($state -eq 'Enabled');$changed=($prior.Enabled -ne $expected)
|
||||
Assert ($apply.NativeSaveAttempted -eq $changed -and $apply.Status -eq $(if($changed){'StateChangedAndVerified'}else{'AlreadyMatches'})) 'Only an actual state transition invokes EcSaveSubscription'
|
||||
$after=Read-WelaWecStateDefinition $id @($sid)
|
||||
Assert ($after.Enabled -eq $expected -and $after.PreservedKey -ceq $before.PreservedKey) 'Native readback differs only in Enabled'
|
||||
Assert ($apply.ReadyRuleCredit -eq 0 -and $apply.BookmarkContinuity -eq 'Not established' -and $null -ne $apply.RuntimeAfter) 'Separate native runtime observation supplies no delivery or bookmark claim'
|
||||
foreach($artifact in $apply.Artifacts){Assert ((Get-FileHash -LiteralPath (Join-Path $apply.OutputPath $artifact.Name)).Hash.ToLowerInvariant() -ceq $artifact.Sha256) 'Saved native artifacts match hashes'}
|
||||
if($changed -and $expected){
|
||||
$enablePlan=$plan
|
||||
$stale=Invoke-Cli -Arguments @('-WecStateAction','Apply','-WecStatePlanPath',$planPath,'-WecStatePlanHash',$plan.PlanHash) -Output (Join-Path $root 'stale-enabled-plan') -Success $false
|
||||
Assert ($stale.Status -eq 'Refused' -and -not $stale.NativeSaveAttempted -and $stale.Diagnostic -match 'differs') 'A completed transition cannot replay its stale pre-state'
|
||||
Assert ((Read-WelaWecStateDefinition $id @($sid)).WholeKey -ceq $after.WholeKey) 'Stale plan refusal preserved enabled definition'
|
||||
}
|
||||
}
|
||||
Assert ((Read-WelaWecStateDefinition $id @($sid)).WholeKey -ceq $before.WholeKey) 'Explicit disable restored entire original native definition'
|
||||
# A separately opened native handle sees a changed description and refuses save.
|
||||
$edit=New-WelaWecStateEdit $before
|
||||
try {
|
||||
$null=Invoke-WelaNative 'wecutil.exe' @('ss',$id,('/d:'+($description+' drift')))
|
||||
$refused=$false;try{$edit.Save($true)}catch{$refused=$true}
|
||||
Assert ($refused -and -not $edit.SaveAttempted) 'Fresh native handle guards description drift before saving'
|
||||
Assert (-not(Read-WelaWecStateDefinition $id @($sid)).Enabled) 'Native drift refusal did not enable subscription'
|
||||
}finally{$edit.Dispose();$null=Invoke-WelaNative 'wecutil.exe' @('ss',$id,('/d:'+$description))}
|
||||
Assert ((Read-WelaWecStateDefinition $id @($sid)).WholeKey -ceq $before.WholeKey) 'Native drift fixture restored original description'
|
||||
Assert ((Key (ChannelState)) -ceq (Key $duringChannel)) 'Product command preserved complete channel configuration'
|
||||
Assert ((Key (ServiceState)) -ceq (Key $duringService)) 'Product command preserved service state/startup'
|
||||
Write-Host "Native WEC state passed $count assertions on $([Environment]::OSVersion.Version), PowerShell $($PSVersionTable.PSVersion). No real source, listener or bookmark claim."
|
||||
}catch{$primary=$_}
|
||||
finally {
|
||||
$errors=@()
|
||||
try {
|
||||
if($created -and @(Subscriptions) -contains $id){$raw=Read-WelaWecSubscriptionXml $id;$doc=Read-WelaWefXml $raw;$ns=New-Object Xml.XmlNamespaceManager($doc.NameTable);$ns.AddNamespace('s',$doc.DocumentElement.NamespaceURI);$observed=$doc.SelectSingleNode('/s:Subscription/s:Description',$ns).InnerText;if($observed -cnotin @($description,$changedDescription)){throw 'Fixture ownership changed; refusing deletion.'};$null=Invoke-WelaNative 'wecutil.exe' @('ds',$id)}
|
||||
if($null -ne $beforeIds -and (Key @($beforeIds|Sort-Object)) -cne (Key @(Subscriptions|Sort-Object))){throw 'Subscription inventory differs after cleanup.'}
|
||||
}catch{$errors+=$_.Exception.Message}
|
||||
try {
|
||||
if((ChannelState).Enabled -ne $beforeChannel.Enabled){Set-ChannelEnabled $beforeChannel.Enabled}
|
||||
if((Key (ChannelState)) -cne (Key $beforeChannel)){throw 'Original destination channel configuration differs.'}
|
||||
}catch{$errors+=$_.Exception.Message}
|
||||
try {
|
||||
if($beforeService.State -eq 'Stopped' -and (Get-Service Wecsvc).Status -ne 'Stopped'){Stop-Service Wecsvc}
|
||||
if($beforeService.StartMode -eq 'Disabled'){Set-Service Wecsvc -StartupType Disabled}
|
||||
if((Key (Get-WelaRegistryState $serviceKey DelayedAutoStart)) -cne (Key $beforeDelayed)){if($beforeDelayed.ValueExists){$null=New-ItemProperty -LiteralPath $serviceKey -Name DelayedAutoStart -Value $beforeDelayed.Value -PropertyType $beforeDelayed.Type -Force}else{Remove-ItemProperty -LiteralPath $serviceKey -Name DelayedAutoStart -ErrorAction Stop}}
|
||||
if((Key (ServiceState)) -cne (Key $beforeService) -or (Key (Get-WelaRegistryState $serviceKey DelayedAutoStart)) -cne (Key $beforeDelayed)){throw 'Original Wecsvc state/startup differs.'}
|
||||
}catch{$errors+=$_.Exception.Message}
|
||||
if($errors.Count){throw "Fixture cleanup failed; retained $root : $($errors -join '; '); primary failure: $primary"}
|
||||
[pscustomobject]@{Passed=($null -eq $primary);Assertions=$count;OriginalSubscriptionsRestored=$true;OriginalServiceRestored=$true;OriginalChannelRestored=$true;Computer=[Environment]::MachineName;Engine=$PSVersionTable.PSVersion.ToString();Scope='Owned native Enabled transitions only; no real source, listener, forwarding or bookmark proof'}|ConvertTo-Json|Set-Content -LiteralPath (Join-Path $root 'acceptance.json') -Encoding UTF8
|
||||
Write-Host 'Original subscription inventory and Wecsvc state/startup restored.'
|
||||
}
|
||||
if($primary){throw $primary}
|
||||
$global:LASTEXITCODE=0
|
||||
Reference in new issue
Block a user