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".
Related
this is PS 5.1
try {
Send-MailMessage -to $EmailTo -Body $Body -Subject "$TodayDate Report" -From 'r-admin#domain.com' -SmtpServer 'mail-relay' -port '25' -BodyAsHtml -ErrorAction Stop
}
Catch {
Write-Warning "Unable to send email"
& gam user $($EmailTo) sendemail html true to $($EmailTo) subject "$TodayDate Report"
}
finally {
#final cleanup
If (#($UsersResultsArray).count -gt 0) {
remove-Item $UsersResultFileName -Force -ErrorAction SilentlyContinue
}
IF ($ArchiveOverFlowCount -gt 0) {
remove-Item $ArchiveOverFilename -Force -ErrorAction SilentlyContinue
}
If ($ZeroCount -gt 0) {
remove-item $ZeroArrayFileName -Force -ErrorAction SilentlyContinue
}
remove-Item $NumbersTableFilename -Force -ErrorAction SilentlyContinue
}
I have tried many combinations but if the try gets an error the catch works but the finally does not.
I have tried no finally and nothing happens after the catch.
No errors.
I am not sure what is going on or why nothing happens after the catch?
If the Try does not get an error everything works fine.
I tested with a continue in the catch and whatever I do if the catch fires the finally does not.
The documentation says:
The finally keyword is followed by a statement list that runs every time the script is run, even if the try statement ran without error or an error was caught in a catch statement.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-7.2
Now lets test:
try {
get-item C:\nothing.there -ErrorAction:Stop
}
catch {
write-error $_
}
finally {
write-host "finally"
}
#Output:
Write-Error: Cannot find path 'C:\nothing.there' because it does not exist.
finally
So the catch caught the error and finally got executed.
try {
get-item C:\Windows -ErrorAction:Stop
}
catch {
write-error $_
}
finally {
write-host "finally"
}
#Output:
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 12/10/2022 22:29 Windows
finally
Once again finally got executed.
So back to your example, I think that the variables used in the IF statements, e.g. $UsersResultsArray, are empty or the variables that should contain paths like $UsersResultFileName.
To verify if the finally block runs, simply add write-host "finally" to that block and re-run the code, you will see "finally" printed on the screen.
Btw. to verify if the variable holds elements you do not need to count them, this is enough: IF ($UsersResultsArray){}.
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
I have weird problem, when im using try/catch method for some cmdlets its working for some not.
Can you advice on that?
This one is working fine:
try
{
$LookingForRemoteMailboxOnPrem = Get-RemoteMailbox $info -ErrorAction Stop | select -ExpandProperty UserPrincipalName
}
catch
{
string]$t = $Error[0]
}
But this one is not:
try
{
$EnableRemoteMailbox = Enable-RemoteMailbox $info -RemoteRoutingAddress $remote -PrimarySmtpAddress $info2 -ErrorAction Stop
}
catch
{
[string]$t = $Error[0]
}
Not saving error to $t variable
The $ErrorActionPreference is set to Continue by default. This means if PowerShell can "recover" from an error it won't throw an exception. You can use the -ErrorAction parameter to change the behaviour at every cmdlet.
This link gives a good example:
Try {dir c:\missingFolder}
Catch [System.Exception] {"Caught the exception"}
Finally {$error.Clear() ; "errors cleared"}
The string "Caught the exception does not occur in PowerShell windows. If you set the -ErrorAction to Stop an exception is raised.
Details are described here.
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 )
I noticed that if applying a configuration through Start-DscConfiguration fails, it writes to the error stream but doesn't
throw an Exception? That is, if I do the following:
try{
Start-DscConfiguration -Path ".\MyConfig" -Wait -Verbose
}catch{
#...
}
...it never ends up in the catch handler. I suspect this may have something to do with the fact that without the "-Wait",
Start-DscConfiguration starts an async job for this, and async commands probably don't throw exceptions, but in a synchronous
scenario, I would very much like to know if my configuration could be applied.
What is the proper way to determine if Start-DscConfiguration has completed succesfully?
The only way I know is to check the global "$error" variable and compare the number of error records before and after your call to Start-DscConfiguration. If there's more afterwards then something must have gone wrong during the call, so throw your own exception:
Configuration TestErrorHandling {
Node "localhost" {
Script ErroringResource {
GetScript = { return $null; }
TestScript = { return $false; }
SetScript = { throw new-object System.InvalidOperationException; }
}
}
}
$errorCount = $error.Count;
write-host "starting dsc configuration"
$mof = TestErrorHandling;
Start-DscConfiguration TestErrorHandling –Wait –Verbose;
write-host "dsc configuration finished"
if( $error.Count -gt $errorCount )
{
$dscErrors = $error[$errorCount..($error.Count - 1)];
write-host "the following errors occurred during dsc configuration";
write-host ($dscErrors | fl * | out-string);
throw $dscErrors[-1];
}
There's another way to make it cause an exception. Try saving it into the ErrorVariable like this :
try
{
Start-DscConfiguration -Path ".\MyConfig" -Wait -Verbose -ErrorVariable ev
}
catch
{
$myException = $_
}
Weirdly so, this throws the exception when there's an error (which is what you wanted). You can get the value of your exception in the $myexception variable, and also could get just a one liner description of your error using $ev
PS: Note that while mentioning ev in the errorVariable parameter, you do it without the '$' symbol - since you're only specifying the variable 'name'.
Start-DscConfiguration when used without -Wait will create a job object - with one child job for every computername. PowerShell job objects have an Error stream which contains all the errors. You can check this stream as well
$job = Start-DscConfiguration -Force -Verbose -Path C:\Temp\Demo\ -ComputerName localhost
Receive-Job $job -Wait
'Errors in job = ' + ($job.childjobs[0].Error.Count)