none
Feedback / get PID for Invoke-Command Start-Process

    Question

  • Hello again Scripting Guys,

    I have a script that lets you install software on remote computers. It's a little slow when using against a long list of computers, but it works. I do not have SCCM available, so I have always used free utilities (psexec) to install / upgrade /remove third party software. I am trying to transition completely from sysinternals suite utilities to powershell, since PSExec send credentials in clear text. I've been working on install and remove scripts for .exe / .msi software. It would be nice to have one script that could do all but I'm nowhere near accomplishing this yet.

    Question is; I want to improve this script by giving me some sort of feedback as to success / failure, as well as speed up the process if possible. I liked the get-wmiobject -class win32_Product commands since they tell me return codes, but couldn't get these to work.  PSExec tells me a PID which I can at least watch on the remote computer to see how long the process takes to complete. I know there are commands like Write-host but I'm not sure how to use this with the invoke-command start-process to provide me with feedback.

    $computers = Get-Content 'C:\computerlist.txt'
    $sourcefile = '\\server\folder\java.exe'
    $process = 'java.exe'
    $arglist = '/s /norestart'
    foreach ($computer in $computers){Write-Host "Processing computer: $computer" -ForegroundColor green
    Try{
    $destinationFolder="\\$computer\C$\Temp"
    if(!(Test-Path -path $destinationFolder)){
    New-Item $destinationFolder -Type Directory -Force -ErrorAction Inquire }
    Copy-Item -Path $sourcefile -Destination $destinationFolder -Force -ErrorAction Inquire
    Invoke-Command -ComputerName $computer -ScriptBlock {Start-Process "C:\Temp\$using:process" -ArgumentList ($using:arglist) -verb runas -Wait -ErrorAction Inquire}}
    Catch{Write-Host $_ -ForegroundColor red -BackgroundColor white}
    }

        
    • Edited by commdudeaf Wednesday, September 18, 2013 5:35 PM
    Wednesday, September 18, 2013 5:34 PM

Answers

  • You can also use this method for discovering the process results:

    $SB={
         Param($arglist,$setup)
        Try{
        	Start-Process $setup -ArgumentList $arglist -Wait -ea Stop -PassThru
        }
        Catch{
            'Error in Start-Process'
        }
    }

    Catch the return and inspect it.

    $result=Invoke-Command -ComputerName$computer -ScriptBlock$SB -ArgumentList$arglist,$setup
    if($result -isnot [string]){$result.ID}else{$result}


    ¯\_(ツ)_/¯

    • Marked as answer by commdudeaf Monday, September 23, 2013 12:34 PM
    Wednesday, September 18, 2013 6:59 PM

