Powershell redirect std error to variable - powershell

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

Related

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.

The way to differ thrown and swallowed exception and an error written to the pipeline

Given the function that receives Powershell code as a text and execute it:
function Exec
{
param(
[string]$expression
)
$Error.Clear()
$Result = $Null
try
{
$Result = Invoke-Expression $expression
}
catch {
$Result = $_
$Error.Clear()
}
finally
{
if($Error.Count -gt 0)
{
$Result = $Error[0]
}
}
Write-Host $Result
}
Three types of expressions might be passed to this function:
$expression1 = "try {throw 'some exception'}catch {'some result'}"
$expression2 = "throw 'another exception'"
$expression3 = "Write-Error 'something went wrong...'"
When I pass the first one I would like "some result" to be printed, however I'll see "some exception" instead of this, because $result was rewritten in Finally block.
Is there any way to print actual result, but not the last error here and not break down working of this code with other two types of expressions?
It would make things more clear if OP specified desired behavior in the various cases you are trying to handle, rather than giving a set of 3 (what are essentially) unit tests. Having 3 unit tests is problematic because: 1) the person answering the question has to reverse engineer the desired behavior by first guessing at what the 3 unit tests are supposed to yield and second by guessing what behavior that result implies and 2) 3 unit tests aren't enough to cover the gamut of behaviors (if I'm guessing the intended behaviors correctly).
The gamut of behaviors I'm guessing are: 1) expression executes without errors, 2) expression executes with one non-terminating error, 3) expression executes with multiple non-terminating errors, 4) a terminating error, 5) a single non-terminating error and a terminating error, 6) multiple non-terminating errors and a terminating error
With that said how about this:
function Exec
{
param(
[string]$expression
)
# Not usually necessary to clear $error but could be done here if
# really necessary, by uncommenting next line
# $error.clear()
$preErrorCount = $postErrorCount = $Error.count
$Result = $Null
# If $expression executes without errors, then $result is value of
# $expression. If a terminating error occurs, then $result is the
# ErrorRecord that caused the termination. If only non-terminating error(s)
# occurred then $result is $error[0] (most recent non-terminating error)
try
{
$Result = Invoke-Expression $expression
}
catch {
$Result = $_
$postErrorCount = $Error.count
}
if($Error.Count -ne $preErrorCount) {
# Either (or both) terminating and/or non-terminating error occurred
if ($postErrorCount -ne $preErrorCount) {
# Terminating error occurred. $Result is already set correctly, nothing to do
} else {
# Only non-terminating error(s) occurred, copy most recent
$result = $error[0]
}
}
# In the OP $Result is not returned by function, only written to host
# Preserving that behavior
Write-Host $Result
# NOTE: in OP script does not clear $error if non-terminating error occurs
# but clears it if terminating error occurs. Probably a bug.
# Uncomment following if $error should be cleared on both terminating and
# non-terminating errors before returning to caller. (In general callers
# of PS cmds shouldn't assume $error is cleared or preserved across the call)
# $error.clear()
}
This function works as I want:
function Exec
{
param(
[string]$expression
)
$Error.Clear()
$Result = $Null
try
{
$Result = Invoke-Expression $expression -ErrorAction Stop
}
catch {
$Result = $_
}
Write-Host $Result
}

How to redirect Access Denied and all other errors in Powershell to a variable (Loop until command doesnt fail)

I am trying to create a bootstrap script to setup servers and add them to the domain. The problem is some of the networking changes do not get implemented for a varying amount of time causing the command to join the domain to fail (always with the same error). TO get around this all I have to do is run the command again a minute later but this is not practical as part of a long script.
My solution is along the lines of:
$exit = 1
while ($exit -ne 0){
$exit = 0
join-domain-command
$exit = $?
sleep 20
}
However $exit is always 0/false even when I put in some non existent command that is bound to fail.
My other idea was to redirect all errors to a variable and search the variable for text containing the error I commonly come across and if that error is not present, stop the loop.
Trying to redirect the stderr in powershell doesn't work for me either
$exit = & (dummy-command -confirm:$false) 2>$exit
echo "exit = $exit"
SO here I deliberately set the ExecPol in an un-elevated prompt:
you can use -errorvariable
$exit = 1
while ($exit -ne 0){
$exit = 0
join-domain-command -errorvariable $error
$exit = $?
sleep 20
}
Or if you want to do something with the error:
try {
join-domain-command -errorvariable $error
}
catch{
$error | out-file C:\errors.txt -append
#or if $error -contains "something"
#...do something
}
then search the text file for your errors
EDIT
So a few things the actual correct use of errorVariable doesnt use the $ so it would be -errorvariable myError
If you want to search an error a better way to do it would be this:
while ($exit -ne 0)
{
try {
join-domain-command
}
catch{
if(!($error[0].tostring().contains("specific error text"))) #error text is not your specific error
{$exit = 1}
}
sleep 20
}
All errors can be found in $error and if you want to check that last error you use $error[0] which give you the last error that was received.
I usually do something like the following and I put it in a separate function to keep the main code path clean. A counter can be added to limit the number of retries.
$Succeeded = $false
while($Succeeded -eq $false){
try{
#commands that may fail
$Succeeded = $true
}
catch{
#write a message or log something
}
start-sleep -s 60
}

