Public/Dismount-VBUserHive.ps1
|
# ============================================================ # FUNCTION : Dismount-VBUserHive # MODULE : VB.WorkstationReport # VERSION : 1.0.1 # CHANGED : 23-04-2026 -- Finalized: expanded help block with remote, WhatIf, and finally-block examples # AUTHOR : Vibhu Bhatnagar # PURPOSE : Safely unloads a user registry hive mounted by Mount-VBUserHive # ENCODING : UTF-8 with BOM # ============================================================ function Dismount-VBUserHive { <# .SYNOPSIS Safely unloads a user registry hive previously mounted by Mount-VBUserHive. .DESCRIPTION Dismount-VBUserHive unloads a user NTUSER.DAT hive from HKEY_USERS. It only unloads hives that were actively mounted in the current operation (HiveMounted = $true). Hives that were already loaded before Mount-VBUserHive was called (AlreadyLoaded = $true) are left untouched. Designed to accept pipeline input directly from Mount-VBUserHive output. Forces a garbage collection pass before unloading to release any open handles that would cause reg.exe to fail with "Access is denied". Supports local and remote execution via ComputerName. .PARAMETER SID The SID of the user hive to unload. Maps to HKU\{SID}. .PARAMETER HiveMounted When true, the unload is performed. When false (hive was already loaded before mounting), the function skips the unload and returns Skipped status. Accepts pipeline input from Mount-VBUserHive. .PARAMETER ComputerName Target computer. Defaults to local machine. .PARAMETER Credential Credentials for remote execution. Not required for local or domain-joined targets. .EXAMPLE Mount-VBUserHive -Username 'jdoe' | Dismount-VBUserHive Mounts and then safely dismounts the hive for jdoe. Skips the unload and returns Status = 'Skipped' if the hive was already loaded before the mount call (i.e. the user was already logged on). .EXAMPLE $mount = Mount-VBUserHive -SID 'S-1-5-21-...' try { # ... registry work against HKU\{SID} ... } finally { $mount | Dismount-VBUserHive | Out-Null } Recommended pattern -- use a finally block to guarantee dismount even if registry work throws an error. .EXAMPLE Mount-VBUserHive -Username 'jdoe' | Dismount-VBUserHive -WhatIf Dry run -- shows what would be unloaded without performing the unload. Useful for validating which hives are mounted before making changes. .EXAMPLE $cred = Get-Credential $mount = Mount-VBUserHive -Username 'jdoe' -ComputerName 'WS001' -Credential $cred # ... remote registry work ... $mount | Dismount-VBUserHive -Credential $cred Remote execution -- mounts and dismounts a hive on a remote workstation. Credential is forwarded to the Invoke-Command call inside Dismount-VBUserHive. .OUTPUTS PSCustomObject Returns one object per call with: - ComputerName : Target computer - SID : User SID that was processed - HiveUnloaded : True if unload was performed - Skipped : True if unload was skipped (AlreadyLoaded or HiveMounted false) - Status : 'Success', 'Skipped', or 'Failed' - Error : Error message (only present on failure) .NOTES Version : 1.0.1 Author : Vibhu Bhatnagar Category : User Profile Management Requirements: - PowerShell 5.1 or higher - Administrative privileges (reg.exe load/unload requires elevation) - No open handles to the hive being unloaded #> [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string]$SID, [Parameter(ValueFromPipelineByPropertyName = $true)] [bool]$HiveMounted = $true, [Parameter(ValueFromPipelineByPropertyName = $true)] [string]$ComputerName = $env:COMPUTERNAME, [PSCredential]$Credential ) process { # Skip -- hive was already loaded before we mounted it, leave it alone if (-not $HiveMounted) { return [PSCustomObject]@{ ComputerName = $ComputerName SID = $SID HiveUnloaded = $false Skipped = $true Status = 'Skipped' } } if (-not $PSCmdlet.ShouldProcess("HKU\$SID on $ComputerName", 'Unload registry hive')) { return } $scriptBlock = { param($SID) # Flush handles before unload -- prevents "Access is denied" from reg.exe [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() Start-Sleep -Seconds 1 # Confirm hive is still loaded before attempting unload $loadedSIDs = Get-ChildItem 'Registry::HKEY_USERS' | Select-Object -ExpandProperty PSChildName if ($loadedSIDs -notcontains $SID) { return @{ AlreadyGone = $true } } $regResult = reg.exe unload "HKU\$SID" 2>&1 if ($LASTEXITCODE -ne 0) { throw "reg.exe unload failed: $regResult" } return @{ AlreadyGone = $false } } try { $result = if ($ComputerName -eq $env:COMPUTERNAME) { & $scriptBlock $SID } else { $invokeParams = @{ ComputerName = $ComputerName ScriptBlock = $scriptBlock ArgumentList = $SID ErrorAction = 'Stop' } if ($Credential) { $invokeParams['Credential'] = $Credential } Invoke-Command @invokeParams } [PSCustomObject]@{ ComputerName = $ComputerName SID = $SID HiveUnloaded = -not $result.AlreadyGone Skipped = $result.AlreadyGone Status = 'Success' } } catch { [PSCustomObject]@{ ComputerName = $ComputerName SID = $SID HiveUnloaded = $false Skipped = $false Error = $_.Exception.Message Status = 'Failed' } } } } |