All replies

  • In PowerShell use WMI or Invoke-Command - do not mix them. They both do remoting.

    What makes you think PsExec sends passwords over the network in plain text?


    ¯\_(ツ)_/¯

    Wednesday, September 18, 2013 5:39 PM
  • In PowerShell use WMI or Invoke-Command - do not mix them. They both do remoting.

    What makes you think PsExec sends passwords over the network in plain text?


    ¯\_(ツ)_/¯

    @jrv, right I'm not trying to mix them. I was saying how I like the idea for the WMI since it gave me return codes so I knew if the process was executed properly. However, I couldn't get this script to work with the wmi method.

    Do a quick Google search for psexec credentials clear text. You can capture packets between server / client and view the password with Wireshark. They also warn about it on the PsExec download page:

    http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx

    Towards the bottom of page "Note that the password is transmitted in clear text to the remote system."



    • Edited by commdudeaf Wednesday, September 18, 2013 6:05 PM
    Wednesday, September 18, 2013 6:03 PM
  • You are asking for something which is not needed.  If you use Wait and allow errors the errors will be reported.  You cannot use "Inquire" in a remote script block.

    I have restructured to remove the errors and conflicts.  It should work and will return errors.

    Try/Catch will not work in this kind of script and serves no purpose.

    $computers = Get-Content C:\computerlist.txt
    $sourcefile = '\\server\folder\java.exe'
    $process = 'java.exe'
    $arglist = '/s /norestart'
    
    foreach ($computer in $computers){
        Write-Host "Processing computer: $computer" -ForegroundColor green
        $destinationFolder="\\$computer\C$\Temp"
        if(!(Test-Path -path $destinationFolder)){
            New-Item $destinationFolder -Type Directory -Force -ErrorAction Inquire
        }
        Copy-Item -Path $sourcefile -Destination $destinationFolder -Force -ErrorAction Inquire
        $SB={
            Start-Process "C:\Temp\$process" -ArgumentList $arglist -Wait
        }
        Invoke-Command -ComputerName $computer -ScriptBlock $SB
    }


    ¯\_(ツ)_/¯


    • Edited by jrv Wednesday, September 18, 2013 6:11 PM
    Wednesday, September 18, 2013 6:11 PM
  • You can use Try/Catch ion the local CmdLets to prevent continuing the section on an error but tit will continue to process other computers.

    $computers = Get-Content C:\computerlist.txt
    $sourcefile = '\\server\folder\java.exe'
    $process = 'java.exe'
    $arglist = '/s /norestart'
    
    foreach ($computer in $computers){
        Write-Host "Processing computer: $computer" -ForegroundColor green
        $destinationFolder="\\$computer\C$\Temp"
        Try{
            if(!(Test-Path -path $destinationFolder)){
                New-Item $destinationFolder -Type Directory -Force -ErrorAction Stop
            }
            Copy-Item -Path $sourcefile -Destination $destinationFolder -Force -ErrorAction Stop
            $SB={
                Start-Process "C:\Temp\$process" -ArgumentList $arglist -Wait
            }
            Invoke-Command -ComputerName $computer -ScriptBlock $SB -ErrorAction Stop
        }
        Catch{
            $_
        }
    }

    Notice that now we use "-ErrorAction Stop". This is required to trap in a try/catch block.


    ¯\_(ツ)_/¯


    • Edited by jrv Wednesday, September 18, 2013 6:15 PM
    Wednesday, September 18, 2013 6:15 PM
  • Here is a version that is partly tested.  I do not use Java so that is not what I tested.

    $computers = Get-Content C:\computerlist.txt
    $sourcefile = '\\omega\c$\temp\scripts.zip'
    $setup = 'java.exe'
    $arglist = '/s /norestart'
    
    foreach ($computer in $computers){
        Write-Host "Processing computer: $computer" -ForegroundColor green
        $destinationFolder="\\$computer\C$\Temp"
        Try{
            if(!(Test-Path -path $destinationFolder)){
                New-Item $destinationFolder -Type Directory -Force -ErrorAction Stop
            }
            Copy-Item -Path $sourcefile -Destination $destinationFolder -Force -ErrorAction Stop
            $SB={
                Param($arglist)
                Start-Process C:\Temp\$setup -ArgumentList $arglist -Wait
            }
            Invoke-Command -ComputerName $computer -ScriptBlock $SB -ArgumentList $arglist
        }
        Catch{
            $_
        }
    }
    

    Here is one way to test without having to fight with the java installer which is very badly behaved.

    $computers = Get-Content C:\computerlist.txt
    $sourcefile = '\\server\temp\java.exe'
    $setup = 'c:\temp\java.exe'
    $arglist = '/s /norestart'
    
    foreach ($computer in $computers){
        Write-Host "Processing computer: $computer" -ForegroundColor green
        $destinationFolder="\\$computer\C$\Temp"
        Try{
            if(!(Test-Path -path $destinationFolder)){
                New-Item $destinationFolder -Type Directory -Force -ErrorAction Stop
            }
            Copy-Item -Path $sourcefile -Destination $destinationFolder -Force -ErrorAction Stop
            $SB={
                Param(
                    $setup,
                    $arglist
                )
                $arglist
                $setup
                $PWD
                #Start-Process $setup -ArgumentList $arglist -Wait
            }
            Invoke-Command -ComputerName $computer -ScriptBlock $SB -ErrorAction Stop -ArgumentList $arglist,$setup
        }
        Catch{
            $_
        }
    }
    
    

    This just returns the passed argulments and the current working directory.  It works on all machines I pointed it at.


    ¯\_(ツ)_/¯

    Wednesday, September 18, 2013 6:32 PM
  • In PowerShell use WMI or Invoke-Command - do not mix them. They both do remoting.

    What makes you think PsExec sends passwords over the network in plain text?


    ¯\_(ツ)_/¯

    @jrv, right I'm not trying to mix them. I was saying how I like the idea for the WMI since it gave me return codes so I knew if the process was executed properly. However, I couldn't get this script to work with the wmi method.

    Do a quick Google search for psexec credentials clear text. You can capture packets between server / client and view the password with Wireshark. They also warn about it on the PsExec download page:

    http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx

    Towards the bottom of page "Note that the password is transmitted in clear text to the remote system."



    I don't use PsExec and never with a password. In a domain it is not necessary.  It will run with the credential you are currently logged in with and they are not passed.  Kerberos does nto pass anything across the network unencrypted.

    Start small and try to just use this code to do an install.

    Copy the file to a test target then just run this:

    $setup = 'c:\temp\java.exe'
    $arglist = '/s /norestart'
    
    $SB={
         Param($arglist,$setup)
         Start-Process $setup -ArgumentList $arglist -Wait
    }
    $computer='MyTestPC'
    Invoke-Command -ComputerName $computer -ScriptBlock $SB -ArgumentList $arglist,$setup

    Running it manually (use CLI version) will help you to understand how it works before trying to batch it.


    ¯\_(ツ)_/¯


    • Edited by jrv Wednesday, September 18, 2013 6:43 PM
    Wednesday, September 18, 2013 6:43 PM
  • You can also use this method for discovering the process results:

    $SB={
         Param($arglist,$setup)
        Try{
        	Start-Process $setup -ArgumentList $arglist -Wait -ea Stop -PassThru
        }
        Catch{
            'Error in Start-Process'
        }
    }

    Catch the return and inspect it.

    $result=Invoke-Command -ComputerName$computer -ScriptBlock$SB -ArgumentList$arglist,$setup
    if($result -isnot [string]){$result.ID}else{$result}


    ¯\_(ツ)_/¯

    • Marked as answer by commdudeaf Monday, September 23, 2013 12:34 PM
    Wednesday, September 18, 2013 6:59 PM
  • You can also use this method for discovering the process results:

    $SB={
         Param($arglist,$setup)
        Try{
        	Start-Process $setup -ArgumentList $arglist -Wait -ea Stop -PassThru
        }
        Catch{
            'Error in Start-Process'
        }
    }

    Catch the return and inspect it.

    $result=Invoke-Command -ComputerName$computer -ScriptBlock$SB -ArgumentList$arglist,$setup
    if($result -isnot [string]){$result.ID}else{$result}


    ¯\_(ツ)_/¯

    Aaaah, that's what I was looking for! The -Passthru parameter. It returns the ProcessName, ID, and ComputerName. Perfect. The other code you posted needs the

    -Verb Runas and the $using:

    added. The $using: I learned recently to pass variables to the remote computer. Without it, just using $arglist returns a null error. Prefixing $arglist (or anything)  with $using:arglist in PS 3.0 will pass the variable to the remote computer. Also, without the -Verb runas, the process hangs and does not ever complete and needs to be terminated.

    The script now looks like this:

    foreach ($computer in $computers){
        Write-Host "Processing computer: $computer" -ForegroundColor green
        $destinationFolder="\\$computer\C$\Temp"
         Try{
        if(!(Test-Path -path $destinationFolder)){
            New-Item $destinationFolder -Type Directory -Force -ErrorAction Inquire
        }
        Copy-Item -Path $sourcefile -Destination $destinationFolder -Force -ErrorAction Inquire
        $SB={
            Start-Process "C:\Temp\$using:process" -ArgumentList $using:arglist -verb runas -Wait -ErrorAction Stop -PassThru
        }
        Invoke-Command -ComputerName $computer -ScriptBlock $SB
    }
        Catch{ 'Error in Start-Process' }
        }

    This works well. I haven't had an 'Error in start-process" yet so I'm not sure what will happen when there is one. But it's very helpful using the -Passthru parameter to see the process. This way I can log on to the remote computer and see if / how long it takes to complete.

    One more thing I'd like with this script if it's possible, is to move on to the next computer after it starts the process. When I run this script, it will process computer1, once the process completes on the remote computer and program is installed, the script moves on to computer2. This takes a very long time with hundreds of computers! PSExec would start the process and move on to the next computer listed in the text file immediately.

    If I remove the -Wait parameter from the scriptblock, the process starts and stops immediately. The program is not installed. Is there a way to accomplish this?

    Thanks alot @jrv!

    Thursday, September 19, 2013 3:13 PM
  • You cannot use both Wait and RunAs and you cannot use RunAs remotely.

    I have run my version remotely with no issues.  You MUST use the WAIT argument or the process will abort as soon as the script exits.

    What you are trying to do won't work and makes little sense.  Just wait for the process and check the termination status.


    ¯\_(ツ)_/¯

    Thursday, September 19, 2013 4:49 PM
  • You cannot use both Wait and RunAs and you cannot use RunAs remotely.

    I have run my version remotely with no issues.  You MUST use the WAIT argument or the process will abort as soon as the script exits.

    What you are trying to do won't work and makes little sense.  Just wait for the process and check the termination status.


    ¯\_(ツ)_/¯

    @jrv perhaps your not running an executable with Start-Process? In the Get-Help for start process it specifies to use the -Verb RunAs for .exe files.

    It does not work without this parameter. I removed only -Verb Runas and the process fails to start. It needs to be included. Without the -Wait parameter the script also fails, so they both are needed for it to work properly. When I put -Verb RunAs back in the script, it works properly. I have another script almost identical that installs .msi files that does work without the RunAs.

    Maybe what I'm trying to do makes little sense in terms of the code not allowing it in Powershell, but logically what I'm trying to do makes very much sense. The example I'm using here is installing java on 100 computers. I put 100 computers in a text file and run this script using $computers = get-content 100computerlist.txt.  The script will show 'Processing computer1' for about 1 to 3 minutes, while it waits for the process to end. Then will move on to 'processing computer2' for another 1-3 minutes, and so on until java has been installed on all computers in the text file.

    With PSExec, you basically put the commands in a batch file "c:\temp\java.exe /s" and use:

    psexec.exe /@100computers.txt -u domain\user javainstall.bat.

    This starts the batch file on the remote computer and moves on to the next computer. It takes about 1 second to move from computer1 to computer 2 and so on. This takes minutes vs hours.

    So what I'm trying to do is impossible with powershell script?




    • Edited by commdudeaf Thursday, September 19, 2013 5:39 PM
    Thursday, September 19, 2013 5:28 PM
  • You need to read more of the info.  The RunAs only works locally and not remotely.  Remotely it is just ignored.

    UAC only applies to a local session.

    You cannot use both RunAs and Wait at the same time remotely. "RunAs" is undocumented so it is a bit confusing.

    YOU can get pack a PID but the install will never run because you have ended the session.

    To do it without waiting you need to use either a workflow or a job

    Look at Invoke-Command -AsJob 

    The wait will work and the job will signal complete when the process finishes.  All output will be retained in the job.

    $job=Invoke-Command -AsJob -ComputerName $computer -ScriptBlock $sb -ArgumentList $arglist


    ¯\_(ツ)_/¯

    Thursday, September 19, 2013 6:38 PM
  • You need to read more of the info.  The RunAs only works locally and not remotely.  Remotely it is just ignored.

    UAC only applies to a local session.

    You cannot use both RunAs and Wait at the same time remotely. "RunAs" is undocumented so it is a bit confusing.

    YOU can get pack a PID but the install will never run because you have ended the session.

    To do it without waiting you need to use either a workflow or a job

    Look at Invoke-Command -AsJob 

    The wait will work and the job will signal complete when the process finishes.  All output will be retained in the job.

    $job=Invoke-Command -AsJob -ComputerName $computer -ScriptBlock $sb -ArgumentList $arglist


    ¯\_(ツ)_/¯

    Thanks again Jrv. As far as the RunAs, I see what your saying but for some reason this does not work without including it, I have tested numerous times. I think, maybe, since I have the '$using:' prefix, the -Verb runas parameter is being passed and run locally on the remote computer. Again you are the expert and I am the beginner I know you're correct but it will not work without the Runas so I'll keep it for now.

    When I use the Invoke-Command -AsJob it does allow the script to move on immediately to the next computer, which works very quickly like I was looking for. However, it doesn't return the PID anymore, so I can not check the status on the remote computer. But I will look in to this more and try to get it to work.

    • Edited by commdudeaf Monday, September 23, 2013 12:41 PM
    Monday, September 23, 2013 12:34 PM
  • That is why we use -Wait.  The job ends when the process is finished.  Just check the job status,

    ¯\_(ツ)_/¯

    Monday, September 23, 2013 2:29 PM