How to ignore warning errors?

I have the following PowerShell script. It picks up the NetBIOS name of computers within a given IP address. I'm using a pipe so as to dump the results into a text file. The problem is that if an IP address is not available, a warning is printed.
This is the PowerShell script:
function Get-ComputerNameByIP {
param( $IPAddress = $null )
BEGIN {
$prefBackup = $WarningPreference
$WarningPreference = 'SilentlyContinue'
}
PROCESS {
if ($IPAddress -and $_) {
throw ‘Please use either pipeline or input parameter’
break
} elseif ($IPAddress) {
([System.Net.Dns]::GetHostbyAddress($IPAddress))
}
} else {
$IPAddress = Read-Host “Please supply the IP Address”
[System.Net.Dns]::GetHostbyAddress($IPAddress)
}
}
END {
$WarningPreference = $prefBackup
}
This is the error message I wish to ignore:
WARNING: The requested name is valid, but no data of the requested type was found
You can use common parameter -WarningAction:SilentlyContinue with the command that generates warning. It's better than separately overriding $WarningPreference before executing the command and reverting it back afterwards as was suggested above - this parameter basically does that for you.
The WarningAction parameter overrides the value of the $WarningPreference variable for the current command. Because the default value of the $WarningPreference variable is Continue, warnings are displayed and execution continues unless you use the WarningAction parameter.
See more here.
You want to suppress warnings, not errors. Warnings can be silenced completely by setting the $WarningPreference variable to SilentlyContinue:
PS C:\> Write-Warning 'foo'
WARNING: foo
PS C:\> $prefBackup = $WarningPreference
PS C:\> $WarningPreference = 'SilentlyContinue'
PS C:\> Write-Warning 'foo'
PS C:\> $WarningPreference = $prefBackup
PS C:\> Write-Warning 'foo'
WARNING: foo
The setting pertains to the current scope, so if you want to suppress all warnings for your function you'd simply set the preference at the beginning of your function:
function Get-ComputerNameByIP {
param( $IPAddress = $null )
BEGIN {
$WarningPreference = 'SilentlyContinue'
}
PROCESS {
if ($IPAddress -and $_) {
throw ‘Please use either pipeline or input parameter’
break
} elseif ($IPAddress) {
[System.Net.Dns]::GetHostbyAddress($IPAddress)
}
[System.Net.Dns]::GetHostbyAddress($_)
} else {
$IPAddress = Read-Host "Please supply the IP Address"
[System.Net.Dns]::GetHostbyAddress($IPAddress)
}
}
END {}
}
If you want warnings suppressed for specific statements only, a simpler way is to redirect the warning output stream to $null:
[System.Net.Dns]::GetHostbyAddress($IPAddress) 3>$null
Warning stream redirection is only available in PowerShell v3 and newer, though.
$ErrorActionPreference = 'SilentlyContinue'
This global var controls error output of those commands that provide intermittent (non-terminating) errors and warnings. Your error is of this kind, so set preference to silently continue to suppress these warnings.
You could use a try/catch block for something like this. Consider the following example using a proper formed IP address but had no associated record.
try{
[System.Net.Dns]::GetHostbyAddress("127.0.0.56")
} Catch [System.Management.Automation.MethodInvocationException]{
Write-Host "Nothing Record Found"
}
When I tested this the error you were seeing was being caught as [System.Management.Automation.MethodInvocationException] so I checked for that specific error type. Based on it's name I'm sure there are other reasons for it to be called. It is possible to just omit that part altogether and it will catch all errors. Since you account for some other possibilities maybe you don't need it.
If that was a concern you could check the text of the $_.Exception.InnerException to see if it matches the error as well. In the above case it contains the text "The requested name is valid, but no data of the requested type was found".
This might be wrong because I am curious as to why your error is prefixed with "WARNING" where mine is not. A little more research on both our parts might be needed.
You can trap the error and force PowerShell to do nothing with it, kind of like a Try/Catch but global for the whole script:
TRAP {"" ;continue}
[System.Net.Dns]::GetHostbyAddress($IPAddress)

