Try, catch : doing something if the TRY completed successfully - powershell

I am looping through a list of samaccountnames and performing several actions:
# Disabling user
try {
Disable-QADUser $user | Out-Null
} catch [exception] {
"Disable-QADuser: " + $($_.Exception.Message) | out-file $logfile -append
write-host " - Error disabling useraccount." -fore yellow
}
# Set informative description
try {
Set-QADuser $user -Description "Disabled $now" | Out-Null
} catch [exception] {
"Set-QADuser: " + $($_.Exception.Message)| out-file $logfile -append
write-host " - Error setting informative description in AD." -fore yellow
}
But how do I output something if the command completed successfully? Something like
write-host "User $user disabled"
"User $user disabled" | out-file $logfile -append
All help/pointers are greatly appreciated!
EDIT
I noticed that I can use tee-object to send the output to file as well as console.. This way I do not have to have to lines to "tee" the output:)

If it's anything like java, you'd just place it underneath the line you are trying to execute:
try {
Set-QADuser $user -Description "Disabled $now" | Out-Null
write-host "User $user disabled"
"User $user disabled" | out-file $logfile -append
} catch [exception] {
"Set-QADuser: " + $($_.Exception.Message)| out-file $logfile -append
write-host " - Error setting informative description in AD." -fore yellow
}

One important thing to keep in mind: if for some reason disabling the user didn't work, your catch block WILL NOT invoke since the error is not a terminating error. To change the type of the error to terminating error, use the ErrorAction parameter:
Set-QADuser $user -Description "Disabled $now" -ErrorAction Stop | ...

Related

Deleting admin local group member script doesnt work

I created the below script to delete the Specified Members from Administrators Group but it crashes when it can't find the member. Specificaly it shows my code and in the end adds
: An unspecifed error occured
At line:21 char:5
"An unspecifed error occured" | Write-Error
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException"
Clear-Host
$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'
#User to search for
$MEMBERS = #("Test1","Test5","Test2","Test76","Test3")
foreach ($MEMBER in $MEMBERS) {
#Declare LocalUser Object
$ObjLocalGroupMember = $null
try {
Write-Verbose "Searching for $($MEMBER) in LocalGroup DataBase"
$ObjLocalGroupMember = Get-LocalGroupMember -Group "Administrators" -Member $MEMBER
Write-Verbose "User $($MEMBER) was found"
}
catch [Microsoft.PowerShell.Commands.MemberNotFoundException] {
"User $($MEMBER) was not found" | Write-Error
}
catch {
"An unspecifed error occured" | Write-Error
Exit
}
if ($ObjLocalGroupMember) {
Write-Verbose "Deleting User $($MEMBER)"
$password = ConvertTo-SecureString -String "password" -AsPlainText -Force
$op = Get-LocalGroupMember -Group "Administrators"| Where-Object {$_.Name -eq $MEMBER}
if ($op)
{
Remove-LocalGroupMember ($op) | Out-Null
}
}
}
I am still very new in this so the solution could be very simple but your help will be very much apreciated.
The main issue is your use of Write-Error in combination with $ErrorActionPreference = 'Stop', since the preference is Stop, the non-terminating error generated by Write-Error will be treated as a terminating error, hence the script stops at first occurrence of an error.
The other main issue is your use of exit instead of continue in your catch block, but also to explain why it's reaching that catch statement, you're trying to catch the error type Microsoft.PowerShell.Commands.MemberNotFoundException however, when Get-LocalGroupMember can't find a member, the exception type is Microsoft.PowerShell.Commands.PrincipalNotFoundException:
try {
Get-LocalGroupMember -Group Administrators -Member doesnotexist
}
catch {
$_.Exception.GetType().FullName
}
# Results in: `Microsoft.PowerShell.Commands.PrincipalNotFoundException`
As for your code, you could simplify it a bit, there are some not needed redundancies:
Clear-Host
$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'
#User to search for
$members = "Test1", "Test5", "Test2", "Test76", "Test3"
foreach($member in $members) {
try {
Write-Verbose "Searching for $member in LocalGroup DataBase"
$ObjLocalGroupMember = Get-LocalGroupMember -Group Administrators -Member $member
Write-Verbose "User $member was found"
Write-Verbose "Deleting User $member"
Remove-LocalGroupMember $ObjLocalGroupMember
}
catch [Microsoft.PowerShell.Commands.PrincipalNotFoundException] {
Write-Warning "User $member was not found"
continue # Go to next user
}
catch {
# Adding `$_` so at least you know what happened
Write-Warning "An unspecifed error occured: $_"
continue
}
# Unclear what is this doing here, never being used and
# should also be outside the loop in any case:
# $password = ConvertTo-SecureString -String "password" -AsPlainText -Force
}

