Share via


Executing external program - foreground and background

Question

Sunday, February 19, 2012 11:33 PM

Hi,

I need to run an external program (SAS) from Powershell.  I have two scenarios:

1)  Foreground Processing:  I want to invoke SAS, execute the SAS program (test1.sas), and post-process the SAS log (extract errors and warnings).  The invocation would go something like this test script:

cd "C:\Path\To\SAS\Programs"
$cmd="C:\Program Files\SAS\SASFoundation\9.2\sas.exe"
$cfg="C:\Program Files\SAS\SASFoundation\9.2\nls\en\SASV9.CFG"
$pgm="test1.sas"
& "$cmd" -config "$cfg" -sysin "$pgm"
date

This sort of works, except the date command executes immediately (same thing with dot sourcing instead of invoke operator).  I need the script to pause while SAS runs. 

The actual post-processing (instead of "date") will be:

select-string -path path\to\SAS\log -pattern "ERROR:|WARNING:" -context 1,1

However, this *does* work:

pushd "$env:userprofile\My Documents\My SAS Programs"

$sas="C:\Program Files\SAS\SASFoundation\9.2\sas.exe"
$cfg="C:\Program Files\SAS\SASFoundation\9.2\nls\en\SASV9.CFG"
$pgm="test1.sas"

cmd /c " ""$cmd"" -config ""$cfg"" -sysin ""$pgm"" "

# I also tried all of these, since I do not want to use cmd /c if at all possible
# But, they do not properly set the $LastExitCode from SAS

# Start-Process "$cmd" "-config ""$cfg"" -sysin ""$pgm"" " -Wait 
# Start-Process "$cmd" "-config ""$cfg"" -sysin ""$pgm"" " -Wait -PassThru
# Start-Process "$cmd" "-config ""$cfg"" -sysin ""$pgm"" " -Wait -NoNewWindow

# I also tried Start-Job, as I like some of its options, such as Wait-Job
# Start-Job {"$sas" -ArgumentList "-config ""$cfg"" -sysin ""$pgm"" "}
# Wait-Job

"LastExitCode=$LastExitCode"

popd

But I would prefer not to use cmd.exe if possible.

Please let me know if there is a way to use Start-Process rather than cmd.exe to launch an executable, wait for the executable to end, then conduct further downstream processing in the script based on the exit code returned by that executable.

2) Background Processing:  Other times, I'll need to invoke SAS multiple times as a background job, then wait for those background jobs to end.  In pseudocode:

Invoke-Job "$cmd" -config "$cfg" -sysin "test1.sas"
Invoke-Job "$cmd" -config "$cfg" -sysin "test2.sas"
Invoke-Job "$cmd" -config "$cfg" -sysin "test3.sas"
Invoke-Job "$cmd" -config "$cfg" -sysin "test4.sas"
Invoke-Job "$cmd" -config "$cfg" -sysin "test5.sas"
Wait-Job (wait for all 5 jobs)
Invoke-Job "$cmd" -config "$cfg" -sysin "test6.sas"
Wait-Job (wait for 1 job)
Invoke-Job "$cmd" -config "$cfg" -sysin "test7.sas"
Invoke-Job "$cmd" -config "$cfg" -sysin "test8.sas"

I'd like fine grained control on which jobs to wait for.  For example, in most cases, I want to wait on all 5 jobs; in other cases, I want to wait on jobs 1,3,5 before continuing.

I don't know how to capture the process id from the Invoke-Job cmdlet, add it to an array, and pass that array as the correct parameter to the Wait-Job cmdlet.  It would also be nice to capture the $LastExitCode from background jobs if possible.

Thanks,
Scott

All replies (20)

Wednesday, February 22, 2012 1:31 PM ✅Answered | 1 vote

$p = Start-Process .\test.cmd -Wait -PassThru

$p.ExitCode

123

 SHAAAZAAM

 

