none
Calling a program with powershell

    Question

  • I am using powershell to call an executable, I want to be able to add the following. Proper error handling, I am reading powershell error handling but i am not an expert yet on it.

    Below is what i have so far:

     

    function StartCalculator {
     $procList = get-process Calculator -ea SilentlyContinue
     if (!$error) { $procList | stop-process }
     Invoke-Item c:\windows\system32\calc.exe
    }
    StartCalculator 
    

    I now need to add a timeout limit as well, how can I do this and also tidy up the error handling.

    Thanks in advance.

     

    Wednesday, August 11, 2010 4:47 PM

Answers

  • "Graceful" is somewhat subjective.  If you want graceful as in "the application is told to close nicely," then yes, CloseMainWindow is the way to go.  If you want graceful as in "the application stops. Now.  With no mess." then kill is your only option.

    You should consider the following about CloseMainWindow

    - It does not guarantee the process will exit.  It's the equivalent of doing File->Exit.  The program can prompt for user input if it likes (Do you want to save?), or really do anything at all, including plain ol' not quit.

    - It only works for processes with a user interface.  If the app is a service or something, it won't work at all.

    - It returns a boolean, so unless you want to emit a bool from this function, you should pipe to Out-Null, or set $null = $proc.CloseMainWindow().  Not that bad, but just a bit more code.

    - You get a 2nd error thrown in the case that the program can't be started in the first place.  Kill (which is alias for Stop-Process btw) does not throw a 2nd error in this case, if the process object is piped to it.

     

    Kill is the only way to end a non UI program, and the only sure way to end any other program.  It is true, though, that force-quitting could make the application unhappy/corrupt in some cases.

    I figure if the app runs longer than the timeout, then it is already behaving badly, so why trust it to quit when asked nicely :)

    -Lincoln

    Edit:  Yes, if PS v1 compatibility is in the picture, then Trap is best.  If the sanity of the scripter is to be considered, PSv2 and try/catch are the best ;)

    Thursday, August 12, 2010 11:35 PM
  • Ok this should work then:

    function RunMyAppWithTimeout
    {
      start-process 'c:\my folder\myapp.exe' -passThru | tee -var proc | wait-process -timeout (60 * 10) -ea SilentlyContinue
      $proc | kill
    }

    That will start myapp.exe, wait up to 10 min for it to be closed, then kill the process if it has not closed by then.  If it closes earlier, this will immediately return.  Is that what you had in mind?

    If any errors happen here, the error will simply bubble up and the function returns.  You could wrap the whole thing in try/catch if desired, but not sure what that really gives you, you would just be printing out the same error message you already would see...

    -Lincoln

    Thursday, August 12, 2010 7:56 PM
  • To satisfy MrFlinstone's requirement of a graceful end to the program using Lincoln's code:

     

    function RunMyAppWithTimeout
    {
     start-process 'c:\my folder\myapp.exe' -passThru | tee -var proc | wait-process -timeout (60 * 10) -ea SilentlyContinue
     $proc.closemainwindow()
    }
    Try/Catch won't work in Powershell V1, only Powershell V2, so using a Trap method would the only way to go.

     

    Thursday, August 12, 2010 10:03 PM

