Powershell equivalent of Perl's $CHILD_ERROR - windows

I essentially require a functionality in Powershell that executes the given string (it can be a CMD/Powershell command, a perl/python/powershell with arguments or an exe with arguments, etc) captures its exit value.
In perl I would pass the string to 'system()' and use the '$CHILD_ERROR' perlval and shift it to access the exit code.
In powershell I am clueless.
I tried using Invoke-Expression, but even if the expression passed to Invoke-Expression fails, the Invoke-Expression call itself will have succeeded.

You can use $LASTEXITCODE to get the exit code from an external program or the Boolean $? to check if the last operation succeeded or failed. Run Get-Help about_Automatic_Variables -ShowWindow from a PowerShell console to see more details.
You may want to check out the & (call) command as an alternative to Invoke-Expression when running external programs. Run Get-Help about_Automatic_Variables -ShowWindow from a PowerShell console for details.
Also remember you may be able to just call the external program without using one of the commands above. See the example below:
param($Hostname="127.0.0.1", $Tries=1, $Wait=1000)
$output = ping.exe $Hostname -n $Tries -w $Wait # captures anything written to stdout
$output|? {$_ -match 'Request timed out'}|Write-Warning
$LASTEXITCODE # returns the exit code from ping.exe
You can copy it to a test.ps1 file and run it from a PowerShell console window (.\test.ps1 8.8.8.8 for instance) to see how it works.

Related

Get exitcode from powershell invoke-command from linux

/usr/bin/pwsh -command Invoke-Command -Hostname PC -UserName boss -FilePath ./check.ps1 -ArgumentList "A","25","10"
So I am running this command from Ubuntu with bash shell. The invoke-command is connecting with ssh. The last line of check.ps1 is "Exit 2". But exit code alway return 0. Any suggestions to get the correct exit code? I would like to use this command as Nagios check.
Unfortunately, as of PowerShell 7.2, any out-of-runspace code, notably including remoting calls, does not relay script / process exit codes.
Therefore, your script's exit 2 command has no effect on the exit code reported by the /usr/bin/pwsh process.
Even inside the PowerShell session this exit code isn't available for out-of-runspace calls[1], so - unfortunately - your only option is to make your script output the desired exit code, make the PowerShell session capture it, and relay with an exit call that is a direct part of the PowerShell code passed to the CLI's -command parameter.
In the simplest case, if you modify your script to output the desired exit code and assuming that that exit code is the script's only output, you can use:
/usr/bin/pwsh -command 'exit (Invoke-Command -Hostname PC -UserName boss -FilePath ./check.ps1 -ArgumentList A, 25, 10)'
[1] For in-runspace calls, the exit code set by scripts that use exit as well as by external programs (processes) is reflected in the automatic $LASTEXITCODE variable.
For what it's worth, I believe if your script has an exception, the exit code will be non-zero. For example, my check.ps1 just has "get-childitem foo" where foo doesn't exist. This is from cmd, but I believe the effect is the same.
pwsh -command Invoke-Command -computername localhost check.ps1 -args A,25,10
Get-ChildItem: Cannot find path 'C:\Users\js\Documents\foo' because it does not exist.
echo %errorlevel%
1

Calling a command line application outtputing to stderr with piping

I'm looking to get both std-err and std-out streams from a binary I have called by powershell piped to a powershell cmdlet. I cant seem to get the syntax or find the right command.
I have a little Write-Smartly cmdlet that I want to process line-by-line of combined standard-error and standard output. It is itself a wrapper on Write-Host that adds some indenting.
My first attempt, I want to:
use -ErrorAction to indicate to powershell not to treat standard-error messages as exceptions
use 2>&1 to combine standard-error and standard-output streams into a pipelineable output
use ... | MyCmdlet to format the combined output & error streams to their ultimate output
giving me:
& $myApp $args --% -ErrorAction 'SilentlyContinue' 2>&1 | Write-Smartly
Unfortunatly this ended up passing -ErrorAction SilentlyContinue and 2>&1 into my application as arguments. Standard-error did show up as regular output, but it was written directly to host, rather than piped to WriteSmartly.
So I made a few modifications to try to correct this:
$ErrorActionPreference = 'SilentlyContinue'
&{ & $myApp $args } 2>&1 | Write-Smartly
This simply causes my error output to disappear.
I also played around with Start-Process, but it seems too heavyweight to allow me to get things out of it with a pipe. I think that Invoke-Command or Invoke-Expression might be helpful here but I'm not sure what their use cases are.
How can I capture both standard-error and standard-output as a pipelined value in powershell?

