powershell exception has misleading InvocationInfo - powershell

given the script.
$foo = #("bar")
try {
$foo | ForEach-Object {
Join-Path $null $null
}
} catch {
$_.InvocationInfo.Line
}
will print
$foo | ForEach-Object {
but I would like
Join-Path $null $null
How can I get where the exception was actually raised?

This will get you the actual line:
$_.Exception.CommandInvocation.Line
and exception message:
$_.Exception.Message
and line number:
$_.Exception.Line
and offset (column):
$_.Exception.Offset
So you could make a helpful little message:
} catch {
$msg = "Failed to do something. Failed on line number '{0}' column '{1}' ('{2}'). The error was '{3}'." -f
$_.Exception.Line, $_.Exception.Offset, $_.Exception.CommandInvocation.Line.Trim(), $_.Exception.Message
Write-Error $msg
}

Related

Powershell - manage error when registry key not exist

I hope you're well.
Desciption
I requiere your help regarding the following code I use and who can generate an error when registry key do not exist.
In this example:
If the key Ins_ProductVersion exist in both registry path, the code show information expected.
However, if this key do not exist, I got an error exception + the text: - Product Version: Not found!
My goal , is to only get my message and not the error exception
I was thinking that -ErrorAction SilentlyContinue will manage this situation, but not in my case it not seems
foreach ($path in 'HKLM:HKEY_LOCAL_MACHINE\SOFTWARE\xxx\xxxx\xxxx\environment\', 'HKLM:HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\xxx\xxxx\xxxx\environment\') {
$hotfix = Get-ItemPropertyValue -Path $path -Name 'Ins_ProductVersion' -ErrorAction SilentlyContinue
# assuming you want to exit the loop at the first successfull 'hit'
if ($hotfix) { break }
}
if ($hotfix) {
write-host "- Product Version: $hotfix"
}
else {
write-host "- Product Version: Not found!"
}
Thanks for your helps and futur advices.
Regards,
Florian
-------------- new code version ---------------
Not working for now
Original key is : Version
Change this key like : Version1
Output can be found here :
code used
foreach ($path in 'HKLM:\SOFTWARE\Dropbox\Client', 'HKLM:\SOFTWARE\WOW6432Node\Dropbox\Client') {
$hotfix = Get-ItemPropertyValue -Path $path -Name 'Version' -ErrorAction SilentlyContinue
# assuming you want to exit the loop at the first successfull 'hit'
if ($hotfix) { break }
}
if ($hotfix) {
write-host "- Product version: $hotfix"
}
else {
Write-Host "- Product version : Not found"
}
========== SOLUTION ==========
Thanks for your help
foreach ($path in 'HKLM:\SOFTWARE\Dropbox\Client', 'HKLM:\SOFTWARE\WOW6432Node\Dropbox\Client') {
try {
$hotfix = Get-ItemPropertyValue -Path $path -Name 'Version' -ErrorAction Stop
# assuming you want to exit the loop at the first successfull 'hit'
if ($hotfix) { break }
}
catch {
Write-Warning $_.Exception.Message
}
}
if ($hotfix) {
write-host "- Product version: $hotfix"
}
else {
Write-Host "- Product version : Not found"
}
The reason you receive both an exception and your own message is because you have defined the registry paths wrong:
HKLM:HKEY_LOCAL_MACHINE\SOFTWARE\xxx\xxxx\xxxx\environment should be either
HKLM:\SOFTWARE\xxx\xxxx\xxxx\environment OR
Registry::HKEY_LOCAL_MACHINE\SOFTWARE\xxx\xxxx\xxxx\environment
In your code, you have now added the registry hive twice, where you only need the short OR long version here.
This works:
foreach ($path in 'HKLM:\SOFTWARE\xxx\xxxx\xxxx\environment\', 'HKLM:\SOFTWARE\WOW6432Node\xxx\xxxx\xxxx\environment\') {
$hotfix = Get-ItemPropertyValue -Path $path -Name 'Ins_ProductVersion' -ErrorAction SilentlyContinue
# assuming you want to exit the loop at the first successfull 'hit'
if ($hotfix) { break }
}
if ($hotfix) {
write-host "- Product Version: $hotfix"
}
else {
write-host "- Product Version: Not found!"
}
and so does this:
foreach ($path in 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\xxx\xxxx\xxxx\environment\', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\xxx\xxxx\xxxx\environment\') {
$hotfix = Get-ItemPropertyValue -Path $path -Name 'Ins_ProductVersion' -ErrorAction SilentlyContinue
# assuming you want to exit the loop at the first successfull 'hit'
if ($hotfix) { break }
}
if ($hotfix) {
write-host "- Product Version: $hotfix"
}
else {
write-host "- Product Version: Not found!"
}
without the need to wrap it inside a try{..} catch{..}
Although I do not have any exception messages, and for me the -ErrorAction SilentlyContinue does exactly what is expected, you say you keep also receiving system Exception messages using the above..
In that case, you will need to add a try{..}catch{..} to figure out what exactly errors out like this:
foreach ($path in 'HKLM:\SOFTWARE\Dropbox\Client', 'HKLM:\SOFTWARE\WOW6432Node\Dropbox\Client') {
try {
$hotfix = Get-ItemPropertyValue -Path $path -Name 'Version' -ErrorAction Stop
# assuming you want to exit the loop at the first successfull 'hit'
if ($hotfix) { break }
}
catch {
Write-Warning $_.Exception.Message
}
}
if ($hotfix) {
write-host "- Product version: $hotfix"
}
else {
Write-Host "- Product version : Not found"
}
Now, exceptions will be written to the screen too like
WARNING: Cannot find path 'HKLM:\SOFTWARE\Dropbox\Client' because it does not exist.
WARNING: Cannot find path 'HKLM:\SOFTWARE\WOW6432Node\Dropbox\Client' because it does not exist.
- Product version : Not found
or
WARNING: Property Version does not exist at path HKEY_LOCAL_MACHINE\SOFTWARE\Dropbox\Client.
WARNING: Cannot find path 'HKLM:\SOFTWARE\WOW6432Node\Dropbox\Client' because it does not exist.
- Product version : Not found
Usually best to use error handling properly rather than try to disable it:
$Paths = 'HKLM:HKEY_LOCAL_MACHINE\SOFTWARE\xxx\xxxx\xxxx\environment\', 'HKLM:HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\xxx\xxxx\xxxx\environment\'
$Paths | ForEach-Object {
Try {
$CurPath = $_
$hotfix = Get-ItemPropertyValue -Path $CurPath -Name 'Ins_ProductVersion' -ErrorAction Stop
}
Catch {
$RegError = "Error obtaining reg key: $_"
}
If ($hotfix) {
[pscustomobject]#{RegPath=$_;Hotfix=$hotfix}
}
Else {
[pscustomobject]#{RegPath=$_;Hotfix=$RegError}
}
}
Or:
$Paths | ForEach-Object {
Try {
$CurPath = $_
$hotfix = Get-ItemPropertyValue -Path $CurPath -Name 'Ins_ProductVersion' -ErrorAction Stop
[pscustomobject]#{RegPath=$_;Hotfix=$hotfix}
}
Catch {
$RegError = "Error obtaining reg key: $_"
[pscustomobject]#{RegPath=$_;Hotfix=$RegError}
}
}