powershell catch export to csv not working

I'm writing a script that is fed a .csv and tries to make an operation and then catches into a seperate .csv but for some reason I cant seem to feed the catch info into the csv. I get the error, "Export-csv : Cannot process argument because the value of argument "name" is not valid. Change the value of the "name" argument and run the operation again."
I appreciate any input from the brains.
#imports a csv and does somthing with the data. The columns in the csv are specified by the $($_.'columnName')
Import-Csv -Path 'PathToMyCSV.csv' | ForEach-Object{
#Print associated User
Write-Host "$($_.ModifiedEmail)" -ForegroundColor Yellow -NoNewline;
Write-Host " is the user's email prefix, " -NoNewline
#Print PC name to be moved
Write-Host "SD-LT-$($_.PCName)" -ForegroundColor Yellow -NoNewline;
Write-Host " is the PC they use, and " -NoNewline
#Print The OU the computer is moving to
Write-Host "$($_.OU)" -ForegroundColor Yellow -NoNewline;
Write-Host "is the OU the computer needs to go in!"
$pcname = "SD-LT-$($_.PCName)"
Try {
Get-ADComputer SD-LT-$($_.PCName) | Move-ADObject -TargetPath $($_.OU)
}
Catch {
Write-Host $_ -ForegroundColor Magenta
$pcname | Export-csv -Path 'PathToAnotherCSV.csv' -Append -Force
}
}
Try creating a PSCustomObject.
[PSCustomObject]#{'pcname'=$pcname} | Export-csv -Path 'PathToAnotherCSV.csv' -Append -Force

Add a Check if ExternalEmailAddress already exists in the CSV and if it does do nothing

