Is there a powershell pattern for if($?) { } - powershell

I find myself chaining these a lot, eg:
do-cmd-one
if($?)
{
do-cmd-two
}
...
then at the end:
if(!$?)
{
exit 1
}
I assume there's a pattern for this in powershell but I don't know it.

PowerShell (Core) 7.0 introduced Bash-like && and || operators called pipeline-chain operators.
They will not be back-ported to Windows PowerShell, however, as the latter will generally see no new features.
In short, instead of:
do-cmd-one; if ($?) { do-cmd-two }
Note: Up to PowerShell 7.1, the more robust formulation is actually
do-cmd-one; if ($LASTEXITCODE -eq 0) { do-cmd-two }, for the reasons explained in this answer.
you can now write:
do-cmd-one && do-cmd-two
&& (AND) and || (OR) implicitly operate on each command's implied success status, as reflected in automatic Boolean variable $?.
This will likely be more useful with external programs, whose exit codes unambiguously imply whether $? is $true (exit code 0) or $false (any nonzero exit code).
By contrast, for PowerShell commands (cmdlets) $? just reflects whether the command failed as a whole (a statement-terminating error occurred) or whether at least one non-terminating error was reported; the latter doesn't necessarily indicate overall failure.
However, there are plans to allow PowerShell commands to set $? directly, as a deliberate overall-success indicator.
Also note that the following do not work with && and ||:
PowerShell's Test-* cmdlets, because they signal the test result by outputting a Boolean rather than by setting $?; e.g., Test-Path $somePath || Write-Warning "File missing" wouldn't work.
Boolean expressions, for the same reason; e.g., $files.Count -gt 0 || write-warning 'No files found' wouldn't work.
See this answer for background information, and the discussion in GitHub issue #10917.
There's a syntax caveat: As of this writing, the following will not work:
do-cmd-one || exit 1 # !! Currently does NOT work
Instead, you're forced to wrap exit / return / throw statements in $(...), the so-called subexpression operator:
do-cmd-one || $(exit 1) # Note the need for $(...)
GitHub issue #10967 discusses the reasons for this awkward requirement, which are rooted in the fundamentals of PowerShell's grammar.

Not sure if you like this any better.
if($(do-cmd-one; $?))
{
do-cmd-two
}
else
{
exit 1
}
Some other ideas. If actually doesn't check the exit code of a command. But if a command has no output when failing, and has output when successful, it can work. In this case, I'm hiding the error messages. If will always hide a command's regular output.
if(test-connection -Count 1 microsoft.com 2>$null) { 'yes' } # never responds
if(test-connection -Count 1 yahoo.com 2>$null) { 'yes' }
yes
Test-connection happens to have a quiet option that returns a boolean anyway:
if(test-connection -count 1 yahoo.com -quiet) { 'yes' }
yes
if(test-connection -count 1 microsoft.com -quiet) { 'yes' }

Related

Powershell: How to run an external command and checks its success in a single line?

In bash, I can do this:
if this_command >/dev/null 2>&1; then
ANSWER="this_command"
elif that_command >/dev/null 2>&1; then
ANSWER="that_command"
else
ANSWER="neither command"
fi
but in Powershell, I have to do this:
this_command >/dev/null 2>&1
if ($?) {
ANSWER="this_command"
} else {
that_command >/dev/null 2>&1
if ($?) {
ANSWER="that_command"
} else {
ANSWER="neither command"
}
}
or something similar with ($LASTEXITCODE -eq 0). How do I make the Powershell look like bash? I'm not a Powershell expert, but I cannot believe that it doesn't not provide some means of running a command and checking its return code in a single statement in a way that could be used in an if-elseif-else statement. This statement would be increasingly difficult to read with every external command that must be tested in this way.
For PowerShell cmdlets you can do the exact same thing you do in bash. You don't even need to do individual assignments in each branch. Just output what you want to assign and collect the output of the entire conditional in a variable.
$ANSWER = if (Do-Something >$null 2>&1) {
'this_command'
} elseif (Do-Other >$null 2>&1) {
'that_command'
} else {
'neither command'
}
For external commands it's slightly different, because PowerShell would evaluate the command output, not the exit code/status (with empty output evaluating to "false"). But you can run the command in a subexpression and output the status to get the desired result.
$ANSWER = if ($(this_command >$null 2>&1; $?)) {
'this_command'
} elseif ($(that_command >$null 2>&1; $?)) {
'that_command'
} else {
'neither command'
}
Note that you must use a subexpression ($(...)), not a grouping expression ((...)), because you effectively need to run 2 commands in a row (run external command, then output status), which the latter doesn't support.
You can't do it inline like in bash, but you can one-line this with two statements on one line, separated by a semi-colon ;:
MyProgram.exe -param1 -param2 -etc *>$null; if( $LASTEXITCODE -eq 0 ) {
# Success code here
} else {
# Fail code here
}
Also, you can't use $? with commands, only Powershell cmdlets, which is why we check that $LASTEXITCODE -eq 0 instead of using $?.
Note that you CAN evaluate cmdlets inline, just not external commands. For example:
if( Test-Connection stackoverflow.com ){
"success"
} else {
"fail"
}
Another approach is to have it output an empty string if it's false:
if (echo hi | findstr there) { 'yes' }
if (echo hi | findstr hi) { 'yes' }
yes
PowerShell's native error handling works completely differently from the exit-code-based error signaling performed by external programs, and, unfortunately, error handling with external programs in PowerShell is cumbersome, requiring explicit checks of the automatic $? or $LASTEXITCODE variables.
PowerShell [Core]:
introduced support for Bash-style && and || pipeline-chain operators in v7 - see this answer.
but this will not also enable use of external-program calls in if statements, because there PowerShell will continue to operate on the output from commands, not on their implied success status / exit code; see this answer for more information.
Solutions:
PowerShell [Core] 7.0+:
$ANSWER = this_command *>$null && "this_command" ||
(that_command *>$null && "that_command" || "neither command")
Note:
If this_command or that_command don't exist (can't be found), a statement-terminating error occurs, i.e. the statement fails as a whole.
Note the need to enclose the 2nd chain in (...) so that && "that_command" doesn't also kick in when this_command succeeds.
*>$null is used to conveniently silence all streams with a single redirection.
Unlike an if-based solution, this technique passes (non-suppressed) output from the external programs through.
Windows PowerShell and PowerShell Core 6.x:
If the external-program calls produce no output or you actively want to suppress their output, as in your question:
See the $(...)-based technique in Ansgar Wiechers' helpful answer.
If you do want the external programs' output:
An aux. dummy do loop allows for a fairly "low-noise" solution:
$ANSWER = do {
this_command # Note: No output suppression
if ($?) { "this_command"; break }
that_command # Note: No output suppression
if ($?) { "that_command"; break }
"neither command"
} while ($false)

