Using PowerShell and PsExec to invoke expressions on remote computers

While eagerly awaiting PowerShell’s upcoming remoting functionality, many people turn to Sysinternals’ PsExec tool to build their own version. However, PowerShell seems to hang when called via PsExec on the remote machine. This has come up on the SysInternal forum (http://forum.sysinternals.com/forum_posts.asp?TID=10823) among other places, and is caused by the same issue outlined here: http://www.leeholmes.com/blog/UsingMshexeInteractivelyFromWithinOtherPrograms.aspx.


 


To work around this problem, you can give some input to the Powershell process. But to give it input, you need to use cmd.exe:


 


psexec \\server cmd /c “echo . | powershell dir ‘c:\program files'”


 


Now, working around quote encoding and two levels of escape characters (cmd.exe and PowerShell) can be quite painful when crafting the PowerShell command this way. For that, you can use the –EncodedCommand parameter, which accepts a Base64-encoded version of your command.


 


$expression = “dir ‘c:\program files'”


$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)


$encodedCommand = [Convert]::ToBase64String($commandBytes)


psexec \\server cmd /c “echo . | powershell -EncodedCommand $encodedCommand”


 


And to make it even more PowerShelly, the –OutputFormat of XML lets you get back an XML representation of your command’s output. On your local system, PowerShell converts your output back to deserialized objects. From there, you can continue to manipulate the output with the object-oriented goodness you’ve come to expect of us J In the example below, the server processes the Where-Object query, but the client sorts the result on Handles.


 



PS >$command = { Get-Process | Where-Object { $_.Handles -gt 1000 } }


PS >Invoke-RemoteExpression \\LEE-DESK $command | Sort Handles


 


Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName


——-  ——    —–      —– —–   ——     – ———–


   1025       8     3780       3772    32   134.42    848 csrss


   1306      37    50364      64160   322   409.23   4012 OUTLOOK


   1813      39    54764      36360   321   340.45   1452 iTunes


   2316     273    29168      41164   218   134.09   1244 svchost


 


Here’s a script that automates all of this for you:


 


Program: Start a Process on a Remote Machine


Example 21-4 lets you invoke PowerShell expressions on remote machines. It uses PsExec (from http://www.microsoft.com/technet/sysinternals/utilities/psexec.mspx) to support the actual remote command execution.


This script offers more power than just remote command execution, however. As Example 21-3 demonstrates, it leverages PowerShell’s capability to import and export strongly structured data, so you can work with the command output using many of the same techniques you use to work with command output on the local system. Example 21-3 demonstrates this power by filtering command output on the remote system but sorting it on the local system.



Example 21-3. Invoking a PowerShell expression on a remote machine



PS >$command = { Get-Process | Where-Object { $_.Handles -gt 1000 } }


PS >Invoke-RemoteExpression \\LEE-DESK $command | Sort Handles


 


Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName


——-  ——    —–      —– —–   ——     – ———–


   1025       8     3780       3772    32   134.42    848 csrss


   1306      37    50364      64160   322   409.23   4012 OUTLOOK


   1813      39    54764      36360   321   340.45   1452 iTunes


   2316     273    29168      41164   218   134.09   1244 svchost


Since this strongly structured data comes from objects on another system, PowerShell does not regenerate the functionality of those objects (except in rare cases). For more information about importing and exporting structured data, see “Easily Import and Export Your Structured Data.”



Example 21-4. Invoke-RemoteExpression.ps1



##############################################################################


##


## Invoke-RemoteExpression.ps1


##


## Invoke a PowerShell expression on a remote machine. Requires PsExec from


## http://www.microsoft.com/technet/sysinternals/utilities/psexec.mspx


##


## ie:


##


##  PS >Invoke-RemoteExpression \\LEE-DESK { Get-Process }


##  PS >(Invoke-RemoteExpression \\LEE-DESK { Get-Date }).AddDays(1)


##  PS >Invoke-RemoteExpression \\LEE-DESK { Get-Process } | Sort Handles


##


##############################################################################


 


param(


  $computer = “\\$ENV:ComputerName”,


  [ScriptBlock] $expression = $(throw “Please specify an expression to invoke.”),


  [switch] $noProfile


  )


 


## Prepare the command line for PsExec. We use the XML output encoding so


## that PowerShell can convert the output back into structured objects.


$commandLine = “echo . | powershell -Output XML “


 


if($noProfile)


{


    $commandLine += “-NoProfile “


}


 


## Convert the command into an encoded command for PowerShell


$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)


$encodedCommand = [Convert]::ToBase64String($commandBytes)


$commandLine += “-EncodedCommand $encodedCommand”


 


## Collect the output and error output


$errorOutput = [IO.Path]::GetTempFileName()


$output = psexec /acceptEula $computer cmd /c $commandLine 2>$errorOutput


 


## Check for any errors


$errorContent = Get-Content $errorOutput


Remove-Item $errorOutput


if($errorContent -match “Access is denied”)


