Tell PowerShell ISE to not send stderr to Write-Error - powershell

The PowerShell console and the PowerShell ISE behave differently when executables write to the standard error stream (stderr). The console (powershell.exe) displays it like standard output. For example, when I get a status with Mercurial on a non-repository, it writes to standard error:
> hg st -R C:\Windows
abort: repository C:\Windows not found!
However, in the PowerShell ISE (powershell_ise.exe), that error gets passed to PowerShell's Write-Error cmdlet:
> hg st -R C:\Windows
hg.exe : abort: repository C:\Windows not found!
At line:1 char:3
+ hg <<<< st -R C:\Windows
+ CategoryInfo : NotSpecified: (abort: repository C:\Windows not found!:String) [], RemoteExcepti
on
+ FullyQualifiedErrorId : NativeCommandError
Is there any way to configure the ISE to behave like the console, and not send the stderr stream to Write-Error?

Redirecting the stderr output to stdout "should" work but it doesn't in ISE. In this case, your best bet is to silence the error output like so:
& {
$ErrorActionPreference = 'SilentlyContinue'
hg st -R C:\Windows 2>&1
}
By executing setting this variable in a nested scope, you avoid setting it globally. When the scope above is exited, the global version of $ErrorActionPreference is still set to whatever it was before.
It is unfortunate that ISE and the console behave differently but it is my understanding that with the "console", another console app just gets the console handle so it is outputting directly to the console (bypassing PowerShell). ISE isn't console based so it is trying to make native stderr play nicely with the PowerShell error stream. IMO the behavior of console isn't ideal in this case. So is it better to have ISE be consistent with console or better to have ISE handle stderr better (except the bit about not honoring stream redirection)? Obviously the PowerShell went with the latter.

hg st -R C:\Windows 2>&1 | %{ if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.Exception.Message } else { $_ } }
This preserves the stderr output and sends it as normal output, rather than dropping it.

Related

Newbie powershell argument issues

I made powershell script that [1] accepts 2 arguments (aka parameters), [2] changes a file's modified date & time, and [3] writes something to host. The following command line works just fine in the powershell console, but triggers an error message when I run the same command line in a Windows cmd prompt (DOS) Window:
E:\Apps\UtilitiesByMarc\Change_DateTime_for_test1.bat_and_Hello_world_with_2_named_args_aaa.ps1 -dateTimeVarArg "01/11/2005 06:01:36" -file_dateTimeMod_fullname "E:\Apps\Delete01\test1.bat"
The following is the coding for the powershell script to which I gave the long name, 'Change_DateTime_for_test1.bat_and_Hello_world_with_2_named_args_aaa.ps1':
param ( [string]$dateTimeVarArg, [string]$file_dateTimeMod_fullname)
Get-ChildItem $file_dateTimeMod_fullname | % {$_.LastWriteTime = $dateTimeVarArg}
#Get-ChildItem "E:\Apps\Delete01\test1.bat" | % {$_.LastWriteTime = $dateTimeVarArg}
$strString = "Hello World"
write-host $strString
function ftest{
$test = "Test"
write-host $test
}
ftest
When I run the command line shown above in a Windows DOS command prompt setting, I get the following error message:
Exception setting "LastWriteTime": "Cannot convert null to type "System.DateTime"."
At E:\Apps\UtilitiesByMarc\Change_DateTime_for_test1.bat_and_Hello_world_with_1_named_arg_aaa.ps1:6 char:50
+ ... "E:\Apps\Delete01\test1.bat" | % {$_.LastWriteTime = $dateTimeVarArg}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting
I would like to know [1] how to alter the command line shown above (which works fine in the powershell console) so that it works in a Windows DOS command prompt setting, and [2] where I can learn more about why my command line triggers errors, and how to avoid them.
According to the output from the command "Get-Host | Select-Object Version", I am running version. 5.1.19041.1682.
Any tips would be much appreciated.
By default, you can not directly execute PowerShell scripts (.ps1 files) from cmd.exe, the Windows legacy shell, or from outside PowerShell altogether.
Attempting to do so opens the script file for editing instead, as does double-clicking .ps1 files from File Explorer / the desktop.
Executing .ps1 scripts from outside PowerShell itself requires use of the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7+), which in your case translates to a call such as (additional CLI parameters may be called for):
powershell.exe -File E:\Apps\UtilitiesByMarc\Change_DateTime_for_test1.bat_and_Hello_world_with_2_named_args_aaa.ps1 -dateTimeVarArg "01/11/2005 06:01:36" -file_dateTimeMod_fullname "E:\Apps\Delete01\test1.bat"
As for what you tried:
The fact that you were able to execute your .ps1 from cmd.exe suggests that you changed the file-type definition for such files to execute via powershell.exe instead.
The fact that the arguments you tried to pass to the script were ignored - as implied by the error message you got - suggests that you used File Explorer's Open with shortcut-menu command to choose to open all .ps1 files with powershell.exe; said method does not support argument-passing.
There is a way to change the file-type definition to support argument-passing too, and it is detailed in this answer (section "Programmatic method").
Generally, however, I suggest not applying this customization, especially in batch files that must also be run by other users / on other machines, which cannot be expected to have the same customization in place.