How would I do this? Any ideas? I just want to make sure my script does not try to create a new mailContact if the ExternalEmailAddress already exists.
If (Test-Path $CSVFileName) {
#Import the CSV file
$csvfile = Import-CSV $CSVFileName
#Loop through CSV file
foreach ($line in $csvfile) {
try {
#Create the mail contact
New-MailContact -domaincontroller $dc -Name $line.Name -ExternalEmailAddress $line.ExternalEmailAddress -OrganizationalUnit $OU -ErrorAction STOP
Set-Contact -domaincontroller $dc -Identity $line.Name -Department $line.Department -Title "Sales Team" -StreetAddress $line.StreetAddress -City $line.City -StateOrProvince $line.StateOrProvince -PostalCode $line.PostalCode -Office $line.Office -CountryOrRegion $line.CountryOrRegion
"$($line.Name) was created successfully." | Out-File $logfile -Append
}
catch {
$message = "A problem occured trying to create the $($line.Name) contact"
$message | Out-File $logfile -Append
Write-Warning $message
Write-Warning $_.Exception.Message
$_.Exception.Message | Out-File $logfile -Append
}
}
}
I'm not seeing any fundamental issue with your approach. If you are worried about testing it, create a truncated version of the CSV file, to see how the script reacts with some tests accounts or whatnot. You could also make use of the -WhatIf parameter on the New & Set commands to check which objects will be affected, but without making the change.
I have to assume the $OU variable is defined elsewhere. The only other thing I'd suggest is to use splatting to make it more readable. That might look something like this:
$NewParams = #{
DomainController = $dc
OrganizationalUnit = $OU
ErrorAction = 'Stop'
}
$SetParams = #{
DomainController = $dc
Title = 'Sales Team'
}
If (Test-Path $CSVFileName) {
#Import the CSV file
$csvfile = Import-CSV $CSVFileName
# Adjust splatting hash tables per incoming line:
$NewParams['Name'] = $line.Name
$NewParams['ExternalEmailAddress'] = $line.ExternalEmailAddress
# Adjust splatting hash for the set command:
$SetParams['Identity'] = $line.Name
$SetParams['Department'] = $line.Department
$SetParams['StreetAddress'] = $line.StreetAddress
$SetParams['City'] = $line.City
$SetParams['StateOrProvince'] = $line.StateOrProvince
$SetParams['PostalCode'] = $line.PostalCode
$SetParams['Office'] = $line.Office
$SetParams['CountryOrRegion'] = $line.CountryOrRegion
#Loop through CSV file
foreach ($line in $csvfile) {
try {
#Create the mail contact
New-MailContact #NewParams
Set-Contact #SetParams
"$($line.Name) was created successfully." | Out-File $logfile -Append
}
catch {
$message = "A problem occured trying to create the $($line.Name) contact"
$message | Out-File $logfile -Append
Write-Warning $message
Write-Warning $_.Exception.Message
$_.Exception.Message | Out-File $logfile -Append
}
}
}
This example sets up 2 hash tables with constant values. Then inside the loop, it will add and/or change the variable parameters in the hashes, per line of input. They simply reference those hashes as the argument to the New-MailContact & Set-Contact commands.
The Exchange Management Shell will not let you duplicate an SMTP address. If you attempt to it will return an error like:
The proxy address "SMTP:Example#Example.com" is already being used by the proxy addresses or LegacyExchangeDN of "local.domian/Users/OtheMailEnabledObject". Please choose another proxy address.
However, if you want to prevent errors altogether and/or prevent writing these specific kinds of errors to the log there are a few things you can do.
From a code perspective, the easiest thing to do is to simply add an If block with a Get-Recipient command.
#...
#Loop through CSV file
foreach ($line in $csvfile) {
If( !(Get-Recipient $line.ExternalEmailAddress) ) {
try {
#Create the mail contact
New-MailContact #NewParams
Set-Contact #SetParams
"$($line.Name) was created successfully." | Out-File $logfile -Append
}
catch {
$message = "A problem occured trying to create the $($line.Name) contact"
$message | Out-File $logfile -Append
Write-Warning $message
Write-Warning $_.Exception.Message
$_.Exception.Message | Out-File $logfile -Append
}
}
}
Note: Get-Recipient is more thorough than Get-MailContact.
Another approach might be to simply handle those specific errors differently.
#...
#Loop through CSV file
foreach ($line in $csvfile) {
try {
#Create the mail contact
New-MailContact #NewParams
Set-Contact #SetParams
"$($line.Name) was created successfully." | Out-File $logfile -Append
}
catch {
If( $Error[0].CategoryInfo.Reason -ne 'ProxyAddressExistsException' ){
$message = "A problem occured trying to create the $($line.Name) contact"
$message | Out-File $logfile -Append
Write-Warning $message
Write-Warning $_.Exception.Message
$_.Exception.Message | Out-File $logfile -Append
}
}
}
So in this case you are only logging when the error isn't a duplicate proxy address. You can of course create additional logic depending on what you want.
If you have a very large number of contacts to create both of these methods might be rather slow. This is because they'd be executing a lot of commands they don't need to. For the latter approach, the performance hit would be related to how many duplicate errors actually occur. For the former, you'd take a hit on each loop iteration because you're always checking regardless of whether the New-MailContact command will be successful anyway.
So the last approach is to create a list of existing proxy addresses you can check against before trying to create a new contact.
To compile a list of all SMTP proxy addresses:
$ExistingAddresses =
Get-Recipient ResultSize Unlimited |
Select-Object -ExpandProperty EmailAddresses |
Where-Object{$_.PrefixString -eq 'SMTP'} |
Select-Object -ExpandProperty SmtpAddress
However, Get-Recipient may have problems paging through large sets returned by a large query. Depends on your environment. If you do have a problem you can use an alternate approach using AD cmdlets, which will be much faster in any case.
$ExistingAddresses =
Get-ADObject -Filter "mailnickname -like '*'" -Properties 'ProxyAddresses' |
ForEach-Object{
ForEach($ProxyAddress in $_.ProxyAddresses)
{
If( $ProxyAddress -match "^smtp" ) {
$ProxyAddress.Split(':')[1]
}
}
}
Once you have $ExistingAddresses populated you can change the loop to check the list first:
foreach ($line in $csvfile) {
If( $line.ExternalEmailAddress -notin $ExistingAddresses ) {
try {
#Create the mail contact
New-MailContact #NewParams
Set-Contact #SetParams
"$($line.Name) was created successfully." | Out-File $logfile -Append
}
catch {
$message = "A problem occured trying to create the $($line.Name) contact"
$message | Out-File $logfile -Append
Write-Warning $message
Write-Warning $_.Exception.Message
$_.Exception.Message | Out-File $logfile -Append
}
}
}
Again, you can build additional logic if desired. For example, an Else{...} if you want to report finding a duplicate etc...
Based on additional comments. If you want to run a set command on those contacts that already existed. here's 1 example:
#Loop through CSV file
foreach ($line in $csvfile) {
If( !(Get-Recipient $line.ExternalEmailAddress) ) {
try {
#Create the mail contact
New-MailContact #NewParams
"$($line.Name) was created successfully." | Out-File $logfile -Append
}
catch {
$message = "A problem occured trying to create the $($line.Name) contact"
$message | Out-File $logfile -Append
Write-Warning $message
Write-Warning $_.Exception.Message
$_.Exception.Message | Out-File $logfile -Append
}
}
# The contact either already exists or was just created, so long as you
# Use the same domain controller go ahead and perform the Set...
Set-Contact #SetParams
}
Again, there are a thousand ways to arrange something like this. And, all of the above approaches can probably be modified to satisfy the new requirement. However, this will not tell you which properties get updated. If you want that kind of property level comparison, we'd have to write still more code.