Win10 Powershell - Simple If/Elseif Depends on Condition Order?

I'm attempting to write a deployment script that checks the OS major version, then runs command based on that. I can grab that just fine with [System.Environment]::OSVersion.Version.Major, but when I attempt to use that in an if/elseif statement, I always get the first condition, and somehow the variable changes.
So the code I'm working with to test is this (using a Windows 10 machine):
$OS_VERSION=$([System.Environment]::OSVersion.Version.Major)
if ($OS_VERSION = 6) {
Write-Output "OS Version is $OS_VERSION"
# Do stuff...
} elseif ($OS_VERSION = 10) {
Write-Output "OS Version is $OS_VERSION"
# Do different stuff..
}
I noticed that when I switch the order of the conditions it runs as expected, and even more frustrating, is that the statement works perfectly fine on a Windows 7 machine.
Is this a bug/quirk of Powershell I'm missing, or am I doing something stupid?
I thought I'd write this out since I can explain it better than my comment. When you enter an expression without assigning it, it gets output to the pipeline. In this case
if ($OS_VERSION = 6) {
is an expression (since the if statement evaluates expressions for a boolean value to take action). If you wrap this in parentheses when entered at an interactive prompt, it'll output what the variable assigns at the same time as assigning the variable, much like
6 | Tee-Object -Variable OS_VERSION
would, or a -PassThru switch on some cmdlets:
> ($Test = 6)
>> 6
> $Test
>> 6
So what you're doing here is always assigning that variable to 6 which evaluates to true since if is truthy and non-zero is true. What you need is the -eq comparison operator:
if ($OS_VERSION -eq 6) {
More information can be found from the following command:
Get-Help -Name about_Comparison_Operators
PowerShell does not use = as a comparison operator.
If you want to compare for equality, the operator is -eq:
if ($OS_VERSION -eq 6) {
Write-Output "OS Version is $OS_VERSION"
# Do stuff...
} elseif ($OS_VERSION -eq 10) {
Write-Output "OS Version is $OS_VERSION"
# Do different stuff..
}
This will correct your problem. You should take a close look at Get-Help -Name about_Comparison_Operators (or read the link).

How to handle errors for the commands to run in Start-Job?

I am writing an automation script. I had a function which takes either a command or an executable. I had to wait until the command or executable has completed running and return if failed or passed. I also want to write the output to file. I am trying with the Start-Job cmdlet.
My current code:
$job = Start-Job -scriptblock {
Param($incmd)
$ret = Invoke-Expression $incmd -ErrorVariable e
if ($e) {
throw $e
} else {
return $ret
}
} -ArumentList $outcmd
Wait-Job $job.id
"write the output to file using receive-job and return if passed or failed"
This works perfectly fine for commands but for executables irrespective of errorcode the value of $e is null. This falsely shows as passed even though the errorcode is 0.
I tried with errorcode using $LASTEXISTCODE and $?. But $? is true for executables and $LASTEXISTCODE is either null or garbage value for commands. I am out of ideas and struck here.
When in doubt, read the documentation:
$?
Contains the execution status of the last operation. It contains TRUE if the last operation succeeded and FALSE if it failed.
[…]
$LASTEXITCODE
Contains the exit code of the last Windows-based program that was run.
Basically, you need to check both. $? indicates whether the last PowerShell command/cmdlet was run successfully, whereas $LASTEXITCODE contains the exit code of the external program that was last executed.
if (-not $? -or $LASTEXITCODE -ne 0) {
throw '... whatever ...'
} else {
return $ret
}
However, Invoke-Expression is not a very good approach to executing commands. Depending on what you actually want to execute there are probably better ways to do it, with better methods for error handling.

Where Command not Working? [duplicate]

Very new to coding in general, so I fear I am missing something completely obvious. I want my program to check for a file. If it is there, just continue the code. If it has not arrived, continue cheking for a given amount of time, or untill the file shows up. My loop works on its own, so when i only select the do-part in Powershell ISE, it works. But when i try running it inside the if statement, nothing happens. The loops doesnt begin.
$exists= Test-Path $resultFile
$a = 1
if ($exists -eq "False")
{
do
{
$a++
log "Now `$a is $a "
start-sleep -s ($a)
$exists= Test-Path $resultFile
write-host "exists = $exists"
}
while (($a -le 5) -and ($exists -ne "True"))
}
Another way of doing this is using a while loop:
$VerbosePreference = 'Continue'
$file = 'S:\myFile.txt'
$maxRetries = 5; $retryCount = 0; $completed = $false
while (-not $completed) {
if (Test-Path -LiteralPath $file) {
Write-Verbose "File '$file' found"
$completed = $true
# Do actions with your file here
}
else {
if ($retryCount -ge $maxRetries) {
throw "Failed finding the file within '$maxRetries' retries"
} else {
Write-Verbose "File not found, retrying in 5 seconds."
Start-Sleep '5'
$retryCount++
}
}
}
Some tips:
Try to avoid Write-Host as it kills puppies and the pipeline (Don Jones). Better would be, if it's meant for viewing the script's progress, to use Write-Verbose.
Try to be consistent in spacing. The longer and more complex your scripts become, the more difficult it will be to read and understand them. Especially when others need to help you. For this reason, proper spacing helps all of us.
Try to use Tab completion in the PowerShell ISE. When you type start and press the TAB-key, it will automatically propose the options available. When you select what you want with the arrow down/up and press enter, it will nicely format the CmdLet to Start-Sleep.
The most important tip of all: keep exploring! The more you try and play with PowerShell, the better you'll get at it.
As pointed out in comments, your problem is that you're comparing a boolean value with the string "False":
$exists -eq "False"
In PowerShell, comparison operators evaluate arguments from left-to-right, and the type of the left-hand argument determines the type of comparison being made.
Since the left-hand argument ($exists) has the type [bool] (a boolean value, it can be $true or $false), PowerShell tries to convert the right-hand argument to a [bool] as well.
PowerShell interprets any non-empty string as $true, so the statement:
$exists -eq "False"
is equivalent to
$exists -eq $true
Which is probably not what you intended.

Powershell loop only if condition is true

Very new to coding in general, so I fear I am missing something completely obvious. I want my program to check for a file. If it is there, just continue the code. If it has not arrived, continue cheking for a given amount of time, or untill the file shows up. My loop works on its own, so when i only select the do-part in Powershell ISE, it works. But when i try running it inside the if statement, nothing happens. The loops doesnt begin.
$exists= Test-Path $resultFile
$a = 1
if ($exists -eq "False")
{
do
{
$a++
log "Now `$a is $a "
start-sleep -s ($a)
$exists= Test-Path $resultFile
write-host "exists = $exists"
}
while (($a -le 5) -and ($exists -ne "True"))
}
Another way of doing this is using a while loop:
$VerbosePreference = 'Continue'
$file = 'S:\myFile.txt'
$maxRetries = 5; $retryCount = 0; $completed = $false
while (-not $completed) {
if (Test-Path -LiteralPath $file) {
Write-Verbose "File '$file' found"
$completed = $true
# Do actions with your file here
}
else {
if ($retryCount -ge $maxRetries) {
throw "Failed finding the file within '$maxRetries' retries"
} else {
Write-Verbose "File not found, retrying in 5 seconds."
Start-Sleep '5'
$retryCount++
}
}
}
Some tips:
Try to avoid Write-Host as it kills puppies and the pipeline (Don Jones). Better would be, if it's meant for viewing the script's progress, to use Write-Verbose.
Try to be consistent in spacing. The longer and more complex your scripts become, the more difficult it will be to read and understand them. Especially when others need to help you. For this reason, proper spacing helps all of us.
Try to use Tab completion in the PowerShell ISE. When you type start and press the TAB-key, it will automatically propose the options available. When you select what you want with the arrow down/up and press enter, it will nicely format the CmdLet to Start-Sleep.
The most important tip of all: keep exploring! The more you try and play with PowerShell, the better you'll get at it.
As pointed out in comments, your problem is that you're comparing a boolean value with the string "False":
$exists -eq "False"
In PowerShell, comparison operators evaluate arguments from left-to-right, and the type of the left-hand argument determines the type of comparison being made.
Since the left-hand argument ($exists) has the type [bool] (a boolean value, it can be $true or $false), PowerShell tries to convert the right-hand argument to a [bool] as well.
PowerShell interprets any non-empty string as $true, so the statement:
$exists -eq "False"
is equivalent to
$exists -eq $true
Which is probably not what you intended.