Appropriate logging in Powershell

If I have a powershell script say called caller.ps1 which looks like this
.\Lib\library.ps1
$output = SomeLibraryFunction
where library.ps1 looks like the following
function SomeLibraryFunction()
{
Write-Output "Some meaningful function logging text"
Write-Output "return value"
}
What I'd like to achieve is a way in which the library function can return it's value but also add some logging messages that allow the caller to process those internal messages as they see fit. The best I can think of is to write both to the pipeline and then the caller will have an array with the actual return value plus the internal messages which may be of use to a logger the calling script has.
Am I going about this problem the right way? Is there a better way to achieve this?
It's usually not a good idea to mix logging messages with actual output. Consumers of your function then have to do a lot of filtering to find the object they really want.
Your function should write logging messages to the verbose output stream. If the caller wants to see those messages, it can by specifying the -Verbose switch to your function.
function SomeLibraryFunction()
{
[CmdletBinding()]
param(
)
Write-Verbose "Some meaningful function logging text"
Write-Output "return value"
}
In PowerShell 3+, consumers can redirect your verbose messages to the output stream or a file:
# Show verbose logging messages on the console
SomeLibraryFunction -Verbose
# Send verbose logging messages to a file
SomeLibraryFunction -Verbose 4> verbose.txt
# redirect verbose message to the output stream
SomeLibraryFunction -Verbose 4>&1
Other options include:
Writing to a well-known file
Writing to the Event Log
Use Start-Transcript to create a log of the session.
Something like Tee-Object might be helpful to you
function SomeLibraryFunction()
{
$filepath = "C:\temp\log.txt"
write-output "Some meaningful function logging text" | Tee-Object -FilePath $filepath -Append
write-output "return value" | Tee-Object -FilePath $filepath -Append
}
For more information on Tee-Object look here
You could use an If statement based on a variable like $logging=$true but i could see that getting messy.
Another Approach
If you are looking for more of an optional solution then maybe you could use something like this Start-Transcript and Stop-Transcript which creates a record of all or part of a Windows PowerShell session in a text file.
function SomeLibraryFunction()
{
write-output "Some meaningful function logging text"
write-output "return value"
}
$logging = $True
If ($logging){
Start-Transcript -Path C:\temp\log.txt -Append
}
SomeLibraryFunction
If ($logging){
Stop-Transcript
}
This would just show that you could toggle the Start and Stop. You could even set the switch with a paramater passed to the script if you chose.
NOTE The output might be more that you are looking for but at least give it a try. Also, this will not work in the Powershell ISE as you will get an error Start-Transcript : This host does not support transcription.
Another way to do this would be to return a compound object that includes the results and the log information. This is then easy to pick apart.
function MyFunc
{
# initialize
$result = $null
$log = #()
# write to the log
$log += "This will be written to the log"
$log += "So will this"
# record the result
$result = $true
# return result and log combined into object
return New-Object -TypeName PSObject -Property #{ result = $result; log = $log -join "`r`n" }
}
# Call the function and get the results
$MyFuncResult = MyFunc
# Display the returned result value
Write-Host ( "MyFunc Result = {0}" -f $MyFuncResult.Result )
# Display the log
write-host ( "MyFunc Log = {0}" -f $MyFuncResult.Log )
Alternatively, if you want to avoid the object, pass in a log variable by reference. The function can write to the log variable and the changes will be visible in the calling scope. To do this, you need to add the [ref] prefix to the function definition AND the function call. When you write to the variable in the function you need to refer to the .value property.
function MyFunc2 ([ref]$log)
{
# initialize
$result = $null
# write to the log
$log.value += "`r`nThis will be written to the log"
$log.value += "`r`nSo will this"
# record the result
$result = $true
# return result and log combined into object
return $result
}
# Call the function and get the results
$log = "before MyFunc2"
$MyFuncResult = MyFunc2([ref]$log)
$log += "`nafter MyFunc2"
# Display the returned result value
write-host ( "MyFunc2 result = {0}" -f $MyFuncResult )
# Display the log
write-host ( "MyFunc2 Log = {0}" -f $Log )