System.Security.Principal.IdentityNotMappedException Catching all errors

I am having issues with a try/catch block where the catch block for System.Security.Principal.IdentityNotMappedException is catching all errors regardless if they're of type System.Security.Principal.IdentityNotMappedException or not. The problem is when I move the try/catch block outside of the script into it's own for testing, the try/catch works as intended.
The try/catch is inside a for/each so they only thing I can think of is that the loop is somehow tripping up the try/catch block? The one error it loves catching is System.Management.Automation.RuntimeException.
Below is the test script I made that works as intended, but placed within the foreach doesn't:
I actually got the error to manifest with this:
foreach($x in Get-ChildItem){
Get-Acl "C:\fake"
try {
$Acl = Get-Acl
Set-Acl LiteralPath "C:\temp" $Acl -ErrorAction Stop
Write-Host "Access Rule Added" -ForegroundColor Cyan
}
catch [System.UnauthorizedAccessException] {
Write-Host "Insufficient Priviliege. Owner: $($Acl.Owner) ($_)" -ForegroundColor Red
}
catch [System.Security.Principal.IdentityNotMappedException] {
Write-Host "Invalid Prinicpal! ($_)" -ForegroundColor Red
$abort = Read-Host -Prompt "Abort? (Y)"
if($abort -ieq "Y"){ Exit }
}
catch {
Write-Host "ALL: $_" -ForegroundColor Red
}
}
This isn't exactly an answer to my question (as I don't fully understand why it was happening and as such it might snag on something else) but adding -ErrorAction SilentlyContinue to the Get-Acl command that was throwing the first error helps prevent the errant catching:
foreach($x in Get-ChildItem){
Get-Acl "C:\fake" -ErrorAction SilentlyContinue
try {
$Acl = Get-Acl
Set-Acl LiteralPath "C:\temp" $Acl -ErrorAction Stop
Write-Host "Access Rule Added" -ForegroundColor Cyan
}
catch [System.UnauthorizedAccessException] {
Write-Host "Insufficient Priviliege. Owner: $($Acl.Owner) ($_)" -ForegroundColor Red
}
catch [System.Security.Principal.IdentityNotMappedException] {
Write-Host "Invalid Prinicpal! ($_)" -ForegroundColor Red
$abort = Read-Host -Prompt "Abort? (Y)"
if($abort -ieq "Y"){ Exit }
}
catch {
Write-Host "ALL: $_" -ForegroundColor Red
}
}
Though it's strange how $_ doesn't display the actual error but just AclObject in this instance.

Suppress Powershell Errors while adding to $Error Variable