Autologon.exe via command line and get result

Is there a way I can execute sysinternals Autologon.exe from command line (Powershell) and get the result, i.e. know if the credentials entered were correct?
If I use the GUI and not the command line then I do get message with this info..
Thanks.
You can run any Windows .exe/cmd/bat/vbs from PoSH, as long as you call it correctly.
PowerShell: Running Executables
https://social.technet.microsoft.com/wiki/contents/articles/7703.powershell-running-executables.aspx
There are several different methods for running executables as well as invoking code. How do you know which one to use for the job? Here is an outline of the methods with examples and general use.
Regarding ---
"If I use the GUI and not the command line then I do get message with this info.."
This is because Autologon.exe is doing this work and returning the result. SO, it's Autologon. PoSH will not get in Autologon's way / process, but you can get the exit code from external tools as long as you know what they are. I've not tried to this with Autologon. Yet, in most cases, it's a Boolean response: success or failure, 0 or 1, or 1 or 2. The Start-Process cmdlet allows you to trap error / exit code from a process, using the -PassThru parameter. Often that is use in concert with the -wait parameter as well.
Something like:
$AutologExitCode = Start-Process -FilePath = Autologon.exe -ArgumentList $SomeArguments -NoNewWindow -Wait -PassThru
"The error/exit code from AutoLogon is $($AutologonExitCode.ExitCode)"
Full disclosure: We do not allow Autologon in our environment. So, I've spent no time using Autologon, but the above premise would be the same as other exe's.

PowerShell 2.0 can't redirect stdout

