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.
Related
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
I have this code which deletes User Profiles off a remote machine. The removal of profiles work just fine but, the Aesthetic of doing so doesn't. What do i mean?
I'm passing the user display names to an index and making a selection out of it, and that works fine in regards to assigning the proper names to the appropriate Index Number its associated to in C:\users.
The next line of code is it grabbing the selections i made, and running through them displaying the same name i did for the index, and then it goes off to delete the CIM instance.
So my question is, why is it not passing the subexpression $userinfo1 that is already made and not putting it into the next block of code, for example, the following works as in grabbing the proper Display Name and assigning it to the proper Number:
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - account not in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
but, the next block doesn't associate the display name (that was captured in $userinfo1) with the number i select and it just continues to display the first display name with the rest of the profiles its reiterating through:
foreach($Profile in $menu[$selection]){
Write-Host "Deleting user: $(,$userinfo1[$selection]) `
ID:$Profile "}
Hopefully this makes sense, and if anyone can point me in the right direction id greatly appreciate it!
Heres the rest of the script, please feel free to use it as it does work for deleting the actual profile off the system and not just the files.
#Deletes a profile properly off remote machine. WARNING: DOES NOT BACK UP DATA! Use at your own peril. Delprofile
$cn = Read-Host -Prompt "Enter Computer Name"
$ping = Test-Connection -ComputerName $cn -Count 1 -Quiet
If($ping -eq $false){ Write-Host "Computer seems to be offline, please check name spelling." -ForegroundColor DarkYellow; Write-Host ""; &PFL-Delete } else {
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - account not in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
foreach($Profile in $menu[$selection]){
Write-Host "Deleting user: $(,$userinfo1[$selection]) `
ID:$Profile "
$del = Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile }
If($del -eq $null){Write-Warning "No CIM instance found on system, profile has been deleted but files persist. Delete manually!"} else{
Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile } | Remove-CimInstance -WhatIf
Write-Host "user profile has been deleted" -ForegroundColor Red
Write-Host ""}
}
}
#CountPs $cn
12/31/2020 - EDIT:
Here is the finished result:
Function Delete-PFL{
#Deletes a profile properly off remote machine. WARNING: DOES NOT BACK UP DATA! Use at your own peril. Delprofile
$cn = Read-Host -Prompt "Enter Computer Name"
$ping = Test-Connection -ComputerName $cn -Count 1 -Quiet
If($ping -eq $false){ Write-Host "Computer seems to be offline, please check name spelling." -ForegroundColor DarkYellow; Write-Host ""; &Delete-PFL } else {
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - ACCOUNT NOT in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
foreach($index in $selection) {
$Profile = $menu[$index]
Write-Host "Deleting user: $($userinfo1[$index]) `
ID:$Profile "
$del = Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile }
If($del -eq $null){Write-Warning "No CIM instance found on system, profile has been deleted but files persist."
Write-Host "Attempting to delete files, please wait. . ."
Remove-Item -Path "\\$cn\c$\users\$Profile" -Force -WhatIf
Write-Host ""
Start-Sleep -Seconds 2
Write-Host "Checking if Files are still there. . ."
$TestPath = Test-Path -Path "\\$cn\c$\users\$Profile"
If($TestPath -eq $false){ Write-Host "Profile Files have been deleted. `
Continuing. . . ." -ForegroundColor Green
}
} else{
Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile } | Remove-CimInstance -WhatIf
Write-Host "user profile has been deleted" -ForegroundColor Red
Write-Host ""
}
}
}
#CountPs $cn
}
Remember to remove the -whatif parameter. Enjoy!
$selection is an array of indices, so in your foreach loop you must refer to the single index at hand, not to $selection as a whole, to get the desired display output.
The conceptually clearest approach is probably to iterate over the indices contained in $selection:
foreach($index in $selection) {
$Profile = $menu[$index]
Write-Host "Deleting user: $($userinfo1[$index]) `
EDIPI:$Profile "
# ...
}
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.
i have a text file containing arround 100 servers, how can i push these into a script and test if they exist within AD? I have a simple script below:
$serverlist = get-content ServerList.txt
foreach ($server in $serverlist) {
if (Get-ADComputer $serverlist ) {
Write-Host "#########################"
Write-Host "Computer object exists"
Write-Host "#########################"
}
else {
Write-Host "#########################"
Write-Host "Computer object NOT FOUND"
Write-Host "#########################"
}
}
the above does not work returning a error:
Get-ADComputer : Cannot convert 'System.Object[]' to the type 'Microsoft.ActiveDirectory.Management.ADComputer' required by parameter 'Identity'. Specified method is not supported.
Can someone please explain does the get-adcomputer only allow a single object? Also if i remove the txt file and add a server shown below:
if (Get-ADComputer "server name" )
The above provides only results if the server exists within AD, if the server does not the error is shown below:
Get-ADComputer : Cannot find an object with identity: 'iuiub' under: 'DC=####,DC=#####,DC=#####'
Thank you for any insight / help!
Phil
Create an array - #(). If the array has 1 or more objects in it - which is $true - then you know the computer exists. If the array has 0 objects in it - which is $false- then you know the computer doesn't exist. I know some people don't like the ErrorAction to be set to SilentlyContinue but you're "Outputting an Error" if an error does occur.
$serverlist = get-content ServerList.txt
foreach ($server in $serverlist) {
if (#(Get-ADComputer $server -ErrorAction SilentlyContinue).Count) {
Write-Host "#########################"
Write-Host "Computer object exists"
Write-Host "#########################"
}
else {
Write-Host "#########################"
Write-Host "Computer object NOT FOUND"
Write-Host "#########################"
}
}
Another thing you could try are try catch blocks. Sorta like this:
$serverlist = get-content ServerList.txt
foreach ($server in $serverlist) {
try{
Get-ADComputer $server -ErrorAction Stop
Write-Host "#########################"
Write-Host "Computer object exists"
Write-Host "#########################"
}
catch{
Write-Host "#########################"
Write-Host "Computer object NOT FOUND"
Write-Host "#########################"
}
}
Line 3, change $serverlist to $server
With regards to handling a not found result. I'd try flipping the logic :
$serverlist = get-content ServerList.txt
foreach ($server in $serverlist) {
$tempVar = Get-ADComputer $server
if ($tempVar -like "Get-ADComputer : Cannot find an object with identity" ) {
Write-Host "#########################"
Write-Host "Computer object NOT FOUND"
Write-Host "#########################"
}
else{
Write-Host "#########################"
Write-Host "Computer object exists"
Write-Host "#########################"
}
}
In order to get a more helpful output, I'd go with the following... You'll just have a list of green and red lines indicating which server was found and which one wasn't.
$serverlist = get-content ServerList.txt
foreach ($server in $serverlist) {
try {
Get-ADComputer $server -ErrorAction Stop | Out-Null
Write-Host "$($server) exists" -ForegroundColor DarkGreen
}
catch {
Write-Host "$($server) NOT FOUND" -ForegroundColor DarkRed
}
}
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 | ...