I have a block of code executing that has been set up with a simple If/Else block to catch errors. I would normally use Try/Catch but the Exchange 2010 PS environment I'm working in doesn't allow me to use most of the Try/Catch features (and I can't update it or change it in any way because it's the customer's system and they're unwilling).
The issue is that while the code will function as expected when the Add-DistributionGroupMember cmdlet is set with -ErrorAction "Stop", it will output the error to the host each time, which annoys the customer (and me) because it's effectively just red noise since all the possible errors are being handled via a detailed output file.
If I set the cmdlet to -ErrorAction "SilentlyContinue" the error text is suppressed, but the Error is not added to the $Error[0] spot as I expected. The same is true of -ErrorAction "Ignore". The code requires that the Error be added to the $Error variable each time there is an error.
Here is the code:
$ListMembershipsIn | % {
$Alias = $_.Alias
$Member = $_.Member
Add-DistributionGroupMember -Identity $Alias -Member $Member -Confirm:$false -ErrorAction Stop
if($Error[0] -match "The recipient"){
Write-Host -ForegroundColor Yellow "Already a member"
Add-Content -Path $OutputPath -Value "$($Alias),$($Member),Group already contains Member"
}
elseif($Error[0] -match "couldn't be found"){
Write-Host -ForegroundColor Yellow "not found"
Add-Content -Path $OutputPath -Value "Group does not exist or cannot be found,$($Alias),N/A"
}
elseif($Error[0] -match "couldn't find"){
Write-Host -ForegroundColor Yellow "not found"
Add-Content -Path $OutputPath -Value "Member does not exist or cannot be found,$($Alias),$($Member)"
}
elseif($Error[0] -match "There are Multiple"){
Add-Content -Path $OuputPath -Value "Member name matches too many recipient - Add Member Manually,$($Alias),$($Member)"
}
else{
Add-Content -Path $OutputPath -Value "Member Successfully Added to Group,$($Alias),$($Member)"
Write-Host -ForegroundColor Green "Throw Flag here"
}
}
You have two options for recourse, you can utilize the -ErrorVariable common parameter, or a Try/Catch block to interact with the specific error.
ErrorVariable
When interacting with ErrorVariable, you can prepend the name with a + to append additional errors to it just like the $Error automatic variable, e.g.: -ErrorVariable '+MyError'
$ListMembershipsIn | ForEach-Object {
$Alias = $_.Alias
$Member = $_.Member
Add-DistributionGroupMember -Identity $Alias -Member $Member -ErrorVariable 'MyError'
## No error = good, continue the next iteration of the loop
If (-not $MyError)
{
Add-Content -Path $OutputPath -Value "Member Successfully Added to Group,$Alias,$Member"
Write-Host -ForegroundColor Green "Throw Flag here"
Continue
}
Switch -Regex ($MyError.Exception.Message)
{
'The recipient'
{
Write-Host -ForegroundColor Yellow "Already a member"
Add-Content -Path $OutputPath -Value "$Alias,$Member,Group already contains Member"
}
"couldn't be found"
{
Write-Host -ForegroundColor Yellow "not found"
Add-Content -Path $OutputPath -Value "Group does not exist or cannot be found,$Alias,N/A"
}
"couldn't find"
{
Write-Host -ForegroundColor Yellow "not found"
Add-Content -Path $OutputPath -Value "Member does not exist or cannot be found,$Alias,$Member"
}
'There are Multiple'
{
Add-Content -Path $OuputPath -Value "Member name matches too many recipient - Add Member Manually,$Alias,$Member"
}
}
}
Try/Catch
$ListMembershipsIn | ForEach-Object {
$Alias = $_.Alias
$Member = $_.Member
Try
{
Add-DistributionGroupMember -Identity $Alias -Member $Member -ErrorAction 'Stop'
## No error thrown = successful processing
Add-Content -Path $OutputPath -Value "Member Successfully Added to Group,$Alias,$Member"
Write-Host -ForegroundColor Green "Throw Flag here"
}
Catch
{
Switch -Regex ($_.Exception.Message)
{
'The recipient'
{
Write-Host -ForegroundColor Yellow "Already a member"
Add-Content -Path $OutputPath -Value "$Alias,$Member,Group already contains Member"
}
"couldn't be found"
{
Write-Host -ForegroundColor Yellow "not found"
Add-Content -Path $OutputPath -Value "Group does not exist or cannot be found,$Alias,N/A"
}
"couldn't find"
{
Write-Host -ForegroundColor Yellow "not found"
Add-Content -Path $OutputPath -Value "Member does not exist or cannot be found,$Alias,$Member"
}
'There are Multiple'
{
Add-Content -Path $OuputPath -Value "Member name matches too many recipient - Add Member Manually,$Alias,$Member"
}
}
}
}
It appears that Add-DistributionGroupMember command doesn't fill ErrorVariable when specified.
I found no way to avoid the unwanted output, so I handled the error with Tristan's trick rather than by exception handling (which is absolutely fine, but not fitting my own dev):
$Error.clear()
Add-DistributionGroupMember -Identity $mySite.MailingList -Member $emailAddress
if ($Error.count -eq 0) {
$script:logger["ldissue"] += "<li>Liste de diffusion $($mySite.MailingList) : La ressource <b>$($ADRecord.Name)</b> ($($ADRecord.SamAccountName) - $($BoondRecord.flags)) a été ajoutée à la liste</li>"
} else {
$script:logger["ldissue"] += "<li>Liste de diffusion $($mySite.MailingList) : La ressource <b>$($ADRecord.Name)</b> ($($ADRecord.SamAccountName) - $($BoondRecord.flags)) n'a pas pu être ajoutée à la liste (<i>$($Error[0])</i>)</li>"
}
Use the "-ErrorAction Continue" this won't suppress the error but it will ensure the script continues and it places the error in the $Error variable.
You can also use the $Error.clear() command to remove any stored errors in the session.