Search code examples
windowspowershellpowershell-remoting

Run RUNDLL32.EXE PRINTUI.DLL,PrintUIEntry remotely for Printer Settings Update


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?

UPDATE:

Note that when running this command locally it works seemingly instantly and there are never any popups or warnings generated.

I tried:

  1. moving files to C and running - same behavior (scriptblock part runs locally but not remotely)
  2. removed -verb runas - same behavior
  3. removed start-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 }
  4. added -asJob - same behavior except job info was written to host
  5. adding -wait - printer was not updated BUT 2 processes were created and did not go away until I killed them (rundll32.exe and wsmprovhost.exe)
  6. replaced 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?

UPDATE 2

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

Solution

    • 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:

        • may not work with non-domain user identities
        • even with domain identities may be prevented by network security policies
        • is a potential security risk, because whatever user connects via this session configuration then uses the preconfigured user identity.
    • 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.