Search code examples
powershellupdatesremote-accessinvoke-commandstart-process

Execute Start-Process Powershell Script on a Remote Computer


I open PowerShell as admin and use the following line of code to run an offline file for windows updates. It works fine

Start-Process 'wusa.exe' -ArgumentList 'C:\Temp\windows10.0-kb5032189-x64_0a3b690ba3fa6cd69a2b0f989f273cfeadba745f.msu' -verb runas

But now I am trying to somehow use this same code to run the file on remote computers. The windows update file has the same name and the exact location on the remote PCs

I came up with this code below which first gets admin credentials and passes it to the the invoke-command. Then the invoke-command runs the Start-Process code.

$Username = 'username123'
$Password = '84fWfghnsf&5Fh'
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass
Invoke-Command -ComputerName 8HPF31J7V6.domain.local -Credential $Cred -ScriptBlock {Start-Process 'wusa.exe' -ArgumentList 'C:\Temp\windows10.0-kb5032189-x64_0a3b690ba3fa6cd69a2b0f989f273cfeadba745f.msu' -verb runas}

Problem is, when I do this, nothing happens on the remote PC

I also don't get any errors when running

enter image description here

Any ideas on what I am doing wrong, or any other solutions besides this?


Solution

    • Add -Wait to your remote Start-Process call to ensure that wusa.exe runs to completion before returning from the remote Invoke-Command call.

      • Otherwise, with an ad-hoc remoting session implicitly created via use of the -ComputerName parameter, the Start-Process-launched process is automatically terminated when the Invoke-Command call returns.
    • Code executed via PowerShell's remoting runs invisibly on the target machines, so the use of -Verb RunAs is pointless:

      • If the remote process already is elevated, it has no effect. Note that remote processes are automatically elevated if the user identity running the remote command is an administrator on the target machine.

      • If it isn't (this would only happen if you explicitly gave non-administrative users permission to use PowerShell remoting), the call invariably fails, because no UAC dialog can be presented.

    Caveat:

    • While the information below in this answer is correct in principle, js2010 notes that in order to successfully run wusa.exe remotely, i.e. in order to install Windows Updates, running in an implicitly elevated remote session alone may not be sufficient.

    • This ServerFault answer provides background information, and third-party module PSWindowsUpdate may provide a solution, if used with its scheduling features.


    Therefore, with synchronous execution:

    # ... credential construction code ($Cred) omitted
    # Note: If the *current user* is allowed to make remoting calls,
    #       you may not need to construct credentials and don't need the 
    #       -Credential parameter below.
    
    # Note the use of -Wait, removal of -Verb RunAs
    Invoke-Command -ComputerName 8HPF31J7V6.domain.local -Credential $Cred -ScriptBlock {
      Start-Process -Wait wusa.exe -ArgumentList 'C:\Temp\windows10.0-kb5032189-x64_0a3b690ba3fa6cd69a2b0f989f273cfeadba745f.msu'
    }
    

    Read on for an asynchronous alternative. Note that the (Start-Process -PassThru …).ExitCode technique shown below, for reporting the remote process' exit code, can equally be used with the synchronous approach above.


    Asynchronous execution:

    If you don't want the Invoke-Command call to wait for the remotely launched process (wusa.exe) to terminate, you have two options:

    • In the simplest case, add the -AsJob switch to the call above, which makes Invoke-Command return a job object that can be queried for output and completion later, on demand, with Receive-Job and Wait-Job, as shown below.

    • Create a remote session explicitly, via New-PSSession, and pass it to Invoke-Command's -Session parameter. Such a session stays open until it is either explicitly removed with Remove-PSSession or times out due to inactivity.

      • However, to make the call asynchronous, you must then not use -Wait with Start-Process, and track the lifetime of the process in the remote session, by adding -PassThru to output a process-information object that you must capture in a variable, and consult again in a later Invoke-Command call with the same -Session argument.

    Asynchronous variant with -AsJob, which additionally demonstrates how to report the remote process' exit code:

    # ... credential construction code omitted
    
    # Note the use of -AsJob and the use of -PassThru with Start-Process
    # so as to be able to report the process' exit code.
    $remoteJob = 
      Invoke-Command -AsJob -ComputerName 8HPF31J7V6.domain.local -Credential $Cred -ScriptBlock {
        (
          Start-Process -Wait -PassThru wusa.exe -ArgumentList 'C:\Temp\windows10.0-kb5032189-x64_0a3b690ba3fa6cd69a2b0f989f273cfeadba745f.msu'
        ).ExitCode
      }
    
    # ... perform foreground activity here.
    
    # Now wait for the remote job to complete - which happens when 
    # wusa.exe terminates - and report its exit code.
    $exitCode = 
      $remoteJob | Receive-Job -Wait -AutoRemoveJob