Can I use -ErrorVariable while using Out-String in Powershell - powershell

I'm trying to capture the output of a command into output variable in Powershell. The output of the command is in table format which is why I'm using Out-String. Now I need to suppress the error messages which is why I use an error variable to store the error messages. I tried the following things but none of them suppress my error and error gets displayed on screen.
$output = $esxcli.network.firewall.ruleset.allowedip.list() | Out-String -ErrorVariable myErr
$output = $esxcli.network.firewall.ruleset.allowedip.list() -ErrorVariable myErr | Out-String
$output = $esxcli.network.firewall.ruleset.allowedip.list() | Out-String 2>&1
$output = $esxcli.network.firewall.ruleset.allowedip.list() 2>&1 | Out-String
Is there a way where I can suppress the errors while using Out-String in a simple way (nothing like try-catch)?

If your .list() method is throwing an exception you have two options:
Set your $ErrorActionPreference variable to 'SilentlyContinue' or 'Ignore'
$ErrorActionPreference = 'SilentlyContinue'
Wrap your method call in a try/catch block
try
{
$output = $esxcli.network.firewall.ruleset.allowedip.list() | Out-String
}
catch
{
"Error thrown! $PSItem"
}
Do note if your method call is throwing a terminating error, you do not have a workaround.

To add a bit more detail to your original question about -ErrrorVariable. -ErrorVariable is a common parameter which you get by default when adding the CmdletBinding attribute to the beginning of a function.
The .list() is a .Net method not a PowerShell function and thus -ErrorVariable does not work with it. You can however write a short function to wrap the .Net method in a small PowerShell function if you would like to use it a lot and would like to leverage some of PowerShell's awesomeness with that method.
Example:
function Get-ESXAllowedIPList {
[CmdletBinding()]
param($esxcli)
return $esxcli.network.firewall.ruleset.allowedip.list()
}
You can then use it like this:
Get-ESXAllowedIPList -ErrorAction SilentlyContinue -ErrorVariable ESXAllowedIPListErrors | Out-String

Related

How to handle -WhatIf inside ForEach-Object -Parallel script block

