Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Ve

### Fixed

- The PowerShell registry writer (used to apply reg files into a mounted user hive under `-Sysprep`/`-User`, and as the fallback when `reg import` fails) now applies `qword`, `hex(2)` (REG_EXPAND_SZ), and `hex(7)` (REG_MULTI_SZ) values instead of throwing "Unsupported value type". The reg-file parser and the applied-state verifier already handled all three; only the writer was out of step, so a future reg file using one of those types would have failed to apply through that path.
- Unload the target user's registry hive reliably. When a `-Sysprep` or per-user run (apply, verification, or restore-to-another-user) read or wrote the mounted hive through the registry provider, lingering .NET `RegistryKey` handles kept the hive open, so `reg unload` failed and the code only warned while the hive stayed mounted, which can lock the profile. The unload now forces a garbage collection to release those handles and retries once, and only warns if it still cannot unload.

### Added
Expand Down
15 changes: 15 additions & 0 deletions Scripts/Helpers/ApplyRegistryRegFile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,24 @@ function Convert-RegOperationToValueKind {
$value = [BitConverter]::ToInt32([BitConverter]::GetBytes($unsigned), 0)
return @{ Name = $valueName; Kind = [Microsoft.Win32.RegistryValueKind]::DWord; Value = $value }
}
'QWord' {
# SetValue takes a signed Int64; wrap the parsed unsigned value through its bytes so a
# high-bit qword applies with the same bit pattern the parser read.
$unsignedQword = [uint64]$Operation.ValueData
$qwordValue = [BitConverter]::ToInt64([BitConverter]::GetBytes($unsignedQword), 0)
return @{ Name = $valueName; Kind = [Microsoft.Win32.RegistryValueKind]::QWord; Value = $qwordValue }
}
'String' {
return @{ Name = $valueName; Kind = [Microsoft.Win32.RegistryValueKind]::String; Value = [string]$Operation.ValueData }
}
'Hex2' {
# hex(2) is REG_EXPAND_SZ; the parser already decoded the bytes to a string.
return @{ Name = $valueName; Kind = [Microsoft.Win32.RegistryValueKind]::ExpandString; Value = [string]$Operation.ValueData }
}
'Hex7' {
# hex(7) is REG_MULTI_SZ; the parser already decoded the bytes to a string array.
return @{ Name = $valueName; Kind = [Microsoft.Win32.RegistryValueKind]::MultiString; Value = [string[]]$Operation.ValueData }
}
'Binary' {
return @{ Name = $valueName; Kind = [Microsoft.Win32.RegistryValueKind]::Binary; Value = [byte[]]$Operation.ValueData }
}
Expand Down
41 changes: 41 additions & 0 deletions Tests/Unit/Test-RegistryFiles.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,44 @@ Describe 'Registry files' {
}
}
}

Describe 'Reg operation to value kind' {
BeforeAll {
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..') | Select-Object -ExpandProperty Path
. (Join-Path $repoRoot 'Scripts\Helpers\ApplyRegistryRegFile.ps1')
}

It 'maps a DWord, preserving a high-bit value through the sign wrap' {
$result = Convert-RegOperationToValueKind -Operation ([PSCustomObject]@{ ValueName = 'X'; ValueType = 'DWord'; ValueData = [uint32]'0xFFFFFFFF'; KeyPath = 'HKLM:\Test' })
$result.Kind | Should -Be ([Microsoft.Win32.RegistryValueKind]::DWord)
$result.Value | Should -Be (-1)
}

It 'maps a QWord, preserving a high-bit value' {
$result = Convert-RegOperationToValueKind -Operation ([PSCustomObject]@{ ValueName = 'X'; ValueType = 'QWord'; ValueData = [uint64]18446744073709551615; KeyPath = 'HKLM:\Test' })
$result.Kind | Should -Be ([Microsoft.Win32.RegistryValueKind]::QWord)
$result.Value | Should -Be (-1)
}

It 'maps hex(2) to an expandable string' {
$result = Convert-RegOperationToValueKind -Operation ([PSCustomObject]@{ ValueName = 'X'; ValueType = 'Hex2'; ValueData = '%SystemRoot%\test'; KeyPath = 'HKLM:\Test' })
$result.Kind | Should -Be ([Microsoft.Win32.RegistryValueKind]::ExpandString)
$result.Value | Should -Be '%SystemRoot%\test'
}

It 'maps hex(7) to a multi-string' {
$result = Convert-RegOperationToValueKind -Operation ([PSCustomObject]@{ ValueName = 'X'; ValueType = 'Hex7'; ValueData = @('one', 'two'); KeyPath = 'HKLM:\Test' })
$result.Kind | Should -Be ([Microsoft.Win32.RegistryValueKind]::MultiString)
@($result.Value).Count | Should -Be 2
$result.Value[0] | Should -Be 'one'
}

It 'maps a String and Binary' {
(Convert-RegOperationToValueKind -Operation ([PSCustomObject]@{ ValueName = 'X'; ValueType = 'String'; ValueData = 'hi'; KeyPath = 'HKLM:\Test' })).Kind | Should -Be ([Microsoft.Win32.RegistryValueKind]::String)
(Convert-RegOperationToValueKind -Operation ([PSCustomObject]@{ ValueName = 'X'; ValueType = 'Binary'; ValueData = [byte[]](1, 2, 3); KeyPath = 'HKLM:\Test' })).Kind | Should -Be ([Microsoft.Win32.RegistryValueKind]::Binary)
}

It 'throws on a genuinely unsupported type' {
{ Convert-RegOperationToValueKind -Operation ([PSCustomObject]@{ ValueName = 'X'; ValueType = 'Nonsense'; ValueData = 1; KeyPath = 'HKLM:\Test' }) } | Should -Throw
}
}
2 changes: 1 addition & 1 deletion Winnow-Standalone.ps1

Large diffs are not rendered by default.