Sometimes, the answer is "no". This turns out not to be a question, but more of a cri de coeur (OK, just complaining).
The Team Foundation Server 2010 PowerShell snap-in exports a cmdlet called Get-TfsPendingChange, which does what it sounds like.
If there are no pending changes, Get-TfsPendingChange echoes "There are no pending changes." on standard output. I never asked it to. The following attempts at suppressing that output all fail:
$pcs = $(Get-TfsPendingChange -Server $server "$/foo/bar" -User $env:USERNAME -r ) | out-null;
$($pcs = Get-TfsPendingChange -Server $server "$/foo/bar" -User $env:USERNAME -r ) | out-null;
$pcs = Get-TfsPendingChange -Server $server "$/foo/bar" -User $env:USERNAME -r | out-null;
I'm not getting errors, but the text output is not redirected. What IS redirected, is the normal successful output of the cmdlet, in the case where there are pending changes for it to return. The ONLY output you can suppress is the useful output. If there are zero pending changes, you get text garbage via stdout. If there are any actual pending changes, that stream (of objects, not text) can be redirected to the null device, but that's worse than useless.
So it appears that text on stdout is a side effect from this cmdlet, not the cmdlet's output, and it cannot be redirected in PowerShell. The PowerShell redirect operators > and |, without the fileno specifiers (2 in PS2, 2, 3, 4, 5, and * in PS3/PS4), work on the object stream, which is not stdout. This would be fine if stdout had been replaced by this new object stream concept (or if some genius on the TFS team hadn't decided that cmdlets should spew random text noise in all directions as a side effect).
The garbage output definitely is on stdout, not stderr. I can redirect the standard output of the whole script to nul when I run it from cmd.exe, and that works as expected, because duh:
c:\>powershell .\tfstest.ps1 > nul
However, I do not want to suppress all the output from the entire script. Just the output from one snapin cmdlet. It's not the end of the world, but it's irritating to have random stuff that I call echoing garbage I have no control over. The script otherwise works correctly (see UPDATE).
One workaround would be to prefix everything I want to echo on stdout with some arbitrary string (say "KLUDGEPREFIX"), and run the whole mess from a batch file which pipes the PowerShell script's output through find /v KLUDGEPREFIX and then through a PowerShell fragment that strips off the prefix from each line.
Instead, I'm going to write the rest of the script around the problem so it looks like what they get is what I intended, because, in fact, I was going to tell them they didn't have any pending changes anyhow. It's been a valuable exercise, however, in terms of getting my head inside PowerShell.
UPDATE: This script turned out to be fragile, unreliable, glacially slow, and impossible to run on any computer but the one where I originally wrote it.
I replaced it with a Perl script that took 1/10 the time to write, executed 10 times as fast, and does the same task better without any foolishness. PS and the TFS PS snap-in are great ideas but they both need a lot of work before they'll be ready for release.
I'm not familiar with Team Foundation Server, but most likely the cmdlet writes to the host rather than one of the output streams. Output to the host cannot be redirected in PowerShell, but if you run a PowerShell script in CMD the host output goes to STDOUT (as does the success output stream) and can be redirected there.
Demonstration:
PS C:\> cat .\test.ps1
Write-Host 'foo'
PS C:\> .\test.ps1 | Out-Null
foo
PS C:\> .\test.ps1 >$null
foo
PS C:\> & cmd.exe /c powershell.exe -File .\test.ps1
foo
PS C:\> & cmd.exe /c powershell.exe -File .\test.ps1 `>nul
PS C:\> & cmd.exe /c powershell.exe -File .\test.ps1 >$null
PS C:\> _
if you add 2 in front of the > it will only redirect error. likewise with 1 for non errors (stdout). So if you want all stdout, but not stderr to go away you can type
$pcs = Get-TfsPendingChange -Server $server "$/foo/bar" -User $env:USERNAME -r 1> $null
$null can be used as an equivalent to /dev/null. your code probably creates the file "nul" on disk.

How to stop a PowerShell script on the first error?

I want my PowerShell script to stop when any of the commands I run fail (like set -e in bash). I'm using both Powershell commands (New-Object System.Net.WebClient) and programs (.\setup.exe).
$ErrorActionPreference = "Stop" will get you part of the way there (i.e. this works great for cmdlets).
However for EXEs you're going to need to check $LastExitCode yourself after every exe invocation and determine whether that failed or not. Unfortunately I don't think PowerShell can help here because on Windows, EXEs aren't terribly consistent on what constitutes a "success" or "failure" exit code. Most follow the UNIX standard of 0 indicating success but not all do. Check out the CheckLastExitCode function in this blog post. You might find it useful.
You should be able to accomplish this by using the statement $ErrorActionPreference = "Stop" at the beginning of your scripts.
The default setting of $ErrorActionPreference is Continue, which is why you are seeing your scripts keep going after errors occur.
Sadly, due to buggy cmdlets like New-RegKey and Clear-Disk, none of these answers are enough. I've currently settled on the following code in a file called ps_support.ps1:
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSDefaultParameterValues['*:ErrorAction']='Stop'
function ThrowOnNativeFailure {
if (-not $?)
{
throw 'Native Failure'
}
}
Then in any powershell file, after the CmdletBinding and Param for the file (if present), I have the following:
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\ps_support.ps1"
The duplicated ErrorActionPreference = "Stop" line is intentional. If I've goofed and somehow gotten the path to ps_support.ps1 wrong, that needs to not silently fail!
I keep ps_support.ps1 in a common location for my repo/workspace, so the path to it for the dot-sourcing may change depending on where the current .ps1 file is.
Any native call gets this treatment:
native_call.exe
ThrowOnNativeFailure
Having that file to dot-source has helped me maintain my sanity while writing powershell scripts. :-)
A slight modification to the answer from #alastairtree:
function Invoke-Call {
param (
[scriptblock]$ScriptBlock,
[string]$ErrorAction = $ErrorActionPreference
)
& #ScriptBlock
if (($lastexitcode -ne 0) -and $ErrorAction -eq "Stop") {
exit $lastexitcode
}
}
Invoke-Call -ScriptBlock { dotnet build . } -ErrorAction Stop
The key differences here are:
it uses the Verb-Noun (mimicing Invoke-Command)
implies that it uses the call operator under the covers
mimics -ErrorAction behavior from built in cmdlets
exits with same exit code rather than throwing exception with new message
You need slightly different error handling for powershell functions and for calling exe's, and you need to be sure to tell the caller of your script that it has failed. Building on top of Exec from the library Psake, a script that has the structure below will stop on all errors, and is usable as a base template for most scripts.
Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"
# Taken from psake https://github.com/psake/psake
<#
.SYNOPSIS
This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode
to see if an error occcured. If an error is detected then an exception is thrown.
This function allows you to run command-line programs without having to
explicitly check the $lastexitcode variable.
.EXAMPLE
exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed"
#>
function Exec
{
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
[Parameter(Position=1,Mandatory=0)][string]$errorMessage = ("Error executing command {0}" -f $cmd)
)
& $cmd
if ($lastexitcode -ne 0) {
throw ("Exec: " + $errorMessage)
}
}
Try {
# Put all your stuff inside here!
# powershell functions called as normal and try..catch reports errors
New-Object System.Net.WebClient
# call exe's and check their exit code using Exec
Exec { setup.exe }
} Catch {
# tell the caller it has all gone wrong
$host.SetShouldExit(-1)
throw
}
I'm new to powershell but this seems to be most effective:
doSomething -arg myArg
if (-not $?) {throw "Failed to doSomething"}
As far as I know, Powershell does not have any automatic handling of non-zero exit codes returned by sub-programs it invokes.
The only solution I know about so far to mimick the behavior of bash -e is to add this check after every call to an external command:
if(!$?) { Exit $LASTEXITCODE }
I came here looking for the same thing. $ErrorActionPreference="Stop" kills my shell immediately when I'd rather see the error message (pause) before it terminates. Falling back on my batch sensibilities:
IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF
I found that this works pretty much the same for my particular ps1 script:
Import-PSSession $Session
If ($? -ne "True") {Pause; Exit}
Seems like simple rethrow does the trick.
param ([string] $Path, [string] $Find, [string] $Replace)
try {
((Get-Content -path $Path -Raw) -replace $Find, $Replace) | Set-Content -Path $Path
Write-Output Completed.
} catch {
# Without try/catch block errors don't interrupt program flow.
throw
}
Now output Completed appears only after successful execution.
for people coming here on 2021 this is my solution that covers both cmdlets and programs
function CheckLastExitCode {
param ([int[]]$SuccessCodes = #(0))
if (!$?) {
Write-Host "Last CMD failed" -ForegroundColor Red
#GoToWrapperDirectory in my code I go back to the original directory that launched the script
exit
}
if ($SuccessCodes -notcontains $LastExitCode) {
Write-Host "EXE RETURNED EXIT CODE $LastExitCode" -ForegroundColor Red
#GoToWrapperDirectory in my code I go back to the original directory that launched the script
exit
}
}
you can use it like this
cd NonExistingpath
CheckLastExitCode
Redirecting stderr to stdout seems to also do the trick without any other commands/scriptblock wrappers although I can't find an explanation why it works that way..
# test.ps1
$ErrorActionPreference = "Stop"
aws s3 ls s3://xxx
echo "==> pass"
aws s3 ls s3://xxx 2>&1
echo "shouldn't be here"
This will output the following as expected (the command aws s3 ... returns $LASTEXITCODE = 255)
PS> .\test.ps1
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
==> pass

Resources