mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-08 23:35:28 +02:00
Activate native SMB audit runtime switches explicitly (#441)
* Add explicit native SMB runtime audit activation * Select explicit PowerShell workflow shells and link PR changelog * Clear expected refusal child exit codes after assertions * Retain native SMB command provenance in capability diagnostics * Bind SMB command guards to observed native CDXML module identities
This commit is contained in:
1 parent
b4fb77da02
commit
9d03a19082
13 files changed
+547
-2
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user