Powershell: Redirecting stderr causes an exception [duplicate]

Why does PowerShell show the surprising behaviour in the second example below?
First, an example of sane behaviour:
PS C:\> & cmd /c "echo Hello from standard error 1>&2"; echo "`$LastExitCode=$LastExitCode and `$?=$?"
Hello from standard error
$LastExitCode=0 and $?=True
No surprises. I print a message to standard error (using cmd's echo). I inspect the variables $? and $LastExitCode. They equal to True and 0 respectively, as expected.
However, if I ask PowerShell to redirect standard error to standard output over the first command, I get a NativeCommandError:
PS C:\> & cmd /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
cmd.exe : Hello from standard error
At line:1 char:4
+ cmd <<<< /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
+ CategoryInfo : NotSpecified: (Hello from standard error :String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
$LastExitCode=0 and $?=False
My first question, why the NativeCommandError?
Secondly, why is $? False when cmd ran successfully and $LastExitCode is 0? PowerShell's documentation about automatic variables doesn't explicitly define $?. I always supposed it is True if and only if $LastExitCode is 0, but my example contradicts that.
Here's how I came across this behaviour in the real-world (simplified). It really is FUBAR. I was calling one PowerShell script from another. The inner script:
cmd /c "echo Hello from standard error 1>&2"
if (! $?)
{
echo "Job failed. Sending email.."
exit 1
}
# Do something else
Running this simply as .\job.ps1, it works fine, and no email is sent. However, I was calling it from another PowerShell script, logging to a file .\job.ps1 2>&1 > log.txt. In this case, an email is sent! What you do outside the script with the error stream affects the internal behaviour of the script. Observing a phenomenon changes the outcome. This feels like quantum physics rather than scripting!
[Interestingly: .\job.ps1 2>&1 may or not blow up depending on where you run it]
(I am using PowerShell v2.)
The '$?' variable is documented in about_Automatic_Variables:
$?
Contains the execution status of the last operation
This is referring to the most recent PowerShell operation, as opposed to the last external command, which is what you get in $LastExitCode.
In your example, $LastExitCode is 0, because the last external command was cmd, which was successful in echoing some text. But the 2>&1 causes messages to stderr to be converted to error records in the output stream, which tells PowerShell that there was an error during the last operation, causing $? to be False.
To illustrate this a bit more, consider this:
> java -jar foo; $?; $LastExitCode
Unable to access jarfile foo
False
1
$LastExitCode is 1, because that was the exit code of java.exe. $? is False, because the very last thing the shell did failed.
But if all I do is switch them around:
> java -jar foo; $LastExitCode; $?
Unable to access jarfile foo
1
True
... then $? is True, because the last thing the shell did was print $LastExitCode to the host, which was successful.
Finally:
> &{ java -jar foo }; $?; $LastExitCode
Unable to access jarfile foo
True
1
...which seems a bit counter-intuitive, but $? is True now, because the execution of the script block was successful, even if the command run inside of it was not.
Returning to the 2>&1 redirect.... that causes an error record to go in the output stream, which is what gives that long-winded blob about the NativeCommandError. The shell is dumping the whole error record.
This can be especially annoying when all you want to do is pipe stderr and stdout together so they can be combined in a log file or something. Who wants PowerShell butting in to their log file??? If I do ant build 2>&1 >build.log, then any errors that go to stderr have PowerShell's nosey $0.02 tacked on, instead of getting clean error messages in my log file.
But, the output stream is not a text stream! Redirects are just another syntax for the object pipeline. The error records are objects, so all you have to do is convert the objects on that stream to strings before redirecting:
From:
> cmd /c "echo Hello from standard error 1>&2" 2>&1
cmd.exe : Hello from standard error
At line:1 char:4
+ cmd &2" 2>&1
+ CategoryInfo : NotSpecified: (Hello from standard error :String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
To:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" }
Hello from standard error
...and with a redirect to a file:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } | tee out.txt
Hello from standard error
...or just:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } >out.txt
This bug is an unforeseen consequence of PowerShell's prescriptive design for error handling, so most likely it will never be fixed. If your script plays only with other PowerShell scripts, you're safe. However if your script interacts with applications from the big wide world, this bug may bite.
PS> nslookup microsoft.com 2>&1 ; echo $?
False
Gotcha! Still, after some painful scratching, you'll never forget the lesson.
Use ($LastExitCode -eq 0) instead of $?
(Note: This is mostly speculation; I rarely use many native commands in PowerShell and others probably know more about PowerShell internals than me)
I guess you found a discrepancy in the PowerShell console host.
If PowerShell picks up stuff on the standard error stream it will assume an error and throw a NativeCommandError.
PowerShell can only pick this up if it monitors the standard error stream.
PowerShell ISE has to monitor it, because it is no console application and thus a native console application has no console to write to. This is why in the PowerShell ISE this fails regardless of the 2>&1 redirection operator.
The console host will monitor the standard error stream if you use the 2>&1 redirection operator because output on the standard error stream has to be redirected and thus read.
My guess here is that the console PowerShell host is lazy and just hands native console commands the console if it doesn't need to do any processing on their output.
I would really believe this to be a bug, because PowerShell behaves differently depending on the host application.
Update: The problems have been fixed in v7.2 - see this answer.
A summary of the problems as of v7.1:
The PowerShell engine still has bugs with respect to 2> redirections applied to external-program calls:
The root cause is that using 2> causes the stderr (standard error) output to be routed via PowerShell's error stream (see about_Redirection), which has the following undesired consequences:
If $ErrorActionPreference = 'Stop' happens to be in effect, using 2> unexpectedly triggers a script-terminating error, i.e. aborts the script (even in the form 2>$null, where the intent is clearly to ignore stderr lines). See GitHub issue #4002.
Workaround: (Temporarily) set $ErrorActionPreference = 'Continue'
Since 2> currently touches the error stream, $?, the automatic success-status variable is invariably set to $False if at least one stderr line was emitted, and then no longer reflects the true success status of the command. See this GitHub issue.
Workaround, as recommended in your answer: only ever use $LASTEXITCODE -eq 0 to test for success after calls to external programs.
With 2>, stderr lines are unexpectedly recorded in the automatic $Error variable (the variable that keeps a log of all errors that occurred in the session) - even if you use 2>$null. See this GitHub issue.
Workaround: Short of keeping track how many error records were added and removing them with $Error.RemoveAt() one by one, there is none.
Generally, unfortunately, some PowerShell hosts by default route stderr output from external programs via PowerShell's error stream, i.e. treat it as error output, which is inappropriate, because many external programs use stderr also for status information, or more generally, for anything that is not data (git being a prime example): Not every stderr line can be assumed to represent an error, and the presence of stderr output does not imply failure.
Affected hosts:
The obsolescent Windows PowerShell ISE and possibly other, older GUI-based IDEs other than Visual Studio Code.
When executing external programs via PowerShell remoting or in a background job (these two invocation mechanisms share the same infrastructure and use the ServerRemoteHost host that ships with PowerShell).
Hosts that DO behave as expected in non-remoting, non-background invocations (they pass stderr lines through to the display and print them normally):
Terminals (consoles), including Windows Terminal.
Visual Studio Code with the PowerShell extension; this cross-platform editor (IDE) is meant to supersede the Windows PowerShell ISE.
This inconsistency across hosts is discussed in this GitHub issue.
For me it was an issue with ErrorActionPreference.
When running from ISE I've set $ErrorActionPreference = "Stop" in the first lines and that was intercepting everything event with *>&1 added as parameters to the call.
So first I had this line:
& $exe $parameters *>&1
Which like I've said didn't work because I had $ErrorActionPreference = "Stop" earlier in file (or it can be set globally in profile for user launching the script).
So I've tried to wrap it in Invoke-Expression to force ErrorAction:
Invoke-Expression -Command "& `"$exe`" $parameters *>&1" -ErrorAction Continue
And this doesn't work either.
So I had to fallback to hack with temporary overriding ErrorActionPreference:
$old_error_action_preference = $ErrorActionPreference
try
{
$ErrorActionPreference = "Continue"
& $exe $parameters *>&1
}
finally
{
$ErrorActionPreference = $old_error_action_preference
}
Which is working for me.
And I've wrapped that into a function:
<#
.SYNOPSIS
Executes native executable in specified directory (if specified)
and optionally overriding global $ErrorActionPreference.
#>
function Start-NativeExecutable
{
[CmdletBinding(SupportsShouldProcess = $true)]
Param
(
[Parameter (Mandatory = $true, Position = 0, ValueFromPipelinebyPropertyName=$True)]
[ValidateNotNullOrEmpty()]
[string] $Path,
[Parameter (Mandatory = $false, Position = 1, ValueFromPipelinebyPropertyName=$True)]
[string] $Parameters,
[Parameter (Mandatory = $false, Position = 2, ValueFromPipelinebyPropertyName=$True)]
[string] $WorkingDirectory,
[Parameter (Mandatory = $false, Position = 3, ValueFromPipelinebyPropertyName=$True)]
[string] $GlobalErrorActionPreference,
[Parameter (Mandatory = $false, Position = 4, ValueFromPipelinebyPropertyName=$True)]
[switch] $RedirectAllOutput
)
if ($WorkingDirectory)
{
$old_work_dir = Resolve-Path .
cd $WorkingDirectory
}
if ($GlobalErrorActionPreference)
{
$old_error_action_preference = $ErrorActionPreference
$ErrorActionPreference = $GlobalErrorActionPreference
}
try
{
Write-Verbose "& $Path $Parameters"
if ($RedirectAllOutput)
{ & $Path $Parameters *>&1 }
else
{ & $Path $Parameters }
}
finally
{
if ($WorkingDirectory)
{ cd $old_work_dir }
if ($GlobalErrorActionPreference)
{ $ErrorActionPreference = $old_error_action_preference }
}
}

How to handle errors in a way that works the same whether the script runs in "regular" powershell console or in powershell ISE?

I have this simple script:
$ErrorActionPreference = "Stop"
try
{
cmd /c mklink a .\DataSvc.sln
}
catch
{
"Failed"
}
(file DataSvc.sln exists)
When I run it in ISE powershell console it prints "Failed", when I do it from the "regular" powershell console it outputs "You do not have sufficient privilege to perform this operation.":
ISE:
Regular:
How am I supposed to write it so that it prints "Failed" in both cases?
EDIT 1
You must run it as a regular account (not elevated) with Windows 10 Developer Mode turned off. If you do not know what Windows 10 Developer Mode is, then you are fine (for this question).
Unfortunately, different hosts treat stderr output from external programs differently.
The ISE routes stderr output to PowerShells own error stream, which explains why anything written to stderr triggers the try / catch handler.
On a side node: Consider switching from the obsolescent ISE to Visual Studio Code with its PowerShell extension. Future development efforts are focused there, and the behavior doesn't differ from the console (at least in this respect).
The regular console patches stderr output through to the console, in which case no error is triggered.
Due to a bug, you can currently (Windows PowerShell v5.1 / PowerShell Core v6.1) trigger an error in the console just by redirecting stream number 2 in PowerShell:
$ErrorActionPreference = "Stop"
try {
cmd /c 'echo tostderr >&2' 2>&1 # even 2>$null would trigger an error(!)
} catch {
"Failed"
}
However, I wouldn't rely on that, as the bug may - and hopefully will - get fixed.
Taking a step back: As implied by the link in the comments, whether an external program failed should only ever derived from its exit code, not from the presence of stderr output, as many program use stderr output to report information other than errors (such as diagnostic information or mere warnings).
Thus, if ($LASTEXITCODE -ne 0) alone should be used to determine failure.
If, for some reason, you do need to infer failure from the presence of stderr output - e.g., because some programs don't properly reflect failure in their exit codes - you can try the following approach, which should work now as well as after the aforementioned bug is fixed:
$ErrorActionPreference = "Stop"
try {
cmd /c 'echo tostderr >&2' 2>&1 | ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) { Throw $_ }
$_
}
} catch {
"Failed"
}
This relies on merging the error stream into the success stream and then detecting stderr-originated lines by their type.
Note that while PowerShell-internally you can collect a command's error output in a variable with common parameter -ErrorVariable / -ev, there is no analogous mechanism when calling external programs; however, introducing such a mechanism has been proposed in this GitHub issue.

PowerShell Streaming Output

I'd like to capture some streaming output in PowerShell. For example
cmd /c "echo hi && foo"
This command should print hi and then bomb. I know that I can use -ErrorVariable:
Invoke-Command { cmd /c "echo hi && foo" } -ErrorVariable ev
however there is an issue: in the case of long running commands, I want to stream the output, not capture it and only get the stderr/stdout output at the end of the command
Ideally, I'd like to be able to split stderr and stdout and pipe to two different streams - and pipe the stdout back to the caller, but be prepared to throw stderr in the event of an error. Something like
$stdErr
Invoke-Command "cmd" "/c `"echo hi && foo`"" `
-OutStream (Get-Command Write-Output) `
-ErrorAction {
$stdErr += "`n$_"
Write-Error $_
}
if ($lastexitcode -ne 0) { throw $stdErr}
the closest I can get is using piping, but that doesn't let me discriminate between stdout and stderr so I end up throwing the entire output stream
function Invoke-Cmd {
<#
.SYNOPSIS
Executes a command using cmd /c, throws on errors.
#>
param([string]$Cmd)
)
$out = New-Object System.Text.StringBuilder
# I need the 2>&1 to capture stderr at all
cmd /c $Cmd '2>&1' |% {
$out.AppendLine($_) | Out-Null
$_
}
if ($lastexitcode -ne 0) {
# I really just want to include the error stream here
throw "An error occurred running the command:`n$($out.ToString())"
}
}
Common usage:
Invoke-Cmd "GitVersion.exe" | ConvertFrom-Json
Note that an analogous version that just uses a ScriptBlock (and checking the output stream for [ErrorRecord]s isn't acceptable because there are many programs that "don't like" being executed directly from the PowerShell process
The .NET System.Diagnostics.Process API lets me do this...but I can't stream output from inside the stream handlers (because of the threading and blocking - though I guess I could use a while loop and stream/clear the collected output as it comes in)
- The behavior described applies to running PowerShell in regular console/terminal windows with no remoting involved. With remoting and in the ISE, the behavior is different as of PSv5.1 - see bottom.
- The 2>$null behavior that Burt's answer relies on - 2>$null secretly still writing to PowerShell's error stream and therefore, with $ErrorActionPreference Stop in effect, aborting the script as soon as an external utility writes anything to stderr - has been classified as a bug and is likely to go away.
When PowerShell invokes an external utility such as cmd, its stderr output is passed straight through by default. That is, stderr output prints straight to the console, without being included in captured output (whether by assigning to a variable or redirecting to a file).
While you can use 2>&1 as part of the cmd command line, you won't be able to distinguish between stdout and stderr output in PowerShell.
By contrast, if you use 2>&1 as a PowerShell redirection, you can filter the success stream based on the input objects' type:
A [string] instance is a stdout line
A [System.Management.Automation.ErrorRecord] instance is a stderr line.
The following function, Invoke-CommandLine, takes advantage of this:
Note that the cmd /c part isn't built in, so you would invoke it as follows, for instance:
Invoke-CommandLine 'cmd /c "echo hi && foo"'
There is no fundamental difference between passing invocation of a cmd command line and direct invocation of an external utility such as git.exe, but do note that only invocation via cmd allows use of multiple commands via operators &, &&, and ||, and that only cmd interprets %...%-style environment-variable references, unless you use --%, the stop-parsing symbol.
Invoke-CommandLine outputs both stdout and stderr line as they're being received, so you can use the function in a pipeline.
As written, stderr lines are written to PowerShell's error stream using Write-Error as they're being received, with a single, generic exception being thrown after the external command terminates, should it report a nonzero $LASTEXITCODE.
It's easy to adapt the function:
to take action once the first stderr line is received.
to collect all stderr lines in a single variable
and/or, after termination, to take action if any stderr input was received, even with $LASTEXITCODE reporting 0.
Invoke-CommandLine uses Invoke-Expression, so the usual caveat applies: be sure you know what command line you're passing, because it will be executed as-is, no matter what it contains.
function Invoke-CommandLine {
<#
.SYNOPSIS
Executes an external utility with stderr output sent to PowerShell's error '
stream, and an exception thrown if the utility reports a nonzero exit code.
#>
param([parameter(Mandatory)][string] $CommandLine)
# Note that using . { ... } is required around the Invoke-Expression
# call to ensure that the 2>&1 redirection works as intended.
. { Invoke-Expression $CommandLine } 2>&1 | ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) { # stderr line
Write-Error $_ # send stderr line to PowerShell's error stream
} else { # stdout line
$_ # pass stdout line through
}
}
# If the command line signaled failure, throw an exception.
if ($LASTEXITCODE) {
Throw "Command failed with exit code ${LASTEXITCODE}: $CommandLine"
}
}
Optional reading: how calls to external utilities fit into PowerShell's error handling
Current as of: Windows PowerShell v5.1, PowerShell Core v6-beta.2
The value of preference variable $ErrorActionPreference only controls the reaction to errors and .NET exceptions that occur in PowerShell cmdlet/function calls or expressions.
Try / Catch is for catching PowerShell's terminating errors and .NET exceptions.
In a regular console window with no remoting involved, external utilities such as cmd currently never generate either error - all they do is report an exit code, which PowerShell reflects in automatic variable $LASTEXITCODE, and automatic variable $? reflects $False if the exit code is nonzero.
Note: The fact that the behavior differs fundamentally in hosts other than the console host - which includes the Windows ISE and when remoting is involved - is problematic: There, calls to external utilities result in stderr output treated as if non-terminating errors had been reported; specifically:
Every stderr output line is output as an error record and also recorded in the automatic $Error collection.
In addition to $? being set to $false with a nonzero exit code, the presence of any stderr output also sets it to $False.
This behavior is problematic, as stderr output by itself does not necessarily indicate an error - only a nonzero exit code does.
Burt has created an issue in the PowerShell GitHub repository to discuss this inconsistency.
By default, stderr output generated by an external utility is passed straight through to the console - they are not captured by PowerShell variable assignments or (success-stream) output redirections.
As discussed above, this can be changed:
2>&1 as part of a command line passed to cmd sends stdout and stderr combined to PowerShell's success stream, as strings, with no way to distinguish between whether a given line was a stdout or stderr line.
2>&1 as a PowerShell redirection sends stderr lines to PowerShell's success stream too, but you can distinguish between stdout- and stderr-originated lines by their DATA TYPE: a [string]-typed line is a stdout-originated line, whereas a [System.Management.Automation.ErrorRecord]-typed line is a stderr-originated one.
Note: updated sample below should now work across PowerShell hosts. GitHub issue Inconsistent handling of native command stderr has been opened to track the discrepancy in previous example. Note however that as it depends on undocumented behavior, the behavior may change in the future. Take this into consideration before using it in a solution that must be durable.
You are on the right track with using pipes, you probably don't need Invoke-Command, almost ever. Powershell DOES distinguish between stdout and stderr. Try this for example:
cmd /c "echo hi && foo" | set-variable output
The stdout is piped on to set-variable, while std error still appears on your screen. If you want to hide and capture the stderr output, try this:
cmd /c "echo hi && foo" 2>$null | set-variable output
The 2>$null part is an undocumented trick that results in the error output getting appended to the PowerShell $Error variable as an ErrorRecord.
Here's an example that displays stdout, while trapping stderr with an catch block:
function test-cmd {
[CmdletBinding()]
param()
$ErrorActionPreference = "stop"
try {
cmd /c foo 2>$null
} catch {
$errorMessage = $_.TargetObject
Write-warning "`"cmd /c foo`" stderr: $errorMessage"
Format-list -InputObject $_ -Force | Out-String | Write-Debug
}
}
test-cmd
Generates the message:
WARNING: "cmd /c foo" stderr: 'foo' is not recognized as an internal or external command
If you invoke with debug output enabled, you'll alsow see the details of the thrown ErrorRecord:
DEBUG:
Exception : System.Management.Automation.RemoteException: 'foo' is not recognized as an internal or external command,
TargetObject : 'foo' is not recognized as an internal or external command,
CategoryInfo : NotSpecified: ('foo' is not re...ternal command,:String) [], RemoteException
FullyQualifiedErrorId : NativeCommandError
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at test-cmd, <No file>: line 7
at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}
PSMessageDetails :
Setting $ErrorActionPreference="stop" causes PowerShell to throw an exception when the child process writes to stderr, which sounds like it's the core of what you want. This 2>$null trick makes the cmdlet and external command behavior very similar.

Why is Powershell ISE showing errors that Powershell console does not show?

I'm running exactly the same script.ps1 file in a Powershell ISE (manually loading the script and pressing F5) and in a Powershell console (executing the script file). They both work but ISE shows errors that the console does not. Why?
The code is:
git push origin master
Write-Host "lastExitCode: $lastExitCode Last command was successful: $?"
This code output this error in the ISE:
git.cmd : Initializing to normal mode
At E:\script.ps1:28 char:4
+ git <<<< push origin master
+ CategoryInfo : NotSpecified: (Initializing to normal mode:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Initializing to normal mode
Everything up-to-date
lastExitCode: 0 Last command was successful: False
And this in the console:
Everything up-to-date
lastExitCode: 0 Last command was successful: True
You can see that the success status is not the same also.
I don't know why they output differently, but the message that we see from git push is coming over stderr. This means that they are both showing errors, although the ISE is making them much louder, and converting it into error objects.
Consider this output from the PowerShell prompt:
PS> git push
Everything up-to-date
PS> git push 2> $null # Redirect stderr to $null
PS> $LastExitCode
1
PS>
and compare it to the ISE:
PS> git push
git : Everything up-to-date
At line:1 char:1
+ git push
+ ~~~~~~~~
+ CategoryInfo : NotSpecified: (Everything up-to-date:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
PS> git push 2> $null
PS> $LastExitCode
1
PS>
Except for the extra output from the error object being displayed, the output is the same. The ISE has converted the stderr string to a NativeCommandError object, and it does even display the error message if you look past the red.
as shown in this Stackoverflow answer to prevent git push to print to STDERR the solution is to call the command witn --porcelain option.
then, calling
git push origin master --porcelain
output goes all to STDOUT
So, the example below case have any error , this command -q 2>&1 | %{ "$_" }` will nullifying the result of errors.
A solution and use: git push -q 2>&1 | %{ "$_" }
Hmmm, the only major difference I can think of right off the top of my head is that the ISE uses single-threaded apartment (STA) mode in v2, and the console uses multi-threaded apartment (MTA). Have you tried running powershell.exe with the -STA argument, or powershell_ise.exe with -MTA, and trying the script again?
It looks like the error is coming from the Git command you're trying to run, FWIW.