Justin Rich
http://jrich523.wordpress.com
PowerShell V3 Guide (Technet)
Please remember to mark the replies as answers if they help and unmark them if they provide no help.


Sunday, February 19, 2012 11:56 PM

Have you tried Start-Process with -Wait?


Monday, February 20, 2012 10:18 PM

Yes see comments in code above.

Anyone else?


Tuesday, February 21, 2012 2:15 PM

while (@(get-process |where {$_.processname -eq "sas.exe").count -gt 0)

{

sleep 1

}

 

rest of the script.....

 

 

Justin Rich
http://jrich523.wordpress.com
PowerShell V3 Guide (Technet)
Please remember to mark the replies as answers if they help and unmark them if they provide no help.


Tuesday, February 21, 2012 2:32 PM | 1 vote

You tried use Start-Process but with -ArgumentList ?

$ArgumentList = '-config "C:\Program Files\SAS\SASFoundation\9.2\nls\en\SASV9.CFG" -sysin "test1.sas"'
Start-Process "C:\Program Files\SAS\SASFoundation\9.2\sas.exe" -ArgumentList $ArgumentList -Wait 

Tuesday, February 21, 2012 10:45 PM

Hi All,

Thanks for the replies so far.  Much appreciated.

Try this:

1.  Create a .bat or .cmd file that returns a non-zero return code.  I called mine error.cmd.

@exit 123

2.  Create the below script.  $cmd is the path to where you created error.cmd:

# specify path to dummy batch file that returns non-zero exit code
$cmd="$env:userprofile\My Documents\My Powershell Scripts\error.cmd"

# invoke using cmd / c - this works
cmd /c " ""$cmd"" "
"LastExitCode=$LastExitCode"

# "reset" LastExitCode
$LastExitCode=999

# invoke using Start-Process - this does not work
Start-Process $cmd -Wait 
"LastExitCode=$LastExitCode"

Execute this script.  When I invoke error.cmd via cmd /c, it works, i.e. $LastExitCode has the correct value.  When I invoke error.cmd via Start-Process, $LastExitCode does not have the correct value.

I thought the whole idea of PowerShell was to replace the ancient and klunky cmd.exe.  (Ok, it's not the "whole idea", but hopefully you get what I mean without arguing semantics).

Sooooo...can I:

1) Invoke an external command without using cmd.exe (via Start-Process, Invoke-Command, Invoke-Expression, whatever)

2) Pause the script until that command finishes

3) Have the correct value of $LastExitCode based on the return code (%ERRORLEVEL%) from the invoked external command?

Thanks,

Scott


Wednesday, February 22, 2012 11:23 PM

Thanks Gomer, much appreciated :-D.

One last question:  any thoughts on the Invoke-Job / Wait-Job question above?  For example, I create 5 background jobs, but only want to wait on 3 of them.  If that's too hard, I'd be happy to wait on all 5, which will cover 99% of my use cases.

Cheers,

Scott