All replies

  • For the error handling:

    function StartCalculator {
    Try{
     $procList = get-process Calc -ea stop
     $procList | stop-process
     }
    Catch {
     ""
     }
     &calc.exe
     }
    StartCalculator
    Also, the process is Calc, not calculator and you should only need to use calc.exe as it already resides in System32 and you can call those without the full path as long as you use the '&' before the program. Just my preference, you can change it back to using the invoke-item and full path if you wish.
    Wednesday, August 11, 2010 5:02 PM
  • Killing any running calc instances and starting a new one should be one of the least error-prone things you could do :)

    function StartCalc
    {
      gps calc -ea SilentlyContinue | kill
      calc.exe
    }

    Folks tend to overthink how to run an EXE from powershell.  You don't need Invoke-Item, or the &.  And since calc.exe is in the PATH, you don't even need to include that here.

    What kind of timeout are you hoping to implement?

    Thanks,

    -Lincoln

    Wednesday, August 11, 2010 5:21 PM
  • You can go with a try/catch/finally, especially if you're using PowerShell v2.  You can also go simply with $LASTEXITCODE.  That variable will contain the return code of the previous EXE that was run.
    Wednesday, August 11, 2010 11:43 PM
    Moderator
  • the application is a bespoke application, I have just used calc.exe for illustration purposes only. I dont want the application running for more than 10 minutes, after which i want it to fail gracefully.

    Any ideas.

    Thursday, August 12, 2010 9:17 AM
  • I have been able to rewite it and add some error handling, i think. I just want a pro to review this and make sure that everything is fine. I am currently using powershelll V1. The next task is to now implement a timeout of 60 minutes, any one to help ?

     

    Function CallImport
    	{
    
    		Invoke-Item "C:\Program Files\SOM\loader.exe"
    
    		trap [SystemException]
    			{
    			$procList = get-process SomLoader -ea stop
    			Write-Output "Failure!"
    			}
    			
    	}
    CallImport
    
    
    Thanks

     

    Thursday, August 12, 2010 1:03 PM
  • I'd have the CallImport function return the process object for the started processs. Save that,  and use it to see if the process is still running an hour later.

     


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Thursday, August 12, 2010 1:21 PM
  • Sorry i am so new to powershell, can you give me an example please ? IF you can use my example above, that would be great.

     

    Thursday, August 12, 2010 3:37 PM
  • This isn't tested, but I beleive it should give you an idea about what's involved.  this won't do a graceful shutdown of the program.  Someone more familiar with it may be able to give you a better option for shutdown than just doing a stop-process.

     

     

    Function CallImport
    {

    Invoke-Item "C:\Program Files\SOM\loader.exe"

    trap [SystemException]
    {
    $procList = get-process SomLoader -ea stop
    Write-Output "Failure!"

    return
       }
     get-process SomLoader
    }
    $procs = CallImport

    start-sleep -seconds 3600

    foreach ($proc in $procs){

    if (get-process -contains $proc){stop-process $proc -force}

    }


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Thursday, August 12, 2010 4:02 PM
  • Thanks for the reply.

    I tested the solution, and i dont think its one that would be suitable for me, the reason being that it runs for an hour even though my application only runs for about 5 mins.

    I am sure there must be another method for this. please use my example above.

    Thursday, August 12, 2010 4:17 PM
  • Thanks for the reply.

    I tested the solution, and i dont think its one that would be suitable for me, the reason being that it runs for an hour even though my application only runs for about 5 mins.

    I am sure there must be another method for this. please use my example above.


    If you are wanting the application to close in 5 minutes instead of an hour, change the Start-Sleep -seconds from 3600 to 300 so it waits for 5 minutes and then closes the program. Or have it go for 600 seconds for 10 minutes.

     

    Thursday, August 12, 2010 4:42 PM
  • Using Mjolinor's code, this should allow you to stop a program gracefully (tested the method on notepad with data in it and it asked to be saved before closing instead of just outright closing on me)

    Function CallImport
    {
    
    Invoke-Item "C:\Program Files\SOM\loader.exe"
    
    trap [SystemException]
    {
    $procList = get-process SomLoader -ea stop
    Write-Output "Failure!"
    
    return
      }
     get-process SomLoader
    }
    $procs = CallImport
    
    #Set seconds to 600 for 10 minute wait
    start-sleep -seconds 3600
    
    foreach ($proc in $procs){
    
    if (get-process -contains $proc){(Get-Process $proc).CloseMainWindow()}
    
    }
    
    Thursday, August 12, 2010 4:51 PM
  • Thanks for the reply.

    I tested the solution, and i dont think its one that would be suitable for me, the reason being that it runs for an hour even though my application only runs for about 5 mins.

    I am sure there must be another method for this. please use my example above.


    Try this.  This will check every 30 seconds for 10 minutes. 

    To change the maximum time to wait before closing it, change $timeout.

    Adjust the start-sleep to change the check interval.

    Function CallImport
    {

    $initprocs = get-process |% {$_.id}

    Invoke-Item "C:\Program Files\SOM\loader.exe"
    trap [SystemException]
    {
    $procList = get-process SomLoader -ea stop |? {$initprocs -notcontains $_}
    Write-Output "Failure!"
    return
       }
     (get-process SomLoader |? {$initprocs -notcontains $_.id}).id
    }


    $timeout = [TimeSpan]::FromMinutes(10)
    $proc = CallImport
    $timer = [Diagnostics.Stopwatch]::StartNew()

    While ($timer.elapsed -lt $timeout){
    start-sleep -seconds 30
    if (!(Get-Process -Id $proc)){exit}
    }

    (Get-Process -Id $proc).CloseMainWindow()


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Thursday, August 12, 2010 5:53 PM
  • Just noticed a bug in the first one.  Use this instead.

     

    Function CallImport
    {

    $initprocs = get-process |% {$_.id}

    Invoke-Item "C:\Program Files\SOM\loader.exe"
    trap [SystemException]
    {
    $procList = get-process SomLoader -ea stop |? {$initprocs -notcontains $_.id}
    Write-Output "Failure!"
    return
       }
     (get-process SomLoader |? {$initprocs -notcontains $_.id}).id
    }


    $timeout = [TimeSpan]::FromMinutes(10)
    $proc = CallImport
    $timer = [Diagnostics.Stopwatch]::StartNew()

    While ($timer.elapsed -lt $timeout){
    start-sleep -seconds 30
    if (!(Get-Process -Id $proc)){exit}
    }

    (Get-Process -Id $proc).CloseMainWindow()


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Thursday, August 12, 2010 6:00 PM
  • Ok this should work then:

    function RunMyAppWithTimeout
    {
      start-process 'c:\my folder\myapp.exe' -passThru | tee -var proc | wait-process -timeout (60 * 10) -ea SilentlyContinue
      $proc | kill
    }

    That will start myapp.exe, wait up to 10 min for it to be closed, then kill the process if it has not closed by then.  If it closes earlier, this will immediately return.  Is that what you had in mind?

    If any errors happen here, the error will simply bubble up and the function returns.  You could wrap the whole thing in try/catch if desired, but not sure what that really gives you, you would just be printing out the same error message you already would see...

    -Lincoln

    Thursday, August 12, 2010 7:56 PM
  • To satisfy MrFlinstone's requirement of a graceful end to the program using Lincoln's code:

     

    function RunMyAppWithTimeout
    {
     start-process 'c:\my folder\myapp.exe' -passThru | tee -var proc | wait-process -timeout (60 * 10) -ea SilentlyContinue
     $proc.closemainwindow()
    }
    Try/Catch won't work in Powershell V1, only Powershell V2, so using a Trap method would the only way to go.

     

    Thursday, August 12, 2010 10:03 PM
  • Okay, now I feel stoopid.
    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Thursday, August 12, 2010 10:33 PM
  • "Graceful" is somewhat subjective.  If you want graceful as in "the application is told to close nicely," then yes, CloseMainWindow is the way to go.  If you want graceful as in "the application stops. Now.  With no mess." then kill is your only option.

    You should consider the following about CloseMainWindow

    - It does not guarantee the process will exit.  It's the equivalent of doing File->Exit.  The program can prompt for user input if it likes (Do you want to save?), or really do anything at all, including plain ol' not quit.

    - It only works for processes with a user interface.  If the app is a service or something, it won't work at all.

    - It returns a boolean, so unless you want to emit a bool from this function, you should pipe to Out-Null, or set $null = $proc.CloseMainWindow().  Not that bad, but just a bit more code.

    - You get a 2nd error thrown in the case that the program can't be started in the first place.  Kill (which is alias for Stop-Process btw) does not throw a 2nd error in this case, if the process object is piped to it.

     

    Kill is the only way to end a non UI program, and the only sure way to end any other program.  It is true, though, that force-quitting could make the application unhappy/corrupt in some cases.

    I figure if the app runs longer than the timeout, then it is already behaving badly, so why trust it to quit when asked nicely :)

    -Lincoln

    Edit:  Yes, if PS v1 compatibility is in the picture, then Trap is best.  If the sanity of the scripter is to be considered, PSv2 and try/catch are the best ;)

    Thursday, August 12, 2010 11:35 PM
  • "Graceful" is somewhat subjective.  If you want graceful as in "the application is told to close nicely," then yes, CloseMainWindow is the way to go.  If you want graceful as in "the application stops. Now.  With no mess." then kill is your only option.

    You should consider the following about CloseMainWindow

    - It does not guarantee the process will exit.  It's the equivalent of doing File->Exit.  The program can prompt for user input if it likes (Do you want to save?), or really do anything at all, including plain ol' not quit.

    - It only works for processes with a user interface.  If the app is a service or something, it won't work at all.

    - It returns a boolean, so unless you want to emit a bool from this function, you should pipe to Out-Null, or set $null = $proc.CloseMainWindow().  Not that bad, but just a bit more code.

    - You get a 2nd error thrown in the case that the program can't be started in the first place.  Kill (which is alias for Stop-Process btw) does not throw a 2nd error in this case, if the process object is piped to it.

     

    Kill is the only way to end a non UI program, and the only sure way to end any other program.  It is true, though, that force-quitting could make the application unhappy/corrupt in some cases.

    I figure if the app runs longer than the timeout, then it is already behaving badly, so why trust it to quit when asked nicely :)

    -Lincoln

    Edit:  Yes, if PS v1 compatibility is in the picture, then Trap is best.  If the sanity of the scripter is to be considered, PSv2 and try/catch are the best ;)

    That is what I love about Powershell..always learning new things ;) Excellent explanation Lincoln.
    Thursday, August 12, 2010 11:39 PM