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 )
Related
I am using powershell to process csv files in a directory when no file found with current date stamp I want the process to raise an error notifying file not found and exit.
# Powershell raise error and exit
# File name: sale_2020_02_03.csv
$getLatestCSVFile = Get-ChildItem -Path $Folder -Filter "*.csv" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($getLatestCSVFile)
{
try
{
# Process .csv file
}
catch
{
# on error
$ErrorMessage = $_.Exception.Message
throw "$ErrorMessage"
}
}
else
{
# If current date file not found raise error and exit
Send-MailMessage
throw "File not found"
}
As for this...
I would like to have the powershell script to stop execution
... that is what the Exit keyword is for or the '-ErrorAction Stop' option is for.
As for this...
if the code gets into the else block in the else block I want to
notify file not found
... as per my above, same thing, and you have to set that.
Meaning stuff like ...
Clear-Host
$Error.Clear()
try
{
# Statement to try
New-Item -Path 'D:\Temp\DoesNOtExist' -Name 'Test.txt' -ItemType File -ErrorAction Stop
}
catch
{
# What to do with terminating errors
Write-Warning -Message $Error[0]
}
# Results
<#
WARNING: Could not find a part of the path 'D:\Temp\DoesNOtExist\Test.txt'.
#>
Or using multiple catch statements, like ...
Example: Force file warning
Clear-Host
$Error.Clear()
try
{
# Results in NoSupportException
# Statement to try
New-Item -Path 'D:\Temp\Temp' -Name my:Test.txt -ItemType File -ErrorAction Stop
# Results in DirectoryNotFoundException
# New-Item -Path 'D:\Temp\Temp' -Name 'Test.txt' -ItemType File -ErrorAction Stop
}
catch [System.NotSupportedException]
{
# What to do with terminating errors
Write-Warning -Message 'Illegal chracter or filename.'
}
catch [System.IO.DirectoryNotFoundException]
{
# What to do with terminating errors
Write-Warning -Message 'The path is not valid.'
}
catch
{
# What to do with terminating errors
Write-Warning -Message 'An unexpected error occurred.'
}
# Results
<#
WARNING: Illegal character or filename.
#>
Example: Force path warning
Clear-Host
$Error.Clear()
try
{
# Results in NoSupportException
# Statement to try
# New-Item -Path 'D:\Temp\Temp' -Name my:Test.txt -ItemType File -ErrorAction Stop
# Results in DirectoryNotFoundException
New-Item -Path 'D:\Temp\Temp' -Name 'Test.txt' -ItemType File -ErrorAction Stop
}
catch [System.NotSupportedException]
{
# What to do with terminating errors
Write-Warning -Message 'Illegal chracter or filename.'
}
catch [System.IO.DirectoryNotFoundException]
{
# What to do with terminating errors
Write-Warning -Message 'The path is not valid.'
}
catch
{
# What to do with terminating errors
Write-Warning -Message 'An unexpected error occurred.'
}
# Results
<#
WARNING: The path is not valid.
#>
See also this discussion, which is a similar use case. Pay particular attention the the 'Exit vs Return vs Break' answer.
Terminating a script in PowerShell
Consider this simple code:
Read-Host $path
try {
Get-ChildItem $Path -ErrorAction Continue
}
Catch {
Write-Error "Path does not exist: $path" -ErrorAction Stop
Throw
}
Write-Output "Testing"
Why is 'Testing' printed to the shell if an invalid path is specified?
The script does not stop in the catch block. What am I doing wrong?
In your Try Catch block, you need to set Get-ChildItem -ErrorAction Stop
so the exception is caught in the Catch block.
With continue, you are instructing the command to not produce a terminating error when an actual error occurs.
Edit:
Also, your throw statement is useless there and you do not need to specify an error-action for Write-Error.
Here's the modified code.
$path = Read-Host
try {
Get-ChildItem $Path -ErrorAction stop
}
Catch {
Write-Error "Path does not exist: $path"
}
Additional note
You could apply this default behavior (if that is what you want) to the entire script by setting the default action to stop using :
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
I think this is what you need:
$path = Read-Host 'Enter a path'
try {
Get-ChildItem $Path -ErrorAction Stop
}
Catch {
Throw "Path does not exist: $path"
}
Write-Output "Testing"
Per Sage's answer, you need to change to -ErrorAction Stop in the Try block. This forces the Get-ChildItem cmdlet to throw a terminating error, which then triggers the Catch block. By default (and with the Continue ErrorAction option) it would have thrown a non-terminating error which are not caught by a try..catch.
If you then want your code to stop in the Catch block, use Throw with the message you want to return. This will produce a terminating error and stop the script (Write-Error -ErrorAction Stop will also achieve a terminating error, it's just a more complicated method. Typically you should use Write-Error when you want to return non-terminating error messages).
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
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.
Is there a way to catch and save bad names in a foreach loop? I have the following:
$CollectionName = "Import Test"
$PCName = Import-Csv "C:\Powershell\import_test.csv"
foreach($computer in $PCName) {
Add-CMDeviceCollectionDirectMembershipRule -CollectionName $CollectionName -ResourceID $(Get- CMDevice -Name $computer.computername).ResourceID
}
What I would like to do is that if there is a bad name in the csv then instead of displaying the "Cannot validate argument" error I currently get, just output the failed name to a text file.
Thanks
Yes. Put the statement(s) inside the loop in a try..catch block:
foreach($computer in $PCName) {
try {
Add-CMDeviceCollectionDirectMembershipRule ...
} catch {
"Bad name: $name" | Out-File 'C:\bad_names.txt' -Append
}
}
If the error is a non-terminating error (i.e. displays an error message, but isn't caught by try..catch), you can turn it into a terminating error by adding -ErrorAction Stop to the command or by setting $ErrorActionPreference = "Stop".