7

I'm wondering if anybody knows of a way to conditionally execute a program depending on the exit success/failure of the previous program. Is there any way for me to execute a program2 immediately after program1 if program1 exits successfully without testing the LASTEXITCODE variable? I tried the -band and -and operators to no avail, though I had a feeling they wouldn't work anyway, and the best substitute is a combination of a semicolon and an if statement. I mean, when it comes to building a package somewhat automatically from source on Linux, the && operator can't be beaten:

# Configure a package, compile it and install it
./configure && make && sudo make install

PowerShell would require me to do the following, assuming I could actually use the same build system in PowerShell:

# Configure a package, compile it and install it
.\configure ; if ($LASTEXITCODE -eq 0) { make ; if ($LASTEXITCODE -eq 0) { sudo make install } }

Sure, I could use multiple lines, save it in a file and execute the script, but the idea is for it to be concise (save keystrokes). Perhaps it's just a difference between PowerShell and Bash (and even the built-in Windows command prompt which supports the && operator) I'll need to adjust to, but if there's a cleaner way to do it, I'd love to know.

Chris
  • 1,416
  • 18
  • 29
Dustin
  • 1,956
  • 14
  • 14
  • Whoever is interested in Bash-style `&&` and `||` becoming a part of PowerShell: please vote for the feature [here](https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11087898-implement-the-and-operators-that-bash-has). – mklement0 Jan 21 '17 at 19:10
  • Vote here: [GitHub](https://github.com/PowerShell/PowerShell/issues/3241) and here: [UserVoice](https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11087898-implement-the-and-operators-that-bash-has) – pilau Feb 07 '19 at 10:24

2 Answers2

2

You could create a function to do this, but there is not a direct way to do it that I know of.

function run-conditionally($commands) {
   $ranAll = $false
   foreach($command in $commands) {
      invoke-command $command
      if ($LASTEXITCODE -ne 0) {
          $ranAll = $false
          break; 
      }
      $ranAll = $true
   }

   Write-Host "Finished: $ranAll"

   return $ranAll
}

Then call it similar to

run-conditionally(@(".\configure","make","sudo make install"))

There are probably a few errors there this is off the cuff without a powershell environment handy.

iOnline247
  • 126
  • 7
GrayWizardx
  • 19,561
  • 2
  • 30
  • 43
  • I'll tweak it to work (Invoke-Expression is needed because Invoke-Command apparently doesn't work the way it ought to...or something), but that's definitely something useful. Thanks for the help. It's greatly appreciated. – Dustin Dec 16 '09 at 22:34
  • 3
    The problem with using `$LASTEXITCODE` is that it only set when PowerShell executes a console EXE. You may be better off using `$?`. – Keith Hill Dec 16 '09 at 22:50
  • You can then alias that function to make it easier to invoke. – GrayWizardx Dec 16 '09 at 23:29
  • @Orion Edwards, what is ugly about it? @Keith Hill, good point, I was using $LastExitCode incorrectly. – GrayWizardx Mar 23 '10 at 18:58
  • not the function itself, but to call it. Surely you can appreciate that `run-conditionally(@("a", "b"))` is uglier than `a && b` :-) – Orion Edwards Mar 23 '10 at 19:45
  • @Orion Edwards, well technically that is me being lazy :), @(...) is just array short hand, and I could have chosen to do it differently. I agree there is no convenient boolean short hand for process exit codes, but truthfully I think it is more explanatory than the a && b illustration, as && does not directly imply "continue" but rather "bail as early as possible". So I am not so much executing "A" then trying "B", as I am succeeding at executing "A" and then trying "B" looking for a failure. The array list tells me I am running "A", then "B", etc and I asking to exit on failure. – GrayWizardx Mar 23 '10 at 20:33
  • @GaryWizardx Actually, comma syntax is array shorthand, whereas `@()` is the longhand version to force an object into array form. I think the simpler `run-conditionally 'a', 'b', 'c'` syntax is clearer, especially as it matches the normal calling convention for built-in commands. – Emperor XLII Feb 07 '11 at 00:40
  • To add to Keith's recommendation to use `$?`: To use `Invoke-Command`, you must pass a script block, so it's better to type the `$commands` parameter that way. Also, to prevent side effects, use `Invoke-Command -NoNewScope` - or simply use the `.` operator. – mklement0 Jan 23 '17 at 13:29
1

I was really hurting for the lack of &&, too, so wrote the following simple script based on GrayWizardX's answer (which doesn't work as-is):

foreach( $command in $args )
{
  $error.clear()
  invoke-command $command
  if ($error) { break; }
}

If you save it as rc.ps1 (for "run conditionally") in a directory in your path, you can use it like:

rc {.\configure} {make} {make install}

Using script blocks (the curly braces) as arguments instead of strings means you can use tab completion while typing out the commands, which is much nicer. This script is almost as good as &&, and works.

Abram
  • 51
  • 2
  • 1
    Also, changing the fourth line to `if ($?) { break; }` makes the script behave like ||, in case that wasn't obvious. – Abram Aug 01 '11 at 19:43
  • It's not a good idea to _clear_ the `$Error` collection; testing `$?` will do (`if ($?) { break }` for `&&`, and `if (-not $?) { break }` for `||`). To use `Invoke-Command`, you must pass a _script block_, so it's better to type the `$args` parameter that way: `[scriptblock[]] $args`. Also, to prevent side effects, use `Invoke-Command -NoNewScope` - or simply use the `.` operator. – mklement0 Jan 23 '17 at 13:40