Powershell: Throwing Exception in Catch block terminates but does not output Exception to console

I recently started having this weird issue where my Exception is caught in my catch block but when rethrowing it terminates the script (as expected) but does not write the re-thrown exception to console. I am not concerned about the specific exception that is being thrown, I only care that the exception is re-thrown and the stacktrace is output to console. I am using PowerShell 4.
try {
$variable1 = 'value1'
$variable2 = 'value2'
[string[]]$variable3 = 'value3'
[Collections.Generic.List[Microsoft.PowerShell.Commands.MatchInfo]]$matches = New-Object Collections.Generic.List[Microsoft.PowerShell.Commands.MatchInfo]
$matches = `
Get-Matches `
-Param1 $variable1 `
-Param2 $variable2 `
-Param3 $variable3 `
-Silent $true
Write-Host `n`nTotal Matches Found: $matches.Count
} catch {
throw $_
}
I've also attempted the following alterations to the catch block:
} catch {
throw
}
} catch {
throw $_.Exception
}
} catch {
throw $_.Exception.Message
}
} catch {
throw $error[0].Exception
}
} catch {
throw $error[0].Exception.Message
}
To output the exception to the screen in your catch block, use the following:
try {
#code
} catch {
#handle exception
Write-Output $_.Exception.Message
}
Additionally your throw should be within your try statement. No point throwing an exception within a catch block as one has already been thrown and caught to be there!
try {
#code
if($something -ne $right) {
throw "Something isn't right!"
}
} catch {
#handle exception
Write-Output $_.Exception.Message
}

Trapping Powershell Error in a function

I'm having a bit of trouble preventing a certain error message from bubbling up from a function to my main routine's 'Catch'. I would like to have my function react to a particular error, then do something, and continue processing as usual without alerting my main routine that there was an error. Currently, if the file this script is trying to read is in use (being written to), it will write a System.IO.IOException error to my log. But sometimes I expect this error to occur and it isn't an issue and I don't want to fill my log with these type of errors. I would expect from the code below that the checkFileLock function would catch the error, return 0 to the findErrorInFile function, and no error would be caught to my error log.
Function findErrorsInFile{
param(
[string]$dir,
[string]$file,
[String]$errorCode
)
If((Get-Item $($dir + "`\" + $file)) -is [System.IO.DirectoryInfo]){ #we dont want to look at directories, only files
}Else{
If($(checkFileLock -filePath $($dir + "`\" + $file))){
$reader = New-Object System.IO.StreamReader($($dir + "`\" + $file))
$content = $reader.ReadToEnd()
$results = $content | select-string -Pattern $errorCode #if there is no regex match (no matching error code found), then the string $results will be == $null
If($results){
Return 1 #we found the error in the file
}Else{
Return 0 #no error found in the file
}
}Else{
Return 0 #The file was being written to, we will skip it and assume no error. This is rare.
}
}
}
Function checkFileLock{
param(
[String]$filePath
)
try{
$openFile = New-Object System.IO.FileInfo $filePath
$testStream = $openFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) #try to open a filestream
If($testStream){ #If the filestream opens, then it isn't locked
$testStream.Close() #close the filestream
}
return $false #File is not locked
}
catch{
return $true #File is locked
}
}
#### START MAIN PROCESS ####
Try{
if($(findErrorsInFile -dir 'somepath' -file 'somefilename' -errorcode 'abc')){
write-host "found something"
}else{
write-host "didn't find anything"
}
}
Catch{
$_.Exception.ToString() >> mylogfile.txt
}
try/catch blocks only catch terminating errors. Is your code generating a terminating or non-terminating error?
ArcSet has highlighted essentially what is required: force a non-terminating error to be a terminating error. I suspect it needs to be added to this line, if allowed:
$testStream = $openFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) -ErrorAction Stop
Edit - solution as -ErrorAction not an accepted parameter
I tried the above and it's not allowed. An alternative is to set the $ErrorActionPreference to Stop. This will affect all errors, so recommend reverting. Someone with more experience using System.IO.FileInfo objects may have a more elegant solution.
try{
$currentErrorSetting = $ErrorActionPreference
$ErrorActionPreference = "Stop"
$openFile = New-Object System.IO.FileInfo $filePath
$testStream = $openFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) #try to open a filestream
If($testStream){ #If the filestream opens, then it isn't locked
$testStream.Close() #close the filestream
}
$ErrorActionPreference = $currentErrorSetting
return $false #File is not locked
}
catch{
$ErrorActionPreference = $currentErrorSetting
return $true #File is locked
}
Use to force a catch
-ErrorAction Stop
use to suppress a error
[Command with error] | out-null

