From e63226b087d380f90b37dfa3c5253df6a4e55c7d Mon Sep 17 00:00:00 2001 From: Tom Plant Date: Sat, 4 Jul 2026 19:22:28 +1000 Subject: [PATCH 1/2] Add -PerUser support to Register-ADTDll, Unregister-ADTDll and Invoke-ADTRegSvr32. Adds a -PerUser switch that performs per-user DLL registration via regsvr32.exe /n /i:user, calling the DLL's DllInstall entry point so registration data is written to HKCU instead of HKLM. When invoked under the SYSTEM account, regsvr32.exe is executed in the context of the currently logged on user via Start-ADTProcessAsUser so the registration lands in the correct user hive. Co-Authored-By: Claude Fable 5 --- .../Public/Invoke-ADTRegSvr32.ps1 | 40 ++++++++++++++++--- .../Public/Register-ADTDll.ps1 | 13 +++++- .../Public/Unregister-ADTDll.ps1 | 13 +++++- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/PSAppDeployToolkit/Public/Invoke-ADTRegSvr32.ps1 b/src/PSAppDeployToolkit/Public/Invoke-ADTRegSvr32.ps1 index 748d7b07bfb..74b0c214539 100644 --- a/src/PSAppDeployToolkit/Public/Invoke-ADTRegSvr32.ps1 +++ b/src/PSAppDeployToolkit/Public/Invoke-ADTRegSvr32.ps1 @@ -19,6 +19,9 @@ function Invoke-ADTRegSvr32 .PARAMETER Action Specify whether to register or unregister the DLL. + .PARAMETER PerUser + Specifies that the DLL should be registered for the current user only by calling its DllInstall entry point with the 'user' argument (regsvr32.exe /n /i:user). If this function is running under the SYSTEM account, regsvr32.exe is executed in the context of the currently logged on user. Note that the DLL must support per-user registration via a DllInstall export for this to work. + .INPUTS None @@ -39,6 +42,11 @@ function Invoke-ADTRegSvr32 Unregisters the specified DLL file. + .EXAMPLE + Invoke-ADTRegSvr32 -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -Action 'Register' -PerUser + + Registers the specified DLL file for the currently logged on user only. + .NOTES An active ADT session is NOT required to use this function. @@ -72,7 +80,10 @@ function Invoke-ADTRegSvr32 [Parameter(Mandatory = $true)] [ValidateSet('Register', 'Unregister')] - [System.String]$Action + [System.String]$Action, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.SwitchParameter]$PerUser ) begin @@ -83,12 +94,12 @@ function Invoke-ADTRegSvr32 { Register { - "/s `"$FilePath`"" + "/s$(if ($PerUser) { ' /n /i:user' }) `"$FilePath`"" break } Unregister { - "/s /u `"$FilePath`"" + "/s /u$(if ($PerUser) { ' /n /i:user' }) `"$FilePath`"" break } } @@ -153,8 +164,27 @@ function Invoke-ADTRegSvr32 throw (New-ADTErrorRecord @naerParams) } - # Register the DLL file and measure the success. - Start-ADTProcess -FilePath $RegSvr32Path -ArgumentList $ActionParameters -CreateNoWindow -SuccessExitCodes 0 + # Register the DLL file and measure the success. Per-user registration writes to the + # user's HKCU hive, so when running as SYSTEM, execute as the logged on user instead. + if ($PerUser -and [PSADT.AccountManagement.AccountUtilities]::CallerSid.IsWellKnown([System.Security.Principal.WellKnownSidType]::LocalSystemSid)) + { + if (!(Get-ADTClientServerUser)) + { + $naerParams = @{ + Exception = [System.InvalidOperationException]::new("The DLL file [$FilePath] cannot be $($Action.ToLowerInvariant() + 'ed') per-user as there is no logged on user to process the request against.") + Category = [System.Management.Automation.ErrorCategory]::InvalidOperation + ErrorId = 'NoActiveUserError' + TargetObject = $FilePath + RecommendedAction = "Please ensure a user is logged onto the system and try again." + } + throw (New-ADTErrorRecord @naerParams) + } + Start-ADTProcessAsUser -FilePath $RegSvr32Path -ArgumentList $ActionParameters -CreateNoWindow -SuccessExitCodes 0 + } + else + { + Start-ADTProcess -FilePath $RegSvr32Path -ArgumentList $ActionParameters -CreateNoWindow -SuccessExitCodes 0 + } } catch { diff --git a/src/PSAppDeployToolkit/Public/Register-ADTDll.ps1 b/src/PSAppDeployToolkit/Public/Register-ADTDll.ps1 index 9e72873ac06..bd945eb4ed1 100644 --- a/src/PSAppDeployToolkit/Public/Register-ADTDll.ps1 +++ b/src/PSAppDeployToolkit/Public/Register-ADTDll.ps1 @@ -16,6 +16,9 @@ function Register-ADTDll .PARAMETER FilePath Path to the DLL file. + .PARAMETER PerUser + Specifies that the DLL should be registered for the current user only by calling its DllInstall entry point with the 'user' argument (regsvr32.exe /n /i:user). If this function is running under the SYSTEM account, regsvr32.exe is executed in the context of the currently logged on user. Note that the DLL must support per-user registration via a DllInstall export for this to work. + .INPUTS None @@ -31,6 +34,11 @@ function Register-ADTDll Registers the specified DLL file. + .EXAMPLE + Register-ADTDll -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -PerUser + + Registers the specified DLL file for the currently logged on user only. + .NOTES An active ADT session is NOT required to use this function. @@ -59,7 +67,10 @@ function Register-ADTDll } return ![System.String]::IsNullOrWhiteSpace($_) })] - [System.String]$FilePath + [System.String]$FilePath, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.SwitchParameter]$PerUser ) begin diff --git a/src/PSAppDeployToolkit/Public/Unregister-ADTDll.ps1 b/src/PSAppDeployToolkit/Public/Unregister-ADTDll.ps1 index 2719225b45f..2f200e74fc1 100644 --- a/src/PSAppDeployToolkit/Public/Unregister-ADTDll.ps1 +++ b/src/PSAppDeployToolkit/Public/Unregister-ADTDll.ps1 @@ -16,6 +16,9 @@ function Unregister-ADTDll .PARAMETER FilePath Path to the DLL file. + .PARAMETER PerUser + Specifies that the DLL should be unregistered for the current user only by calling its DllInstall entry point with the 'user' argument (regsvr32.exe /u /n /i:user). If this function is running under the SYSTEM account, regsvr32.exe is executed in the context of the currently logged on user. Note that the DLL must support per-user registration via a DllInstall export for this to work. + .INPUTS None @@ -31,6 +34,11 @@ function Unregister-ADTDll Unregisters the specified DLL file. + .EXAMPLE + Unregister-ADTDll -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -PerUser + + Unregisters the specified DLL file for the currently logged on user only. + .NOTES An active ADT session is NOT required to use this function. @@ -59,7 +67,10 @@ function Unregister-ADTDll } return ![System.String]::IsNullOrWhiteSpace($_) })] - [System.String]$FilePath + [System.String]$FilePath, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.SwitchParameter]$PerUser ) begin From f9fe8d4e8e2eec322dcf8939e6b66cbbe1232602 Mon Sep 17 00:00:00 2001 From: Tom Plant Date: Sat, 4 Jul 2026 21:40:49 +1000 Subject: [PATCH 2/2] Rename ADTRegSvr32 -PerUser to -AsUser, remove redundant user/context guards Signed-off-by: Tom Plant --- .../Public/Invoke-ADTRegSvr32.ps1 | 23 +++++-------------- .../Public/Register-ADTDll.ps1 | 6 ++--- .../Public/Unregister-ADTDll.ps1 | 6 ++--- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/PSAppDeployToolkit/Public/Invoke-ADTRegSvr32.ps1 b/src/PSAppDeployToolkit/Public/Invoke-ADTRegSvr32.ps1 index 74b0c214539..01eba99d0d3 100644 --- a/src/PSAppDeployToolkit/Public/Invoke-ADTRegSvr32.ps1 +++ b/src/PSAppDeployToolkit/Public/Invoke-ADTRegSvr32.ps1 @@ -19,7 +19,7 @@ function Invoke-ADTRegSvr32 .PARAMETER Action Specify whether to register or unregister the DLL. - .PARAMETER PerUser + .PARAMETER AsUser Specifies that the DLL should be registered for the current user only by calling its DllInstall entry point with the 'user' argument (regsvr32.exe /n /i:user). If this function is running under the SYSTEM account, regsvr32.exe is executed in the context of the currently logged on user. Note that the DLL must support per-user registration via a DllInstall export for this to work. .INPUTS @@ -43,7 +43,7 @@ function Invoke-ADTRegSvr32 Unregisters the specified DLL file. .EXAMPLE - Invoke-ADTRegSvr32 -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -Action 'Register' -PerUser + Invoke-ADTRegSvr32 -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -Action 'Register' -AsUser Registers the specified DLL file for the currently logged on user only. @@ -83,7 +83,7 @@ function Invoke-ADTRegSvr32 [System.String]$Action, [Parameter(Mandatory = $false)] - [System.Management.Automation.SwitchParameter]$PerUser + [System.Management.Automation.SwitchParameter]$AsUser ) begin @@ -94,12 +94,12 @@ function Invoke-ADTRegSvr32 { Register { - "/s$(if ($PerUser) { ' /n /i:user' }) `"$FilePath`"" + "/s$(if ($AsUser) { ' /n /i:user' }) `"$FilePath`"" break } Unregister { - "/s /u$(if ($PerUser) { ' /n /i:user' }) `"$FilePath`"" + "/s /u$(if ($AsUser) { ' /n /i:user' }) `"$FilePath`"" break } } @@ -166,19 +166,8 @@ function Invoke-ADTRegSvr32 # Register the DLL file and measure the success. Per-user registration writes to the # user's HKCU hive, so when running as SYSTEM, execute as the logged on user instead. - if ($PerUser -and [PSADT.AccountManagement.AccountUtilities]::CallerSid.IsWellKnown([System.Security.Principal.WellKnownSidType]::LocalSystemSid)) + if ($AsUser) { - if (!(Get-ADTClientServerUser)) - { - $naerParams = @{ - Exception = [System.InvalidOperationException]::new("The DLL file [$FilePath] cannot be $($Action.ToLowerInvariant() + 'ed') per-user as there is no logged on user to process the request against.") - Category = [System.Management.Automation.ErrorCategory]::InvalidOperation - ErrorId = 'NoActiveUserError' - TargetObject = $FilePath - RecommendedAction = "Please ensure a user is logged onto the system and try again." - } - throw (New-ADTErrorRecord @naerParams) - } Start-ADTProcessAsUser -FilePath $RegSvr32Path -ArgumentList $ActionParameters -CreateNoWindow -SuccessExitCodes 0 } else diff --git a/src/PSAppDeployToolkit/Public/Register-ADTDll.ps1 b/src/PSAppDeployToolkit/Public/Register-ADTDll.ps1 index bd945eb4ed1..aa2f1cf7872 100644 --- a/src/PSAppDeployToolkit/Public/Register-ADTDll.ps1 +++ b/src/PSAppDeployToolkit/Public/Register-ADTDll.ps1 @@ -16,7 +16,7 @@ function Register-ADTDll .PARAMETER FilePath Path to the DLL file. - .PARAMETER PerUser + .PARAMETER AsUser Specifies that the DLL should be registered for the current user only by calling its DllInstall entry point with the 'user' argument (regsvr32.exe /n /i:user). If this function is running under the SYSTEM account, regsvr32.exe is executed in the context of the currently logged on user. Note that the DLL must support per-user registration via a DllInstall export for this to work. .INPUTS @@ -35,7 +35,7 @@ function Register-ADTDll Registers the specified DLL file. .EXAMPLE - Register-ADTDll -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -PerUser + Register-ADTDll -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -AsUser Registers the specified DLL file for the currently logged on user only. @@ -70,7 +70,7 @@ function Register-ADTDll [System.String]$FilePath, [Parameter(Mandatory = $false)] - [System.Management.Automation.SwitchParameter]$PerUser + [System.Management.Automation.SwitchParameter]$AsUser ) begin diff --git a/src/PSAppDeployToolkit/Public/Unregister-ADTDll.ps1 b/src/PSAppDeployToolkit/Public/Unregister-ADTDll.ps1 index 2f200e74fc1..7a7a5f145b3 100644 --- a/src/PSAppDeployToolkit/Public/Unregister-ADTDll.ps1 +++ b/src/PSAppDeployToolkit/Public/Unregister-ADTDll.ps1 @@ -16,7 +16,7 @@ function Unregister-ADTDll .PARAMETER FilePath Path to the DLL file. - .PARAMETER PerUser + .PARAMETER AsUser Specifies that the DLL should be unregistered for the current user only by calling its DllInstall entry point with the 'user' argument (regsvr32.exe /u /n /i:user). If this function is running under the SYSTEM account, regsvr32.exe is executed in the context of the currently logged on user. Note that the DLL must support per-user registration via a DllInstall export for this to work. .INPUTS @@ -35,7 +35,7 @@ function Unregister-ADTDll Unregisters the specified DLL file. .EXAMPLE - Unregister-ADTDll -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -PerUser + Unregister-ADTDll -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -AsUser Unregisters the specified DLL file for the currently logged on user only. @@ -70,7 +70,7 @@ function Unregister-ADTDll [System.String]$FilePath, [Parameter(Mandatory = $false)] - [System.Management.Automation.SwitchParameter]$PerUser + [System.Management.Automation.SwitchParameter]$AsUser ) begin