Catching error in Powershell and rewriting output - powershell

I'm trying to craft a script to resolve a long list of domain names to IP addresses. Some of these aren't defined and I need to catch the error and just return a "blank value." In the script below, I tried doing this using a basic If/Then, but I still get a wordy error (at the bottom) rather than just a blank value. Any ideas how to get around this? I really appreciate it!
----- SCRIPT -----
$names = Get-Content C:\temp\names.txt
ForEach ($name in $names) {
$ipAddress = [System.Net.Dns]::GetHostAddresses("$name")[0].IPAddressToString;
if ($ipAddress) {
Write-Host $name"-"$ipAddress
}
else {
Write-Host $name"-"
}
}
---- OUTPUT/ERROR ----
mydomain.com-1.2.3.4
yourdomain.com-4.3.2.1
Exception calling "GetHostAddresses" with "1" argument(s): "The requested name is valid, but no data of the requested type was found"
anotherdomain.com-5.5.5.5
---- What I'd Like to See -----
mydomain.com-1.2.3.4
yourdomain.com-4.3.2.1
NOTDEFINEDDOMAIN.tld-
anotherdomain.com-5.5.5.5
---- HERE'S THE SOLUTION THAT WORKED - THANK YOU!----
$names = Get-Content C:\temp\names.txt
ForEach ($name in $names) {
Try {
$ipAddress = [System.Net.Dns]::GetHostAddresses("$name")[0].IPAddressToString;
Write-Host $name"-"$ipAddress
}
Catch {
Write-Host $name"-"
}
}

Update of answer:
Catching error in Powershell and rewriting output
I need to catch the error and just return a "blank value
Use try/catch:
$names = Get-Content C:\temp\names.txt
ForEach ($name in $names)
{
try
{
$ipAddress = [System.Net.Dns]::GetHostAddresses("$name")[0].IPAddressToString;
Write-Host $name"-"$ipAddress
}
catch
{
Write-Host $name"-"
$_.Exception.Message # <- Check this to read and rewrite exception message
}
}
---- What I'd Like to See -----
If you want - you can manipulate of exception message like as string - this is line to get message in catch block:
$_.Exception.Message
Other way to get info about errors is $Error variable (it's the array/list of errors)...
More information:
http://vwiki.co.uk/Exceptions_and_Error_Handling_(PowerShell)
PowerShell Tutorial – Try Catch Finally and error handling in PowerShell
Using PowerShell $Error variable
about_Try_Catch_Finally
Update 2:
I forgot about one thing - try/catch working only with terminating errors.
I'm not sure about type of error in your case (because can't reproduce it), however sometimes you may want to add to your command:
-Error Stop

Related

Unable to catch the error using try..catch in powershell

I am trying to catch the error if DL is not found. I have written the below code
try
{
Get-DistributionGroup -Identity "#AB-TestDL"|select ManagedBy -ErrorAction Stop
}
catch
{
if($Error[0] -match "couldn't be found on")
{
Write-Host "DL not found"
}
}
But when i run the code, it throws an error as "#AB-TestDL" couldn't be found on ...
Please help me capture this error. Thanks..
Try using the -ErrorAction Stop parameter on the Get-DistributionGroup -Identity "#AB-TestDL" CmdLet instead of Select-Object.
Select-Object can be used to create a new object but it isn't a error for the CmdLet when a property does not exist.
C:\> [PSCustomObject]#{ Test = 123 } | Select Test2 -ErrorAction Stop
Test2
-----
You can however make it work different (while i still suggest moving -ErrorAction Stop to the first CmdLet):
Set-StrictMode -Version 3
$Object = [PSCustomObject] #{
Test = 123
}
try {
$null = $Object.Test2
} catch {
throw "I don't extist and catch because of strictmode version 3"
}
Just an idea which came into my head. I actually never used strictmode this way.

Catching Cascading Errors in PowerShell