There is a script that has CmdletBinding attribute, which effectively makes it an “advanced” script. Inside the script I'm processing data in a pipeline in parallel, and I want the -WhatIf parameter to be passed down to the processing script block when I pass it to the script invocation.
Simplified code:
#Requires -Version 7.2
[CmdletBinding(SupportsShouldProcess = $true)]
param()
Get-ChildItem | ForEach-Object -Parallel {
if ($PSCmdlet.ShouldProcess("target", "operation")) {
Write-Host "Processing"
}
}
PS C:\> script.ps1 -WhatIf
InvalidOperation:
Line |
2 | if ($PSCmdlet.ShouldProcess("target", "operation")) {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| You cannot call a method on a null-valued expression.
This does not work because $PSCmdlet is not defined in the script block.
When I replace $PSCmdlet with ($using:PSCmdlet), I get another error (only when -WhatIf is provided):
MethodInvocationException:
Line |
2 | if (($using:PSCmdlet).ShouldProcess("target", "operation")) {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Exception calling "ShouldProcess" with "2" argument(s): "The WriteObject and WriteError methods cannot be called from outside the overrides of the BeginProcessing, ProcessRecord, and EndProcessing methods, and they can only be called from within the same thread. Validate that the cmdlet makes these calls correctly,
or contact Microsoft Customer Support Services."
Obviously, this happens because script blocks are executed in seaprate threads (“they can only be called from within the same thread”).
How to properly handle -WhatIf inside the script blocks of Foreach-Object -Parallel?
I've read this official article and seen this comment to the PowerShell issue #13816. Maybe another related issue: #14984.
As a side-note: specifying -WhatIf to the ForEach-Object itself doesn't make any difference in this case. This is also noticed here: https://thedavecarroll.com/powershell/foreach-object-whatif/#script-blocks-and--whatif
The closest I could get to something working, as you can see it is very cumbersome. First, as stated, Risk Management parameters do not seem to work in ForEach-Object -Parallel nor Start-ThreadJob. Using the function from this answer, -WhatIf seem to work properly, however the workaround does require an inner script block also supporting SupportsShouldProcess.
This is how the code would look:
[CmdletBinding(SupportsShouldProcess)]
param()
Get-ChildItem | Invoke-Parallel {
& {
[CmdletBinding(SupportsShouldProcess)]
param()
if ($PSCmdlet.ShouldProcess($_, "doing something")) {
Write-Host "Processing"
}
} -WhatIf:$WhatIf
} -Variables #{ WhatIf = [bool] $PSBoundParameters['WhatIf'] }

Non-terminating Error Handling - Difference between if $Error.count and $ErrorActionPreference = 'stop'

I need to handle a non-terminating error in a powershell script. What is the most efficient way to this?
To set $ErrorActionPreference variable to stop and use try/catch
$ErrorActionPreference = 'stop'
try{
functionThatCanFail
}catch{
#Do Stuff
}
Or to clear $Error variable and then evaluate if it is populated
$Error.Clear()
functionThatCanFail
if( $Error.Count -ne 0){
#Do Stuff
}
I would add [CmdletBinding()]to your function and put it inside a try/catch.
Because of the CmdletBinding you are now able to call your function with the parameter -ErrorAction Stop.
The other suggestion with the $ErrorActionor $Error.Clear()would also work but is not a 'clean' way.
function functionThatCanFail
{
[CmdletBinding()]
param
(
$Param1
)
#Do stuff
}
try
{
functionThatCanFail -ErrorAction Stop
}
catch
{
#Error case
}
The simplest approach is to use the -ErrorVariable / -ev common parameter, which records all non-terminating errors reported by a given cmdlet in a designated variable.
Note, however, that this only works with cmdlets and advanced functions / scripts, because only they support common parameters - see Patrick's helpful answer for how to define your own function as an advanced function.
# Provoke a non-terminating error and silence it,
# but store it in custom variable $err via common parameter -ErrorVariable
Get-Item /NoSuchFile -ErrorAction SilentlyContinue -ErrorVariable err
if ($err) { # if a / a least one non-terminating error was reported, handle it.
"The following non-terminating error(s) occurred:`n$err"
}
That way, the error analysis is command-scoped, without having to record the state of or alter the state of the session-level $Error collection.
Note, however, that you cannot handle terminating errors this way.
For a comprehensive overview of PowerShell error handling, see here.

Powershell redirect std error to variable

I would like to invoke an arbitrary expression and redirect std error to a variable without redirection to a file.
For example, in Powershell it is possible to redirect standard error using 2> operator. Using a temporary file, I can easily get what I want:
#$expr = "1/0" # with std error
#$expr = "2+2" # without stderror
#$expr = "Get-Service invalidsvc" # with stderror
$expr = "try { throw '111' } catch { }" # without std error
$ans = Invoke-Expression $expr -ErrorVariable ev 2> C:\log\stderr.txt
if(cat C:\log\stderr){
Write-Host "Error $ev"
}
How can I do the same, but without creation of a temporal output file?
Wrong solutions:
Using -ErrorVariable switch. Counter example:
Invoke-Expression $expr -ErrorVariable ev 2> C:\aaa\file1.txt
$expr = "try { throw '111' } catch { }"
Write-Host "Error $ev" # will output error, but std error is empty
$LASTEXITCODE and $? check. Counter example:
Get-Service invalidservice
$lastexitcode is equal to 0, $? is equal to True, but std error is not empty
The idea is simple: save the "red" (std error) text in Powershell console in a variable. The command I receive is an arbitrary string.
Examples:
When I write "try { throw '111' } catch { }" in Powershell console there will be no red (error) text in PS console (despite the fact $error is not empty). So if I invoke that expression in my code I get no error saved in some variable.
When I write "Get-Service notexistingservice", or "1/0", or "Write-Error 111" there will red (error) text and non-null $error. So if I invoke that expression in my code I would like to get error saved in some variable.
Save standard output and standard error to separate variables. It won't work without the dollar sign (from Windows Powershell in Action).
$err = $( $output = get-childitem foo3 ) 2>&1
The way to do it is the -errorvariable common parameter. Your counter example is only valid (and I only hesitantly use that word) because you have explicitly coded for it to not output an error with the use of the Try/Catch and not including anything coding in your catch. You are basically complaining that you told PowerShell to send error cases to the Catch scriptblock, where you did not output anything, and then having an issue when nothing is output. An error still occurs, it is logged in the errorvariable as you stated it should be, and also stored in $Error, but since you did not output anything in your Catch block there's nothing for your StdErr redirect to do.
If you want $ev to not contain an error because you corrected the issue in your Catch block, then also clear the variable in the catch block.
$expr = 'try{ throw "1111"}catch{Remove-Variable ev}'
Or if you want StdErr to contain the error text, make sure you include that output in your Catch block:
$expr = 'try{ throw "1111"}catch{Write-Error $_.Exception.Message}'
I know this is a very old question but I had the same problem and wanted to share what I ended up with.
$error.clear()
$List = New-Object PSObject
Invoke-Command -ComputerName $server -ScriptBlock {
$smbv1 = (Get-SmbServerConfiguration | Select EnableSMB1Protocol)
$smbv1 | Select-Object EnableSMB1Protocol} -OutVariable List
foreach($item in $list.EnableSMB1Protocol){
if($error -ne $null){
$item = "unknown"
$msg = $error.Exception.Message
ac .\smb1errors.txt "$Server, $msg"
}
Since the $error variable is a .NET object in order to clear it I needed to pass it parameter (). I then executed my code, in this case checking for SMBv1 service, and testing if $error is still $null. If the $error variable has content I grabed it in a variable. $msg = $error.Exception.Message

Powershell verbose output for chained exceptions

I'm new to powershell and troubleshooting an issue with one of our custom cmdlets. By default, all exceptions thrown within the cmdlet have minimum information, no stack trace and no info on chained exceptions. Is there a way to enable verbose output of exceptions?
the $error collection contains a live list of all unhandled exceptions thrown in the current session. The last exception is at $error[0]. A good technique to do something like this to capture the error as soon as possible:
ps> invoke-something
error: ...
ps> $e = $error[0]
Explore $e with get-member.
I've used this technique to get nested error objects:
$error[0]|format-list -force
Here's a neat function that I've stolen from someone on the 'net :). I have it in my profile and will happily spread it further:
#Get detailed information on an error
function Resolve-Error ($ErrorRecord=$Error[0])
{
$ErrorRecord | Format-List * -Force
$ErrorRecord.InvocationInfo |Format-List *
$Exception = $ErrorRecord.Exception
for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException))
{ "$i" * 80
$Exception |Format-List * -Force
}
}

Try method in powershell

So I want to build a try method into my powershell script below. If I am denied access to a server, I want it to skip that server. Please help..
[code]$Computers = "server1", "server2"
Get-WmiObject Win32_LogicalMemoryConfiguration -Computer $Computers | Select-Object `
#{n='Server';e={ $_.__SERVER }}, `
#{n='Physical Memory';e={ "$('{0:N2}' -f ($_.TotalPhysicalMemory / 1024))mb" }}, `
#{n='Virtual Memory';e={ "$('{0:N2}' -f ($_.TotalPageFileSpace / 1024))mb" }} | `
Export-CSV "output.csv"[/code]
Try/catch functionality is built-into PowerShell 2.0 e.g.:
PS> try {$i = 0; 1/$i } catch { Write-Debug $_.Exception.Message }; 'moving on'
Attempted to divide by zero.
moving on
Just wrap you script in a similar try/catch. Note you could totally ignore the error by leaving the catch block empty catch { } but I would recommend at least spitting out the error info if your $DebugPreference is set to 'Continue'.
You can simply suppress errors with the ErrorAction parameter:
Get-WmiObject Win32_LogicalMemoryConfiguration -Computer $Computers -ErrorAction SilentlyContinue | ...
You can use trap to replicate Try/Catch, see http://huddledmasses.org/trap-exception-in-powershell/ or http://weblogs.asp.net/adweigert/archive/2007/10/10/powershell-try-catch-finally-comes-to-life.aspx for examples.
Use a filter function? Like this tutorial explains.
He passes a list of computers to his pipeline - first it tries to ping each one, and then only passes the ones that respond to the next command (reboot). You could customize this for whatever actual functionality you wanted.