{


    $OFS = “`n”


    $errorMessage = “Could not execute remote expression. “


    $errorMessage += “Ensure that your account has administrative ” +


        “privileges on the target machine.`n”


    $errorMessage += ($errorContent -match “psexec.exe :”)


 


    Write-Error $errorMessage


}


 


## Return the output to the user


$output


 


 


Edit: Thanks to Otto’s suggestion, Added the /acceptEula switch to PsExec to accept the EULA automatically. 

14 Responses to “Using PowerShell and PsExec to invoke expressions on remote computers”

  1. Chris Oldwood writes:

    You can avoid the double quotes around the entire cmd.exe command by escaping the pipe symbol ‘|’ with a hat ‘^’, e.g.

    psexec \\server cmd /c echo. ^| PowerShell dir ‘c:\program files’

  2. Mark writes:

    I have had problems in the past getting PsExec to execute remote applications on Vista. I played around with some firewall settings for WMI & DCOM, but still access was denied. Any advice for use with Vista systems (workgroup mode, not domain)?

  3. Lee writes:

    Have you tried launching the console window as an administrator?

  4. Chuck writes:

    I am getting an error from psexec with your function, I have posted the issue on the PsTools forum: http://forum.sysinternals.com/forum_posts.asp?TID=3748

  5. Lee writes:

    Thanks for letting me know. It seems to be an issue with PsExec, as the command works locally.

    Lee

  6. Chuck writes:

    Sorry, I gave the wrong link to the sysinternals thread: http://forum.sysinternals.com/forum_posts.asp?TID=12407

  7. Alex writes:

    I tried this, but nothing happens. I just get the PS prompt, instead of any output. I even used different cmdlets, with the same result.

  8. EDF writes:

    FYI to those who will try something like the following using ssh and Invoke-RemoteExpression.ps1:

    ssh2 username@hostname "powershell -command Invoke-RemoteExpression.ps1 \\hostname { Get-Process }"

    Specifically, it doesn’t work with Tectia SSH Server on the host, even if you change the shell from cmd to powershell. I don’t fully understand it, but it looks like something to do with whether or not you are actually logged in. The above command fails, but if you log in through ssh [ssh2 username@hostname], your command prompt comes up and then you run the same command [powershell -command Invoke-RemoteExpression.ps1 \\hostname { Get-Process }], that will work. I haven’t tried other SSH servers because I need an enterprise level ssh server, but don’t have the time to test the three other solutions out there. Re: I can’t use cygwin, etc. on my servers.

    On the plus side, I’ve found this Invoke-RemoteExpression.ps1 script also helps with some escaping issues in some ps1 commands I was writing.

    If you find a way to make the first ssh statement work, I am all ears, and you would be my hero. WinRM/WinRS/WinMGMT isn’t an option to me for years still… for reasons I’d love to get into with Microsoft, but won’t get into in comments.

  9. WL writes:

    psexec \\server cmd /c "echo . | powershell dir ‘c:\program files’"

    The command works fine for me. However, it executes the cmd using the local administration account instead of the domain administator account.
    The powershell script I am executing remotely contains "$env:userdomain", and I meant to use the domain value the server is associated with instead of the local admin account.

    I’ve already tried:

    psexec \\server -u "domain\adminname" -p "admin password" cmd /c "echo . | powershell -f mypowershellscript.ps1"

    It did not help. If there is a way to execute the powershell script using the domain account, that will save my day.

    Thanks.

  10. JBR writes:

    Hi WL,

    I have tried the same, I really wanna know if there is any possibility for this aswell.
    We wanna get our Antivirus service started on another computer, but in another persons profile.

    Thanks.

  11. LanceVD writes:

    As a POC using psexec and a powershell script, this works for me:

    psexec \\Computer cmd /c START /WAIT powershell c:\scripts\powershell\dirList.ps1

    The dirList.ps1 scripts just does a │Get-Childitem c:\windows\temp
    and redirects the output to a text file on a share.

    The goal is to put psexec\powershell commands in an automation\scheduling tool we have, and target servers do not have WinRM.
    Gathering the exit code from powershell is another matter…
    =LVD

  12. Pavol writes:

    Hello guys,

    is there a way to get this running for a list of servers?

    psexec @servers.txt …

    Thanks,
    Pavol

  13. Powershell command to list certificates on remote machine « Nothing and Everything writes:

    […] found a fix/workaround on http://www.leeholmes.com/blog/2007/10/02/using-powershell-and-psexec-to-invoke-expressions-on-remote… that solved the issue. That’s what the “echo. ^|” is all about. The powershell […]

  14. ris writes:

    How do I run PSEXEC and output it to txt file on respective servers and do that parallely something like –
    foreach ($s in $Srvs) {Psexec -s -d \\$s someexe.exe > \\$s\c$\temp\$s.txt} …this works without the -d option but soon as I add -d, the output is blank. The basic idea is to run psexec remotely and parallely. Have tried Invoke-Command, Start-Process, etc etc within a PS Workflow but nothing works with piping option “>” present… I have also tried 2> and 2>$&1…No go…any ideas ?

Leave a Reply