P.S.:  If you don't get the Gomer joke above, you're too young (or I'm too old!)


Wednesday, February 22, 2012 11:51 PM

I'll throw in an idea:

$job_check = {(get-job job1,job2,job3 |                select -expand state) -contains "Running"                 }while (&$job_check){    start-sleep -Seconds 1    }

[string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "


Thursday, February 23, 2012 4:11 AM

Hi,

This is what I have so far:

Start-Process.ps1:

# cls

$cmd="ping"

$args="invalidhost"

$p=Start-Process $cmd -ArgumentList $args -Wait -PassThru -NoNewWindow

"LastExitCode=" + $p.ExitCode

This works, returning $LastExitCode=1.

Now try invoking Start-Process.ps1 as a job:

Start-Job.ps1:

# cls

$job="$env:userprofile\My Documents\My Powershell Scripts\Start-Process.ps1"

# are jobs running?  (1)
Get-Job -State Running

# stop all jobs  (2)
Get-Job -State Running | Stop-Job

# start job  (3)
$p=Start-Job $job

# get jobs  (4)
Get-Job -Name $p.Name

# wait for job to end (5)
Wait-Job -Name $p.Name

# get jobs  (6)
Get-Job -Name $p.Name

# get job output (7)
$o=Receive-Job -Name $p.Name
"LastExitCode=" + $o.ExitCode

Comments:

(1) This will show any running jobs from previous invocations
(2) This will stop any running jobs from previous invocations
(3) Start Start-Process.ps1 as a background job on the local machine
(4) This will show the running job
(5) This will pause the script execution until the job ends
(6) If things worked properly, this would show the job as finished
(7) Somehow, I have to get the LastExitCode from the background job into this script

The problem is, (5) hangs the script.  Even though Start-Process.ps1 runs fine (and very quickly), the Start-Job.ps1 script hangs on (5) until I kill the script.  Why would that be?

Questions:

1.  I need a better understanding of Start-Process vs. Start-Job.  Perhaps, for my purposes, I could just use Start-Process without the -wait option, then Wait-Process, in order to multi-thread multiple SAS jobs (or ping commands) at once. 

2.  Whether I use Start-Process or Start-Job, I need the LastExitCode in the calling script.  I was hoping to use:

$o=Receive-Job -Name $p.Name
"LastExitCode=" + $o.ExitCode

to accomplish this.

3.  Why the restiction that Start-Job has to be a PowerShell script?  Why can't I call an EXE file directly as a background job?  Seems like an unnecessary restriction to me.

Thanks,
Scott


Thursday, February 23, 2012 1:54 PM | 2 votes

too young apparently :)

 

man wait-job -full

 

notice the -job param, it accepts an array of jobs...

 $j1 = start-job ...

$j2 = start-job ..

 

etc etc

 

$jobs = $j1,$j2,$j3

 

or whatever.. you can change the log with either job IDs or instanceid, the

wait-job accepts all as a collection..

 

 

Justin Rich
http://jrich523.wordpress.com
PowerShell V3 Guide (Technet)
Please remember to mark the replies as answers if they help and unmark them if they provide no help.


Thursday, February 23, 2012 2:20 PM

5 - not sure, but you can do it based on the job itself rather than the

name, might help

 

wait-job -job $p #should use $j at least, $p is for process

 

7 - receive-job gets output from the job, which in this case is the text

"LastExitCode $code", if you just did $p.exitcode at the end of the

start-process.ps1 file you'd get back the int that is the code... you could

even pass back the whole process item.. $P

 

part of the problem (im having) is that I cant run start-process within a

job... it might have to do with the fact that we are trying to run an app

without a "desktop"... ping is interactive, even if you don’t have a window

for it...

 

that im only getting about... but, why do all this work?

 

why not just use start-process remove the wait, use the process object

returned?

 

you've got a few options from there.. if it’s a quick process you can loop

and monitor the $P.hasexited property... if its longer you can create an

event for

Exited

  

Justin Rich
http://jrich523.wordpress.com
PowerShell V3 Guide (Technet)
Please remember to mark the replies as answers if they help and unmark them if they provide no help.


Thursday, February 23, 2012 9:34 PM

(Gomer Pyle (old TV show) always exclaimed SHAZAM whenever he had a eureka moment :-) )

Thanks...yeah, I've got 4 PowerShell books on order from Amazon.  In the meantime, I'm doing my best...

I was thinking something like (pseudocode):

$array = @[]  # empty array

$array = start-job ...  # i need to hit the doc on adding new items to the end of an array

start-job ...  # if I don't want to wait on the job

Wait-Job $array

Thanks again for the tips!!!


Thursday, February 23, 2012 10:09 PM

I think it will depend on what you're trying to do in the "job" but if the reason for it being in the background is that the application (in the example, ping" is whats taking the bulk of the time i'd use the Start-Process method and stay away from Jobs in general. I wouldnt say this should be a standard approach, but applications inside of a Job is not all that stable it appears.