Catch "Cannot Find File"

I have been searching for a while, but I cannot find the exception in PowerShell that would catch a "Cannot find file" error.
I would also like to have this loop until the user types in the correct file name to get.
# Ask user for file to read from
Try {
$readFile = Read-Host "Name of file to read from: "
$ips = GC $env:USERPROFILE\Desktop\$readFile.txt
}
Catch {
}
The error you get is a non-terminating error, and thus not caught. Add -ErrorAction Stop to your Get-Content statement or set $ErrorActionPreference = 'Stop' and your code will work as you expect:
try {
$readFile = Read-Host "Name of file to read from: "
$ips = GC $env:USERPROFILE\Desktop\$readFile.txt -ErrorAction Stop
} catch {
}
Don't use try/catch blocks for flow control. That is a generally-frowned-on practice, especially in PowerShell, since PowerShell's cmdlets will write errors instead of throwing exceptions. Usually, only non-PowerShell .NET objects will throw exceptions.
Instead, test if the file exists. That gives you much greater error control:
do
{
$readFile = Read-Host "Name of file to read from: "
$path = '{0}\Desktop\{1}.txt' -f $env:USERPROFILE,$readFile
if( (Test-Path -Path $path -PathType Leaf) )
{
break
}
Write-Error -Message ('File ''{0}'' not found.' -f $path)
}
while( $true )

