Files
Win11Debloat/Tests/User-HiveHelpers.Tests.ps1
T

150 lines
7.1 KiB
PowerShell

BeforeAll {
function Normalize-UserLookupValue { param($Value) ([string]$Value).Trim() }
function Resolve-UserProfileContext { param($UserName) $null }
function reg { param($Action, $Mount, $Path) $global:LASTEXITCODE = 0 }
. (Join-Path $PSScriptRoot '..\Scripts\Helpers\User-HiveHelpers.ps1')
}
Describe 'New-TargetUserHiveContext' {
It 'projects user information and defaults an empty mount name' {
$result = New-TargetUserHiveContext -TargetUserName 'Alice' -UserContext ([PSCustomObject]@{ UserSid = 'S-1'; ProfilePath = 'C:\Users\Alice' }) -HiveDatPath 'C:\Users\Alice\NTUSER.DAT' -MountName ''
$result.TargetUserName | Should -Be 'Alice'
$result.UserSid | Should -Be 'S-1'
$result.MountName | Should -Be 'Default'
$result.WasAlreadyLoaded | Should -BeFalse
}
}
Describe 'Resolve-TargetUserHiveContext' {
BeforeEach {
Mock Normalize-UserLookupValue { param($Value) ([string]$Value).Trim() }
Mock Resolve-UserProfileContext { [PSCustomObject]@{ UserSid = 'S-1-5-21-123'; ProfilePath = $TestDrive } }
Mock Test-Path { $true }
}
It 'uses an already loaded SID hive for a normal user' {
$result = Resolve-TargetUserHiveContext -TargetUserName ' Alice '
$result.MountName | Should -Be 'S-1-5-21-123'
$result.WasAlreadyLoaded | Should -BeTrue
$result.WasLoadedByScript | Should -BeFalse
}
It 'uses the temporary Default mount for <Case>' -ForEach @(
@{ Case = 'an unloaded user'; User = 'Alice'; Sid = '' }
@{ Case = 'the default profile'; User = 'Default'; Sid = 'S-1-5-21-123' }
) {
Mock Resolve-UserProfileContext { [PSCustomObject]@{ UserSid = $Sid; ProfilePath = $TestDrive } }
Mock Test-Path { param($LiteralPath) $LiteralPath -like '*NTUSER.DAT' }
$result = Resolve-TargetUserHiveContext -TargetUserName $User
$result.MountName | Should -Be 'Default'
$result.WasAlreadyLoaded | Should -BeFalse
}
It 'rejects <Case>' -ForEach @(
@{ Case = 'an empty user name'; UserName = ' '; Setup = 'None'; ExpectedError = 'Target user name for registry hive resolution is empty.' }
@{ Case = 'an unresolved profile'; UserName = 'Missing'; Setup = 'MissingProfile'; ExpectedError = "Unable to resolve profile path for target user 'Missing'." }
@{ Case = 'a missing hive file'; UserName = 'Alice'; Setup = 'MissingHive'; ExpectedError = 'Unable to find target user hive at *' }
) {
if ($Setup -eq 'MissingProfile') {
Mock Resolve-UserProfileContext { $null }
}
elseif ($Setup -eq 'MissingHive') {
Mock Resolve-UserProfileContext { [PSCustomObject]@{ UserSid = 'S-1'; ProfilePath = $TestDrive } }
Mock Test-Path { $false }
}
{ Resolve-TargetUserHiveContext -TargetUserName $UserName } | Should -Throw $ExpectedError
}
}
Describe 'Resolve-LoadedTargetUserHiveContext' {
It '<Case>' -ForEach @(
@{ Case = 'returns a loaded context when a SID hive is mounted'; Sid = 'S-1'; HiveMounted = $true; ExpectedLoaded = $true }
@{ Case = 'returns null when the SID is empty'; Sid = ''; HiveMounted = $true; ExpectedLoaded = $false }
@{ Case = 'returns null when the SID hive is not mounted'; Sid = 'S-1'; HiveMounted = $false; ExpectedLoaded = $false }
) {
$input = [PSCustomObject]@{ TargetUserName = 'Alice'; UserSid = $Sid; ProfilePath = 'C:\Users\Alice'; HiveDatPath = 'C:\Users\Alice\NTUSER.DAT' }
Mock Test-Path { $HiveMounted }
$result = Resolve-LoadedTargetUserHiveContext -HiveContext $input
if ($ExpectedLoaded) {
$result.WasAlreadyLoaded | Should -BeTrue
}
else {
$result | Should -BeNullOrEmpty
}
}
}
Describe 'Invoke-WithTargetUserHive' {
BeforeEach {
$script:RegistryTargetHiveMountName = 'Previous'
$script:context = [PSCustomObject]@{
TargetUserName = 'Alice'; UserSid = 'S-1'; ProfilePath = 'C:\Users\Alice'; HiveDatPath = 'C:\Users\Alice\NTUSER.DAT'
MountName = 'Temporary'; WasAlreadyLoaded = $false; WasLoadedByScript = $false
}
Mock Resolve-TargetUserHiveContext { $script:context }
Mock Resolve-LoadedTargetUserHiveContext { $null }
Mock reg { $global:LASTEXITCODE = 0 }
Mock Write-Warning {}
}
It 'loads, executes, passes context, unloads, and restores the previous mount name' {
$result = Invoke-WithTargetUserHive -TargetUserName 'Alice' -ArgumentObject 'payload' -PassHiveContext -ScriptBlock {
param($Argument, $Context)
"$Argument|$($Context.MountName)|$script:RegistryTargetHiveMountName"
}
$result | Should -Be 'payload|Temporary|Temporary'
Should -Invoke reg -Times 1 -Exactly -ParameterFilter { $Action -eq 'load' }
Should -Invoke reg -Times 1 -Exactly -ParameterFilter { $Action -eq 'unload' }
$script:RegistryTargetHiveMountName | Should -Be 'Previous'
}
It 'does not load or unload a hive that was already mounted' {
$script:context.WasAlreadyLoaded = $true
Invoke-WithTargetUserHive -TargetUserName 'Alice' -ScriptBlock { 'done' } | Should -Be 'done'
Should -Invoke reg -Times 0 -Exactly
}
It 'unloads and restores state when the scriptblock throws' {
{ Invoke-WithTargetUserHive -TargetUserName 'Alice' -ScriptBlock { throw 'script failed' } } | Should -Throw 'script failed'
Should -Invoke reg -Times 1 -Exactly -ParameterFilter { $Action -eq 'unload' }
$script:RegistryTargetHiveMountName | Should -Be 'Previous'
}
It 'throws when loading fails without an already-loaded SID fallback' {
Mock reg { if ($Action -eq 'load') { $global:LASTEXITCODE = 5 } }
{ Invoke-WithTargetUserHive -TargetUserName 'Alice' -ScriptBlock { 'never' } } |
Should -Throw "Failed to load target user hive 'C:\Users\Alice\NTUSER.DAT' (exit code: 5)."
Should -Invoke reg -Times 0 -Exactly -ParameterFilter { $Action -eq 'unload' }
}
It 'uses a loaded SID fallback after a load race' {
Mock reg { if ($Action -eq 'load') { $global:LASTEXITCODE = 5 } }
Mock Resolve-LoadedTargetUserHiveContext {
[PSCustomObject]@{ TargetUserName = 'Alice'; UserSid = 'S-1'; ProfilePath = 'C:\Users\Alice'; HiveDatPath = 'C:\Users\Alice\NTUSER.DAT'; MountName = 'S-1'; WasAlreadyLoaded = $true; WasLoadedByScript = $false }
}
Invoke-WithTargetUserHive -TargetUserName 'Alice' -ScriptBlock { $script:RegistryTargetHiveMountName } | Should -Be 'S-1'
Should -Invoke reg -Times 0 -Exactly -ParameterFilter { $Action -eq 'unload' }
}
It 'warns on unload failure without discarding the scriptblock result' {
Mock reg {
if ($Action -eq 'load') { $global:LASTEXITCODE = 0 }
if ($Action -eq 'unload') { $global:LASTEXITCODE = 5 }
}
Invoke-WithTargetUserHive -TargetUserName 'Alice' -ScriptBlock { 'result' } | Should -Be 'result'
Should -Invoke Write-Warning -Times 1 -Exactly -ParameterFilter { $Message -like "Failed to unload registry hive*" }
}
}