I'm working with a wrapper script that calls a function which queries some databases inside specified servers and inserts metadata to a specific database on a separate server. I use the $error.count variable to determine if the script was successful or not. I anticipate some permission/extraction errors to happen and want these to be caught and ignored (no increase in the $error.count variable but having a warning written in the job log). I can confirm one permission error is happening and is being properly caught. The $error.count variable is not increased but a warning is printed from the catch showing the database that could not be accessed.
My problem occurs after the extraction/insertion function is finished running. Immediately after this function returns to the wrapper script, I have the $error.count variable print again. This time, it returns a 1 as if the error previously caught cascades into the wrapper script. As I mentioned previously, I do not want this to be included in the error count. I'm not sure how or why the $error.count is increased from this function.
Should I use a different variable to determine if the script "failed" or not? Is there some underlying reason why the $error.count would increase outside of the function that has the error while not increasing after the error is caught? Any guidance on this issue would be appreciated.
Code for reference:
Wrapper function:
$errorCount = $error.count
Write-Warning ("$errorCount Before function")
Extraction/Insertion_Function -serverList $serverList -insertionDB $insertionDB -ErrorAction SilentlyContinue
$errorCount = $error.count
Write-Warning ("$errorCount After function")
} catch {
Write-Error "Error caught by wrapper: $_"
}
Extraction/Insertion_Function:
ForEach ($db in $dbList) {
Write-Warning "$errorCount database
.
.
.
try {
$totalProperties = Get-ServerDBMetadata -DBConnectionString ($connStr) -DatabaseName $dbName -EA SilentlyContinue
} catch {
Write-Warning "Unable to extract metadata from $dbname in $server"
}
}
I then have the error count printing out inside the loop that extracts/inserts the metadata from each database to the insertion database, as well as in the loop for each server that contains the databases:
WARNING: 0 Before function
WARNING: 0 database
.
.
.
WARNING: 0 database
WARNING: Unable to extract metadata from *database* in *server*
WARNING: 0 database
.
.
.
WARNING: 0 database
**WARNING: 1 After function**
The error (permission issue) is caught inside the function but cascades to my wrapper script. I want this particular error to be ignored while NOT ignoring other, more serious errors (like being unable to connect to the server I'm inserting the metadata into) so placing -EA Ignore on the driver function inside the wrapper script is out of the question.
Replace SilentlyContinue with Ignore to ignore the error and not have it increase the count.
Extraction/Insertion_Function -serverList $serverList -insertionDB $insertionDB -ErrorAction Ignore
To catch it inside the function, use -ErrorAction Stop as in thepip3r's answer as try/catch statements only catch terminating errors.
Your primary problem with the try-catch not catching the error (even though you don't supply all of the code) is that your cmdlet explicitly calls -ErrorAction SilentlyContinue. Try/Catch blocks REQUIRE the use of terminating errors so in the case of your function/cmdlet, you need to change to -ErrorAction Stop for try/catch to appropriately handle an error from that function/cmdlet.
This needs to be updated for any other function/cmdlet in the code we can't see.
Edit described in comments below:
$n = New-Object PSObject -property #{
'Test1' = ''
'Test2' = ''
'Test3' = ''
}
try {
get-process someprocess -ErrorAction Stop
$n.Test1 = $true
} catch {
$n.Test1 = $false
}
try {
Get-WmiObject win32_computersystem -ErrorAction Stop
$n.Test2 = $true
} catch {
$n.Test2 = $false
}
try {
Get-Content somefile.ext -ErrorAction Stop
$n.Test3 = $true
} catch {
$n.Test3 = $false
}
if ($n.Test1 -and $n.Test2 -and $n.Test3) {
## All procedures completed successfully -- do something magical
} else {
## At least one test procedure failed.
}
Logging the remaining errors since the last log:
While ($Global:ErrorCount -lt $Error.Count) {
$Err = $Error[$Error.Count - ++$Global:ErrorCount]
$ErrLine = "Error at $($Err.InvocationInfo.ScriptLineNumber),$($Err.InvocationInfo.OffsetInLine): $Err"
Write-Host $ErrLine -ForegroundColor Red # Log this
}

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 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)

Powershell script error processing CSV data

I'm writing a script to load computer names from a CSV file, then look up their IP addresses.
The script creates an error when using the name from the CSV.
If I run the script in ISE, the error shows up, but the result still comes though. If I run the script from powershell, it errors and the result is null.
If I substitute the $PCname = $_.System for $PCname = "Computer01" everything works fine.
If I write-host $_.System it displays "Computer01". How can I get this to work in powershell?
$file = "\\server.contoso.net\private$\Systems.csv";
$CSV = Import-CSV $file;
$CSV | %{
if ($_.Skip -eq 0)
{
$PCname = $_.System
# $PCname = "Computer01"
write-host $PCname
try
{
$ipaddress = [System.Net.Dns]::GetHostByName($PCname).AddressList[0].IpAddressToString
}
Catch [system.exception]
{
if(1)
{ $error[0].tostring() }
}
}
}
Error displayed is:
Exception calling "GetHostByName" with "1" argument(s): "The requested name is valid, but no data of the requested type was found"
Turns out that the values in the CSV at some point had whitespace added after them, which caused a name look up error. I'm not sure why ISE would still be able to look up the host, but removing the whitespace fixed the issue.
Thanks to sha, his recommendation helped me see the whitespace.