PowerShell Try, Catch, custom terminating error message

Is there a way to customize the error message of a terminating error?
In the example below I would like to just end up with one Try section and collect all errors in one Catch section by combining both script blocks. My problem is that the $error generated by Import-csv is not descriptive enough and I would like to have the text Failed CSV-File import c:\test.csv within the $error message.
Thank you for your advise.
Script block 1
Try {
$File = (Import-Csv -Path c:\test.csv)
}
Catch {
throw $error[0].Exception.Message | Send-Mail $ScriptAdmin "FAILED CSV-File import"
}
Script block 2
try {
if(!(Test-Path $LogFolder -PathType Container)) {throw "Can't find the log folder: '$LogFolder'"}
$Credentials = Import-Credentials $UserName $PasswordFile
}
catch {
throw $Error[0].Exception.Message | Send-Mail $ScriptAdmin "FAILURE"
}
Workaround
A possible solution would be to check first with test-path if the import file exists and then create a throw with a customized message. But I would like to know if it's possible to handle it in one line of code without using test-path first.
Best solution (thanks to mjolinor):
try {
$File = (Import-Csv -Path $ImportFile -Header "A", "B", "C", "D" | Where { $_.A -NotLike "#*" })
if(!(Test-Path $LogFolder -PathType Container)) {throw "Can't find the log folder: '$LogFolder'"}
$Credentials = Import-Credentials $UserName $PasswordFile
}
catch {
Switch -Wildcard ($Error[0].Exception)
{
"*$ImportFile*"
{ $FailType = "FAILED CSV-File import" }
"*$LogFolder*"
{ $FailType = "FAILED Log folder not found" }
"*Import-Credentials*"
{ $FailType = "FAILED Credential import" }
Default
{ $FailType = "FAILED Unrecognized error" }
}
Write-Warning $Error[0].Exception.Message
throw $Error[0].Exception.Message | Send-Mail $ScriptAdmin $FailType
}
Make sure that your advanced-function which you created (like Import-Credentials above) contains the function name in the throw section. So we can filter it out in the catchblock.
Something like this, maybe?
Try {
$File = (Import-Csv -Path c:\test.csv)
if(!(Test-Path $LogFolder -PathType Container))
{ throw "Can't find the log folder: '$LogFolder'" }
$Credentials = Import-Credentials $UserName $PasswordFile
}
catch {
Switch -Wildcard ($Error[0].CategoryInfo)
{
'*[Import-CSV]*'
{ $FailType = 'Import CSV failed' }
'*[Test-Path]*'
{ $FailType = 'LogFolder not found' }
'*[Import-Credentials]*'
{ $FailType = 'Credential import failed' }
Default
{ $FailType = 'Unrecognized error' }
}
$Error[0].Exception.Message | Send-Mail $ScriptAdmin $FailType
}
Edit (for some reason I'm not able to post a comment):
I should have specified that it wasn't tested, and was intended more as a pattern than a finished solution, and it seems to have done what it was intended to do.
#BartekB - I habitually use $Error[0] instead of $_ because it seems more explicit and intuitive to read, and less likely to be misunderstood by someone less experienced who might inherit the code later.