I have admin for same account on 2 Windows 2016 servers. Server1 has this folder\file: 'E:\Temp\ZEBRA_1.dat'
If I open PowerShell (v4 or v5) (as admin) on Server1 and run this line of code everything works as expected (printer settings are updated via .dat file):
start-process -Verb runas -filepath "C:\Windows\System32\RUNDLL32.EXE" -argumentlist "PRINTUI.DLL,PrintUIEntry /Sr /n `"ZEBRA_1`" /a `"E:\Temp\ZEBRA_1.dat`" 2 7 c d g u"
If I then open PowerShell (as admin) on Server2 and enter the same code wrapped in an Invoke-Command it looks like the code runs (no errors), but when I check on Server1 - no changes were made:
Invoke-Command -ComputerName 'Server1' -ScriptBlock {
start-process -Verb runas -filepath "C:\Windows\System32\RUNDLL32.EXE" -argumentlist "PRINTUI.DLL,PrintUIEntry /Sr /n `"ZEBRA_1`" /a `"E:\Temp\ZEBRA_1.dat`" 2 7 c d g u"
}
I have tried wrapping the Start-process
piece in a with New-PSSession
to Server1 which also did not work. Using in the -Credential
arg my admin credentials also does not work on the with Invoke-Command.
If I mess with the "s then I get errors or no response, if I put the file on Server2 and use UNC path it still does not work from Server2 (but it still works from Server1).
I assume this is a RunAs Admin related issue since if the command is not run as admin on Server1 it will look as though it completes by also does not do anything.
How do I get that printer updated with that .DAT file remotely?
Note that when running this command locally it works seemingly instantly and there are never any popups or warnings generated.
I tried:
-verb runas
- same behaviorstart-process
- same behavior
Invoke-Command -ComputerName 'Server1' -ScriptBlock { C:\Windows\System32\RUNDLL32.EXE PRINTUI.DLL,PrintUIEntry /Sr /n "ZEBRA_1" /a "C:\Temp\ZEBRA_1.dat" 2 7 c d g u }
-asJob
- same behavior except job info was written to host-wait
- printer was not updated BUT 2 processes were created and did not go away until I killed them (rundll32.exe and wsmprovhost.exe)invoke-command
with a session - same behavior as #5 except killing the session killed the stalled processes for me$sess = New-PSSession -ComputerName 'Server1'
Invoke-Command -Session $sess -ScriptBlock {
C:\Windows\System32\RUNDLL32.EXE PRINTUI.DLL,PrintUIEntry /Sr /n "ZEBRA_1" /a "C:\Temp\ZEBRA_1.dat" 2 7 c d g u
}
Start-Sleep -Seconds 120
Remove-PSSession -Id $sess.Id
All this said, the hung rundll32.exe & wsmprovhost.exe processes seem the most suspicious - and they are of course invisible - but is there any way to get useful info back from them via the session?
After much fiddling based on @mklement0's answer the following code works - not really a fan of using psexec.exe because of additional file overhead, but I will take what I can get at this point. Interestingly, psexec's -i switch was not needed (big help with debugging though).
$server = 'Server1'; $localDatFolder = 'e:\Temp\'; $printer = 'ZEBRA_1'
$credential = Get-Credential -Message 'U & P for printersettings'
$user = "MyDomain\$($credential.GetNetworkCredential().UserName)"
$password = $credential.GetNetworkCredential().password
$ARGS = @('PRINTUI.DLL,PrintUIEntry', '/Sr', $('/n"'+$printer+'"'), $('/a"'+$localDatFolder+$printer+'.dat"'), '2', '7', 'c', 'd', 'g', 'u')
e:\temp\pstools\psexec.exe \\$server -u $user -p $password C:\Windows\System32\RUNDLL32.EXE $ARGS
Finally If I did not care about adjusting my personal preferences this code works. Seems like it should work without psexec since it's just using system and not interreacting but I was never able to make it happen
#THIS WORKS IF NOT ADJUSTING PERSONAL SETTINGS TOO:
$server = 'Server1'; $localDatFolder = 'e:\Temp\'; $printer = 'ZEBRA_1'
$ARGS = @('PRINTUI.DLL,PrintUIEntry', '/Sr', $('/n"'+$printer+'"'), $('/a"'+$localDatFolder+$printer+'.dat"'), '2', '7', 'c', 'd', 'g', 'u')
e:\temp\pstools\psexec.exe \\$server -s C:\Windows\System32\RUNDLL32.EXE $ARGS
Using -Verb RunAs
in a remote Start-Process
call is pointless, but usually benign:
By default, only administrators are permitted to make remoting calls, and their remote sessions invariably run with elevation (with administrative privileges), so that -Verb RunAs
is neither necessary nor has any effect.
In the unusual event that you've explicitly allowed non-administrators to run remote commands, on-demand elevation cannot work, because no UAC dialog can be presented in the remote session, which runs invisibly.
Therefore, you may not even need to use Start-Process
remotely.
That is, direct invocation should do (but note the additional, network-related problem below): C:\Windows\System32\RUNDLL32.EXE ...
If you do use Start-Process
, you must use its -Wait
switch to ensure that the command completes before the Invoke-Command -ComputerName ...
call returns, as the the launched process may otherwise get terminated before it has a chance to finish.[1]
Additionally, as Doug Maurer notes, you're probably running into the infamous double-hop problem, which prevents your remotely running commands from accessing network resources.
See this answer for one way to work around the problem, by passing credentials explicitly to the remote session, which can use them to establish a (temporary) drive mapping to the network resource.
As a potential alternative, Doug points to the solution presented in this blog post, which involves creating a session configuration with a fixed domain-user identity on the target machine; however, this approach:
If you've heeded all the advice above and the C:\Windows\System32\RUNDLL32.EXE
call still doesn't work as expected:
A conceivable reason is if the specific DLL call being made requires a window station, i.e. the same type of environment you get interactively, and therefore fails to run in the invisible, desktop-less session that PowerShell remoting commands run in.
A possible solution is therefore to use psexec
instead of PowerShell remoting, with its -i
and -h
options; note, however, that -i
requires a (any) user to be logged on interactively on the target machine; see this answer for an example.
[1] Using Invoke-Command
's -ComputerName
parameter creates an ad-hoc session that is closed when the call returns. By contrast, an explicitly created session, via New-PSSession
that is passed to Invoke-Command
via -Session
is longer-lived (at least 2 hours by default, unless closed earlier with an explicit Remove-PSSession
call). However, it is still better to run all commands synchronously in the remote session. If the caller doesn't want to wait for the results synchronously, it can use the Invoke-Command
's -AsJob
parameter to receive a job object that can be queried for completion and results later, on demand.