$ErrorActionPreference = "Stop" $supportPath = Join-Path $PSScriptRoot "MinerOpsNativeHarnessSupport.ps1" . $supportPath function Assert-True { param( [Parameter(Mandatory = $true)][bool]$Condition, [Parameter(Mandatory = $true)][string]$Message ) if (-not $Condition) { throw $Message } } function Assert-Throws { param( [Parameter(Mandatory = $true)][scriptblock]$Action, [Parameter(Mandatory = $true)][string]$Pattern ) try { & $Action | Out-Null } catch { if ($_.Exception.Message -notmatch $Pattern) { throw "Expected '$Pattern', got '$($_.Exception.Message)'." } return } throw "Expected action to fail with '$Pattern'." } $script:Passed = 0 function Invoke-HarnessTest { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][scriptblock]$Action ) & $Action $script:Passed++ Write-Host "PASS: $Name" } function New-PassingSwapPostconditions { return [pscustomobject]@{ TargetExists = $true TargetIdentityUnchanged = $true TargetContentUnchanged = $true TargetAclTrusted = $true TargetAclUnchanged = $true TargetOwnerTrusted = $true TargetOwnerUnchanged = $true TargetSingleLink = $true SourceExists = $true SourceIdentityUnchanged = $true SourceContentUnchanged = $true AttackerSourceNotPromoted = $true TargetDeniedStandardUserRead = $true TargetDeniedStandardUserModify = $true TargetDeniedStandardUserDelete = $true TargetDeniedStandardUserRename = $true NoAttackerReadableBackup = $true } } function New-ReadySwapFixture { return [pscustomobject]@{ SourceExists = $true TargetExists = $true PathsDistinct = $true SourceCreatedByStandardUser = $true SourceOwnedByStandardUser = $true SourceReadable = $true SourceModifiable = $true SourceRenameable = $true SourceDeleteCapable = $true SourceReplaceAccessVerified = $true TargetAclTrusted = $true TargetOwnerTrusted = $true TargetIdentityCaptured = $true SourceIdentityCaptured = $true FileIdentitiesDistinct = $true BackupArgumentValid = $true BackupPathsDistinct = $true BackupExpectedStateVerified = $true BackupConfidentialitySafe = $true SameVolume = $true NtfsVolume = $true } } function New-SwapResult { param([Parameter(Mandatory = $true)][string]$Status) return [pscustomobject]@{ operation = "swap" status = $Status exception_type = "" hresult = 0 outer_exception_type = "" outer_hresult = 0 leaf_exception_type = "" leaf_hresult = 0 native_error = $null exception_chain_types = @() replace_invoked = $true } } Invoke-HarnessTest "helper classifies success and status exit codes" { Assert-True ((Get-MinerOpsNativeStatusExitCode -Status "succeeded") -eq 0) ` "Success exit code changed." Assert-True ((Get-MinerOpsNativeStatusExitCode -Status "security_denied") -eq 5) ` "Security-denial exit code changed." Assert-True ((Get-MinerOpsNativeStatusExitCode -Status "fixture_error") -eq 6) ` "Fixture-error exit code changed." Assert-True ((Get-MinerOpsNativeStatusExitCode -Status "unexpected_failure") -eq 7) ` "Unexpected-failure exit code changed." Assert-True ((Get-MinerOpsNativeStatusExitCode ` -Status "security_denied_requires_postconditions") -eq 8) ` "Postcondition-gated security exit code changed." Assert-True ((Get-MinerOpsNativeStatusExitCode ` -Status "api_precondition_failure") -eq 9) ` "API-precondition exit code changed." } Invoke-HarnessTest "helper distinguishes security, fixture, and unexpected failures" { $denied = [UnauthorizedAccessException]::new("denied") Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation read -Exception $denied) -ceq "security_denied") ` "UnauthorizedAccessException was not a security denial." $unableToRemove = [IO.IOException]::new("expected", -2147023721) Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation swap -Exception $unableToRemove) -ceq "security_denied_requires_postconditions") ` "ERROR_UNABLE_TO_REMOVE_REPLACED was not postcondition-gated." $missing = [IO.FileNotFoundException]::new("missing") Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation swap -Exception $missing) -ceq "fixture_error") ` "Missing swap source was not a fixture error." $invalidArgument = [ArgumentException]::new("illegal backup path") Assert-True ($invalidArgument.HResult -eq -2147024809) ` "ArgumentException no longer carries 0x80070057." Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation swap -Exception $invalidArgument) -ceq "fixture_error") ` "ERROR_INVALID_PARAMETER-like argument failure was not a fixture error." $crossVolume = [IO.IOException]::new("different volume", -2147024879) Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation swap -Exception $crossVolume) -ceq "api_precondition_failure") ` "Cross-volume swap was not an API-precondition failure." $partialMove = [IO.IOException]::new("unexpected", -2147023720) Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation swap -Exception $partialMove) -ceq "unexpected_failure") ` "Partial ReplaceFile failure was accepted." } Invoke-HarnessTest "helper unwraps MethodInvocationException security denial" { $denied = [UnauthorizedAccessException]::new("denied") $wrapped = [Management.Automation.MethodInvocationException]::new( "method invocation failed", $denied ) Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation read -Exception $wrapped) -ceq "security_denied") ` "Wrapped UnauthorizedAccessException was not a security denial." } Invoke-HarnessTest "helper unwraps multiple PowerShell exception layers" { $denied = [UnauthorizedAccessException]::new("denied") $methodWrapper = [Management.Automation.MethodInvocationException]::new( "method invocation failed", $denied ) $runtimeWrapper = [Management.Automation.RuntimeException]::new( "runtime failed", $methodWrapper ) Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation read -Exception $runtimeWrapper) -ceq "security_denied") ` "Nested PowerShell wrappers were not unwrapped." } Invoke-HarnessTest "helper decodes only HRESULT_FROM_WIN32 values" { Assert-True ((Get-MinerOpsNativeWin32Error -HResult -2147024891) -eq 5) ` "0x80070005 did not decode as ERROR_ACCESS_DENIED." $accessDenied = [IO.IOException]::new("access denied", -2147024891) Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation read -Exception $accessDenied) -ceq "security_denied") ` "Win32-derived ERROR_ACCESS_DENIED was not security evidence." Assert-True ($null -eq ( Get-MinerOpsNativeWin32Error -HResult -2146233087 )) "CLR HRESULT 0x80131501 produced a bogus native error." } Invoke-HarnessTest "unrelated wrapped IOException is not a security denial" { $ioFailure = [IO.IOException]::new("unrelated") $wrapped = [Management.Automation.MethodInvocationException]::new( "method invocation failed", $ioFailure ) Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation read -Exception $wrapped) -ceq "unexpected_failure") ` "Unrelated wrapped IOException was accepted as security evidence." } Invoke-HarnessTest "wrapped fixture and precondition failures stay distinct" { $missing = [IO.FileNotFoundException]::new("missing") $wrappedMissing = [Management.Automation.MethodInvocationException]::new( "method invocation failed", $missing ) Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation swap -Exception $wrappedMissing) -ceq "fixture_error") ` "Wrapped fixture error changed classification." $crossVolume = [IO.IOException]::new("different volume", -2147024879) $wrappedCrossVolume = [Reflection.TargetInvocationException]::new( "target invocation failed", $crossVolume ) Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation swap -Exception $wrappedCrossVolume) -ceq "api_precondition_failure") ` "Wrapped API precondition changed classification." } Invoke-HarnessTest "exception diagnostics report outer leaf and chain types" { $denied = [UnauthorizedAccessException]::new("denied") $wrapped = [Management.Automation.MethodInvocationException]::new( "method invocation failed", $denied ) $details = Get-MinerOpsNativeExceptionDetails -Exception $wrapped Assert-True ($details.OuterExceptionType -ceq "System.Management.Automation.MethodInvocationException") ` "Exception diagnostics recorded the wrong outer type." Assert-True ($details.LeafExceptionType -ceq "System.UnauthorizedAccessException") ` "Exception diagnostics recorded the wrong leaf type." Assert-True ($details.ExceptionChainTypes.Count -eq 2) ` "Exception diagnostics recorded the wrong chain length." Assert-True (-not $details.ExceptionChainTruncated) ` "A short exception chain was reported as truncated." } Invoke-HarnessTest "exception diagnostics handle null InnerException" { $wrapperOnly = [Management.Automation.RuntimeException]::new( "wrapper without inner exception" ) $details = Get-MinerOpsNativeExceptionDetails -Exception $wrapperOnly Assert-True ($details.OuterExceptionType -ceq $details.LeafExceptionType) ` "Null InnerException did not leave the wrapper as the leaf." Assert-True ($details.ExceptionChainTypes.Count -eq 1) ` "Null InnerException produced an invalid chain." } Invoke-HarnessTest "exception-chain traversal is bounded defensively" { [Exception]$deep = [UnauthorizedAccessException]::new("denied") foreach ($level in 1..40) { $deep = [Exception]::new("wrapper $level", $deep) } $details = Get-MinerOpsNativeExceptionDetails -Exception $deep Assert-True ($details.ExceptionChainTypes.Count -eq 32) ` "Deep exception traversal did not stop at its bound." Assert-True ([bool]$details.ExceptionChainTruncated) ` "Deep exception traversal did not report truncation." Assert-True ((Get-MinerOpsNativeExceptionStatus ` -Operation read -Exception $deep) -ceq "unexpected_failure") ` "Truncated exception evidence did not fail closed." } Invoke-HarnessTest "swap probe parses and passes an explicit native null" { $probePath = Join-Path $PSScriptRoot ` "Invoke-MinerOpsStandardUserProbe.ps1" $tokens = $null $parseErrors = $null [Management.Automation.Language.Parser]::ParseFile( $probePath, [ref]$tokens, [ref]$parseErrors ) | Out-Null Assert-True ($parseErrors.Count -eq 0) ` "Standard-user probe has parser errors." $probeSource = [IO.File]::ReadAllText($probePath) Assert-True ($probeSource -match '\[NullString\]::Value') ` "Swap probe does not pass an explicit native null backup argument." Assert-True ($probeSource -notmatch 'File\]::Replace\([^\)]*\$null') ` "Swap probe regressed to a bare PowerShell null backup argument." Assert-True ($probeSource -match 'backup-producing swap fixtures are prohibited') ` "Swap probe no longer prohibits confidentiality-unsafe backups." } Invoke-HarnessTest "NullString preserves native null for a .NET string" { if (-not ("MinerOpsNativeHarnessNullStringProbe" -as [type])) { Add-Type -TypeDefinition @' public static class MinerOpsNativeHarnessNullStringProbe { public static bool IsNull(string value) { return value == null; } } '@ } Assert-True (-not ( [MinerOpsNativeHarnessNullStringProbe]::IsNull($null) )) "A bare PowerShell null was not coerced to String.Empty." Assert-True ( [MinerOpsNativeHarnessNullStringProbe]::IsNull([NullString]::Value) ) "NullString did not preserve native null for a .NET string." } Invoke-HarnessTest "same source and destination is a fixture failure" { $fixture = New-ReadySwapFixture $fixture.PathsDistinct = $false $classification = Get-MinerOpsNativeSwapFixtureStatus -Fixture $fixture Assert-True ($classification.Status -ceq "fixture_error") ` "Equal source and destination paths were accepted." } Invoke-HarnessTest "missing source is a fixture failure" { $fixture = New-ReadySwapFixture $fixture.SourceExists = $false $classification = Get-MinerOpsNativeSwapFixtureStatus -Fixture $fixture Assert-True ($classification.Status -ceq "fixture_error") ` "Missing source was accepted." } Invoke-HarnessTest "missing target is a fixture failure" { $fixture = New-ReadySwapFixture $fixture.TargetExists = $false $classification = Get-MinerOpsNativeSwapFixtureStatus -Fixture $fixture Assert-True ($classification.Status -ceq "fixture_error") ` "Missing target was accepted." } Invoke-HarnessTest "invalid backup argument is a fixture failure" { foreach ($property in @( "BackupArgumentValid", "BackupPathsDistinct", "BackupExpectedStateVerified", "BackupConfidentialitySafe" )) { $fixture = New-ReadySwapFixture $fixture.$property = $false $classification = Get-MinerOpsNativeSwapFixtureStatus -Fixture $fixture Assert-True ($classification.Status -ceq "fixture_error") ` "Invalid backup property $property was accepted." } } Invoke-HarnessTest "cross-volume limitation is not a security pass" { $fixture = New-ReadySwapFixture $fixture.SameVolume = $false $classification = Get-MinerOpsNativeSwapFixtureStatus -Fixture $fixture Assert-True ($classification.Status -ceq "api_precondition_failure") ` "Cross-volume fixture was not an API-precondition failure." } Invoke-HarnessTest "valid controlled same-volume source is attack-ready" { $classification = Get-MinerOpsNativeSwapFixtureStatus ` -Fixture (New-ReadySwapFixture) Assert-True ($classification.Status -ceq "ready") ` "Valid same-volume fixture was not attack-ready." } Invoke-HarnessTest "source ownership and control are mandatory" { foreach ($property in @( "SourceCreatedByStandardUser", "SourceOwnedByStandardUser", "SourceReadable", "SourceModifiable", "SourceRenameable", "SourceDeleteCapable", "SourceReplaceAccessVerified" )) { $fixture = New-ReadySwapFixture $fixture.$property = $false $classification = Get-MinerOpsNativeSwapFixtureStatus -Fixture $fixture Assert-True ($classification.Status -ceq "fixture_error") ` "Missing source-control proof $property was accepted." } } Invoke-HarnessTest "invalid swap fixture cannot pass as security" { Assert-Throws -Pattern "fixture was invalid" -Action { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult -Status "fixture_error") ` -Postconditions (New-PassingSwapPostconditions) } } Invoke-HarnessTest "unrelated swap API precondition cannot pass as security" { Assert-Throws -Pattern "unrelated API precondition" -Action { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult -Status "api_precondition_failure") ` -Postconditions (New-PassingSwapPostconditions) } } Invoke-HarnessTest "successful replacement is a security failure" { Assert-Throws -Pattern "completed the protected replacement" -Action { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult -Status "succeeded") ` -Postconditions (New-PassingSwapPostconditions) } } Invoke-HarnessTest "security denial passes only with all postconditions" { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult -Status "security_denied") ` -Postconditions (New-PassingSwapPostconditions) Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult ` -Status "security_denied_requires_postconditions") ` -Postconditions (New-PassingSwapPostconditions) $failed = New-PassingSwapPostconditions $failed.TargetAclTrusted = $false Assert-Throws -Pattern "target ACL is not trusted" -Action { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult -Status "security_denied") ` -Postconditions $failed } } Invoke-HarnessTest "denial with changed target identity is a security failure" { $failed = New-PassingSwapPostconditions $failed.TargetIdentityUnchanged = $false Assert-Throws -Pattern "target identity changed" -Action { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult -Status "security_denied") ` -Postconditions $failed } } Invoke-HarnessTest "denial with changed target content is a security failure" { $failed = New-PassingSwapPostconditions $failed.TargetContentUnchanged = $false Assert-Throws -Pattern "target content changed" -Action { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult -Status "security_denied") ` -Postconditions $failed } } Invoke-HarnessTest "denial with changed ACL or owner is a security failure" { foreach ($property in @( "TargetAclTrusted", "TargetAclUnchanged", "TargetOwnerTrusted", "TargetOwnerUnchanged" )) { $failed = New-PassingSwapPostconditions $failed.$property = $false Assert-Throws -Pattern "target (ACL|owner)" -Action { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult -Status "security_denied") ` -Postconditions $failed } } } Invoke-HarnessTest "attacker-readable protected backup is a security failure" { $failed = New-PassingSwapPostconditions $failed.NoAttackerReadableBackup = $false Assert-Throws -Pattern "through a backup" -Action { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result (New-SwapResult -Status "security_denied") ` -Postconditions $failed } } Invoke-HarnessTest "denial must record that File.Replace was reached" { $result = New-SwapResult -Status "security_denied" $result.replace_invoked = $false Assert-Throws -Pattern "did not reach System.IO.File.Replace" -Action { Assert-MinerOpsNativeSwapSecurityOutcome ` -Result $result -Postconditions (New-PassingSwapPostconditions) } } $initialState = Get-MinerOpsNativeGetItemState try { Invoke-HarnessTest "trace leaves no global function when none existed" { $childScript = Join-Path $PSScriptRoot ` "Test-MinerOpsNativeHarnessGetItemState.ps1" $powerShell = (Get-Process -Id $PID).Path & $powerShell -NoLogo -NoProfile -File $childScript ` -Mode NoExistingFunction -SupportPath $supportPath Assert-True ($LASTEXITCODE -eq 0) ` "No-existing-function child test failed." } Invoke-HarnessTest "trace restores an existing global function exactly" { $childScript = Join-Path $PSScriptRoot ` "Test-MinerOpsNativeHarnessGetItemState.ps1" $powerShell = (Get-Process -Id $PID).Path & $powerShell -NoLogo -NoProfile -File $childScript ` -Mode ExistingFunction -SupportPath $supportPath Assert-True ($LASTEXITCODE -eq 0) ` "Existing-function child test failed." } Invoke-HarnessTest "trace cleanup survives Action failure" { $before = Get-MinerOpsNativeGetItemState Assert-Throws -Pattern "injected trace failure" -Action { Invoke-MinerOpsNativeGetItemTrace -Action { Get-Item -LiteralPath $supportPath | Out-Null throw "injected trace failure" } } Assert-MinerOpsNativeGetItemState -Expected $before ` -Context "throwing trace action" } Invoke-HarnessTest "trace cleanup survives a later assertion failure" { $before = Get-MinerOpsNativeGetItemState try { [void](Invoke-MinerOpsNativeGetItemTrace -Action { Get-Item -LiteralPath $supportPath }) throw "injected post-trace assertion failure" } catch { Assert-True ( $_.Exception.Message -match "post-trace assertion failure" ) "Unexpected post-trace failure." } Assert-MinerOpsNativeGetItemState -Expected $before ` -Context "post-trace assertion failure" } Invoke-HarnessTest "multiple sequential traces preserve command state" { $before = Get-MinerOpsNativeGetItemState foreach ($iteration in 1..3) { $observed = @(Invoke-MinerOpsNativeGetItemTrace -Action { Get-Item -LiteralPath $supportPath }) Assert-True ($observed.Count -eq 1) ` "Sequential trace $iteration recorded the wrong count." Assert-MinerOpsNativeGetItemState -Expected $before ` -Context "sequential trace $iteration" } } Invoke-HarnessTest "simulated top-level failure restores Get-Item" { $before = Get-MinerOpsNativeGetItemState try { try { [void](Invoke-MinerOpsNativeGetItemTrace -Action { Get-Item -LiteralPath $supportPath }) throw "injected top-level harness failure" } finally { Assert-MinerOpsNativeGetItemState -Expected $before ` -Context "simulated harness final cleanup" } } catch { Assert-True ( $_.Exception.Message -match "top-level harness failure" ) "Unexpected simulated top-level failure." } Assert-MinerOpsNativeGetItemState -Expected $before ` -Context "after simulated top-level failure" } } finally { Assert-MinerOpsNativeGetItemState -Expected $initialState ` -Context "portable native-harness tests final cleanup" } Write-Host "$Passed native-harness regression tests passed."