At any rate, I hope I've provided enough info to help you approach your problem in multiple ways.

Justin Rich
http://jrich523.wordpress.com
PowerShell V3 Guide (Technet)
Please remember to mark the replies as answers if they help and unmark them if they provide no help.


Thursday, February 23, 2012 10:15 PM

I'm curious.

What books did you order?

  - Larry (easily old enough to know immediately who Gomer is)

 >

 >

On 2/23/2012 3:34 PM, The Other Scott Bass wrote:

>

> ... I've got 4 PowerShell books on order

 >

 


Thursday, February 23, 2012 10:20 PM

Gomer.  Didn't he invent the pile driver?  Some sort of WWF star from back in the day.  : )


Friday, February 24, 2012 3:46 AM

I previously ordered "Windows PowerShell Cookbook" by Lee Holmes as a Kindle edition.  It is good for some "cookie cutter" code segments, but I found I need more basics and foundation.

So, I ordered these below books based on Amazon user reviews and a bit of Googling:

"Windows PowerShell In Action, Second Edition" by Bruce Payette.  I read this goes into tremendous detail on the theory and ins and outs of PowerShell.  More than likely I'll kinda skim this one based on the index and chapter headings, but hope to pick up a few (or a lot) of nuggets.

"Learn Windows PowerShell in a Month of Lunches" by Don Jones.  What can I say, catchy title and the reviews on Amazon were good.  I hope this will lay a solid foundation on which I can learn PowerShell.  I will probably literally read this at lunch and during the daily commute.

"Windows PowerShell Pocket Reference" by Lee Holmes.  I usually like O'Reilly books and have usually found them to be well organized.  Plus at $11 I've blown more than that on a bad lunch.  I'm hoping that will be a quick reference when I can't remember a particular syntax.

They've been shipped but haven't arrived yet, so I can't comment personally on how I find them.

(Although I love my Kindle and the e-books are searchable, the last three are hard copies.  I find I need my computer reference books to be hard text for some reason.)


Friday, February 24, 2012 3:52 AM

;)  I remember the pile driver (was that Dusty Rhoades or Andre The Giant?), but missed Gomer inventing it.

BTW, I wasn't having a go at Justin, he's been incredibly helpful.  I just got a chuckle over "SHAAAZAAM" which brought back memories. 

Guess you have to be a certain age to get the joke lol.


Friday, February 24, 2012 8:34 AM

Me too.  I like to place small Postits to mark pages as I'm researching something.

 

If possible, I also get the PDF form of the book for searching, as well as the paper version.

Manning books like that Bruce Payette one are almost always available in PDF form.

  - Larry

 >

 >

On 2/23/2012 9:46 PM, The Other Scott Bass wrote:

 > ...

> (Although I love my Kindle and the e-books are searchable, the last three are hard copies. I find I

> need my computer reference books to be hard text for some reason.)

>

 

 


Friday, February 24, 2012 1:01 PM | 1 vote

the powershell.com ebook is great and its free... also the help files are REALLY good, just not easy to sit and read through.

Justin Rich
http://jrich523.wordpress.com
PowerShell V3 Guide (Technet)
Please remember to mark the replies as answers if they help and unmark them if they provide no help.


Tuesday, October 16, 2012 6:20 PM

Likely a little late/old, but this is how I solve the issue:

$cmd="C:\Program Files\SAS\SASFoundation\9.2\sas.exe"
$cfg="C:\Program Files\SAS\SASFoundation\9.2\nls\en\SASV9.CFG"
$pgm="test1.sas"
& "$cmd" -config "$cfg" -sysin "$pgm" | Out-Null
date

Or

$x = & "$cmd" -config "$cfg" -sysin "$pgm"

Both put the output of the command into the pipeline, and so the script will stop until it is complete before proceeding.

HTH
-scott