Error handling and minimize script output to end-user - powershell

I have a script which loops through a list of users (samaccountname):
# Read usersfile to variable
$users = get-content ("users.txt")
# Get current time
$now = $(get-date -uformat "%H:%M %d/%m/%Y")
# Loop through list of users
foreach($user in $users) {
# Disable user
Disable-QADUser $user
# Set informative description
Set-QADuser $user -Description "Disabled $now"
# Delete all groupmemberships except "domain users"
Get-QADGroup -Containsmember $user | where-object { $_.name -ne 'domain users'} | Remove-QADGroupmember
# Move to "disabled users" group
move-QADObject $user -NewParentContainer 'contosoc.com/Disabled users'
# Hide from addresslist
Set-Mailbox -identity $user -HiddenFromAddressListsEnabled $true
# Moving mailbox to disabled users database
Move-Mailbox -Identity $user -TargetDatabase "myserver\mydb" -BadItemLimit 50 -Confirm:$False
}
I would like to:
Suppress output from the different cmdlets and only show "$user is OK!" if all is ok and log success to a logfile.txt
Display "Error!" and the command that failed if not ok. And output the complete error msgs to a separate logfile.
I've been thinking about doing a if(!cmdlettorun) { write-host "Error!" } But I'm thinking that there must be a better way.
How should I do error handling in a proper fashion so I minimize the displayed output but still let me see it if desirable?

For suppressing cmlet output you can pipe to out-null or precede the command with:
[void](.. your cmdlets..)
a good way for your goal is using a tray-catch-finally code like in this minimized code:
$a = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
foreach($user in $users) {
try
{
... yours code ...
$JobStatus = "OK"
}
catch [exception]
{
$("Error catched: " + $_.Exception.GetType().FullName) | out-file c:\file.log
$("Error catched: " + $_.Exception.Message) | out-file c:\file.log -append
$JobStatus = "not OK"
continue;
}
finally
{
write-host "$user is $JobStatus!"
}
$ErrorActionPreference = $a
}
For some hint to use try-catch-finally read here

Related

Remove Name Notepad file if doesnt exist in AD through powershell

$Users = GC "Desktop\Master.txt"
foreach ($user in $users) {
$userobj = $(try {Get-ADUser $user} catch {$Null})
If ($userobj -ne $Null) {
Write-Host "$User Exists"
} else {
Write-Host "$User Doesn't Exist"
}}
I am using this code to check if a user exists in AD. Later on this notepad file is processed to delete the users that are on the file. I was going to see if it was possible that I can remove the users off the list that don't exist. Is there a command to remove the line from the Notepad file that has the names on if the user doesn't exist in AD
Write a function to test just a single user:
function Test-ADUser {
param($Identity)
try {
$null = Get-ADUser #PSBoundParameters -ErrorAction Stop
# user must exist if we reached this point
return $true
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]{
return $false
}
}
Now you can use Where-Object to filter the list from the text file based on what the function determines:
# Read list from file, filter out usernames that don't exist
$filteredList = Get-Content "Desktop\Master.txt" |Where-Object { Test-ADUser -Identity $_ }
# Write filtered data back to disk
$filteredList |Set-Content "Desktop\Master.txt" -Force

PowerShell AzureAD cmdlet filter not working with variable

My PowerShell script to retrieve the last sign in date of Azure AD users doesn't work when using variables within the filter for UPN. This is a well discussed topic when it comes to AzureAD PowerShell cmdlets, with the most common answer being to wrap the variable in additional quotes. However, none of the examples are for this exact cmdlet. In any case, this does not work for me. To break things down I am now using the approach of building my filter as a variable ($filter).
If I use
$filter = "UserPrincipalName eq 'user123#domain.com'"
the Get-AzureADAuditSignInLogs cmdlet passes me a result. If I use $filter = "UserPrincipalName eq '$($row.UserPrincipalName)'", Get-AzureADAuditSignInLogs gives a $null result.
In BOTH cases, Write-Host $filter outputs the exact same string so there is no problem with retrieving the entry from the CSV file. There is something "special" about the way MS's Azure AD cmdlets implement filters.
Has anyone come across this before? Is there a way I can force my variable to be as if I "hand typed" it?
Connect-AzureAD
$Result = $null
$output = $null
$filter = $null
$csv = Import-csv -Path c:\temp\UPNs.csv
ForEach($row in $csv)
{
$filter = "UserPrincipalName eq '$($row.UserPrincipalName)'"
#$filter = "UserPrincipalName eq 'user123#domain.com'"
$filter = $filter.ToString()
Write-Host $filter
$Result = Get-AzureADAuditSignInLogs -Filter $filter -Top 1 | Select-Object CreatedDateTime, UserPrincipalName
$output = ($Result.UserPrincipalName.ToString() + "," + $Result.CreatedDateTime.ToString())
$output | Out-File C:\temp\HighwaysSignInInfo.txt -Append
Write-host $output
}
CSV file:
UserPrincipalName
user123#domain.com
user1234#domain.com
user12345#domain.com
user12356#domain.com
user1234567#domain.com
This filter works just fine:
Get-AzureADAuditSignInLogs -Filter "startsWith(userPrincipalName,'name#Contoso.com')"
Although some websites say you can use Get-AzureADAuditSignInLogs -Filter "UserPrincipalName eq 'name#Contoso.com'" it doesn't seem to work for me.
The full script (with handling for bandwidth throttling)
#GetAADUserSignIns v1.0
#This script will obtain the last sign in date for each supplied UPN via a source csv file. The biggest issue with such queries is bandwidth throttling. This is handled within the script using a simple
#Try, Catch combined with a Function that can be used to make nested calls.
#IMPORTANT: Review the below 3 options to generate a source file (Option 3 is recommended)
#1. This attempts dump the entire audit log to text file. However you are likely to be throttled before the export completes so don't bother
#Get-AzureADAuditSignInLogs -Filter -All:$true | Out-File C:\Temp\AuditLogsAll.txt
#2. To get a list of all AAD accounts exported to csv (This will capture guest accounts too, the list will be huge, therefore not recommended to run Get-AzureADAuditSignInLogs with)
#Get-AzureADUser -All $true | Export-Csv C:\Temp\AADUsers.csv -NoTypeInformation
#3. Obtain a list of on-prem AD accounts that have a valid UPN
#Get-ADUser -Filter {userPrincipalName -like "*#customdomain.com"} | Select-Object userPrincipalName | Export-Csv C:\Temp\UPNs.csv -NoTypeInformation
Connect-AzureAD
Function GetLastSignInDate {
param (
[string]$upn
)
$filter = "startsWith(userPrincipalName,'" + $upn + "')"
Try
{
$Result = Get-AzureADAuditSignInLogs -Filter $filter -Top 1 | Select-Object CreatedDateTime, UserPrincipalName
$output = ($upn + "," + $Result.CreatedDateTime.ToString())
$output | Out-File C:\temp\SignInInfo.txt -Append
Write-host $output -ForegroundColor Green
}
Catch
{
$message = $_
if ($message -like "*Too Many Requests*")
{
Write-host "Sleeping for 10 seconds due to throttling limitations..." -ForegroundColor Yellow
sleep 10
#Nested function call to retry the entry that was throttled
GetLastSignInDate $upn
}
elseif ($message -like "*This request is throttled*")
{
Write-host "Sleeping for 10 seconds due to throttling limitations..." -ForegroundColor Yellow
sleep 10
#Nested function call to retry the entry that was throttled
GetLastSignInDate $upn
}
elseif ($message -like "*null-valued*")
{
$output = ($upn + ", Not Found")
$output | Out-File C:\temp\SignInInfo.txt -Append
Write-host $output -ForegroundColor Gray
}
elseif ($message -like "*Invalid filter clause*")
{
$output = ($upn + ", Invalid character")
$output | Out-File C:\temp\SignInInfo.txt -Append
Write-host $output -ForegroundColor Gray
}
elseif ($message -like "*Error reading JToken*")
{
$output = ($upn + ", Script stopped due to authentication token timeout")
Write-host $output -ForegroundColor White -BackgroundColor Red
exit
}
else
{
$output = ($upn + ",Error - " + $message.ToString().SubString(0,15))
$output | Out-File C:\temp\SignInInfo.txt -Append
Write-host $output -ForegroundColor Red
}
}
}
$csv = $null
$Result = ""
$output = ""
$filter = ""
$i = $null
$csv = Import-csv -Path C:\temp\upns.csv
ForEach($row in $csv)
{
$upn = $row.UserPrincipalName.ToLower().Replace('`r`n','').Replace('`r','').Replace('`n','').Trim()
GetLastSignInDate $upn
}

Catching errors within powershell script

I have written a script to pull permissions from file directories so that we can audit access to folders. I just want to see what groups we have not what users so I wrote this script to pull out all group names and remove the domain name from the value so that it can then run it through a second script that corrects the AD group name for us if its incorrect at all since we ran into an issue where for some reason some were coming back with slightly different names. The problem is all the AD users named in permissions come back as errors. I want those errors to not even show up on screen is there a way to do that? As you can see I have been trying a few different ways to pipe them to a log or the -ea ignore option but it still shows the errors on screen.
$filelocationscsv = "C:\AD\Excel\File Share migration.csv"
$filelocationcsvcontents = Get-Content -LiteralPath $filelocationscsv
$AllFolders = #()
foreach ($location in $filelocationcsvcontents) {
$AllFolders += $location.Substring(0,$location.Length-1)
}
$outputfilelocation = "C:\AD\Excel\permissions.csv"
$Results = #()
$errResults = #()
Foreach ($i in $Allfolders) {
if (Test-Path $i){
Write-Host "Obtaining file permissions for $i."
$acl = (Get-Acl $i -Filter *).Access | select -ExpandProperty IdentityReference
foreach($Access in $acl) {
if ($Access.Value -notlike "BUILTIN\Administrators" -and $Access.Value -notlike "domain\Domain Admins" -and $Access.Value -notlike "CREATOR OWNER" -and $access.Value -notlike "NT AUTHORITY\SYSTEM" -and $access.Value -notlike "Everyone" -and $access.Value -notlike "BUILTIN\Users" -and $access.Value -notlike "s-1*") {
[string]$perm = $Access.Value.Split('\')[1]
if($checkgroup = Get-ADGroup $perm){
#try
#{
## if( $LASTEXITCODE -gt 0 ){
## # Handle the error here
## # This example writes to the error stream and throws a terminating error
## $errResults += $LASTEXITCODE
## Write-Error "Unable to ping server, ping returned" -EA Ignore
## }
$Properties = [ordered]#{'AD Group'=$perm}
$Results += New-Object -TypeName PSObject -Property $Properties
#}
#Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
#{
# Write-Verbose "$perm skipped." -Verbose
# #$ErrorMessage =
# #$FailedItem = $_.Exception.ItemName
# #$errResults += $ErrorMessage + $FailedItem
#}
}
}
}
}
else {
Write-Host "$i is not accessible"
}
}
$Results | select -Property 'AD Group' -Unique | Export-Csv $outputfilelocation -NoTypeInformation
Its worth noting these errors do not stop my script from running its more of an aesthetic function as well as a learning opportunity for myself. I can use my script like it is but I would love to make it look cleaner and learn how to handle errors better.
As you
indicate you are interested in learning more about error handling, one thing I learned this week are these common Parameters for error handling and recording:
-ErrorAction
-WarningAction
-ErrorVariable
-WarningVariable
You can silence the error messages by using the parameter -ErrorAction SilentlyContinue but capture the error by using the parameter -ErrorVariable
EXAMPLE: get-adgroup -ErrorAction SilentlyContinue -ErrorVariable MyErrors
You can read and manipulate the errors by calling $MyErrors
The warnings work the same way
It might give an alternative to Try/Catch.
Thank you #pwnosh you're a genius!
I changed line 20 to
if($errResults += try {$checkgroup = Get-ADGroup $perm -ErrorAction Stop } catch {[Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]}){
This line forces the users into my CSV as well but then the second script cleans them out anyways with
$results = #()
foreach($Group in ($Groups = Import-csv C:\AD\Excel\permissions.csv)){
$groupname = $Group.'AD Group'
Write-Host "Confirming $groupname group name in AD."
$results += get-adgroup -Filter "name -like '$groupname'" -Properties * -SearchBase "dc=domain,dc=local,dc=net" | select name
}
$results | Export-Csv C:\AD\Excel\ADGroups.csv -NoTypeInformation

powershell how to find out if a variable is part of another variable

I have pasted my code below and pulled out everything that is already working, so I only have the part that isn't working as intended.
I am trying to put the EmployeeID, from a csv, in front of the Description field in AD. I can get that part to work, but the beginning of the If statement where I try to check if the $ID is already in the description fails; it just keeps adding it every time the script runs.
I have tried making both the $ID and $Description type as string with Out-String, and I have left that out, but it's the same result. I have tried -notcontains, -notmatch, and -notlike (which I believe is the correct one to use), but none work. I have even put my variables in a text file to make sure they are pulling the correct information.
I am still learning all of the intricacies of Powershell. Can anyone see what I'm doing wrong?
# Get script Start Time (used to measure run time)
$startDTM = (Get-Date)
#Null out variables
$Users = $Null
$ID = $Null
$Users = Import-Csv .\ImportADUsers\Test-Import-user-data.csv
Import-Module ActiveDirectory
$path = Split-Path -parent ".\ImportADUsers\*.*"
#Create log date
$logdate = Get-Date -Format yyyy-MM-dd-THH.mm.ss
$logfile = $path + "\logs\$logdate.logfile.txt"
# Enumerate the users, one line at a time.
# This assumes the first line is a header line defining the fields.
ForEach ($User In $Users)
{
# Retrieve values from the csv.
$ID = $User.HRRef
# Retrieve the sAMAccountName of the user from AD.
$UserDN = (Get-ADUser -LDAPFilter "(employeeID=$ID)").sAMAccountName
$ID | Out-File $logfile -Append
$IDString = $ID | Out-String
#Retrieve the Description of the user from AD.
$Description = Get-ADUser -Identity $UserDN -Properties description
$Description = $Description.description | Out-String
$Description | Out-File $logfile -Append
# Make sure there is only one user with this employeeID.
If ($UserDN.Count -eq 1)
{
IF ($Description -notlike $IDString) {Set-ADUser -Identity $UserDN
-Description "$($ID) - $($Description)" }
}
Else {"User with ID $ID either not found, or more than one user found."
| Out-File $logfile -Append}
#Log error for users that are not in Active Directory or EmployeeID
#found more than once
}
#Finish
#The lines below calculates how long it takes to run this script
# Get End Time
$endDTM = (Get-Date)
# Echo Time elapsed
"Elapsed Time: $(($endDTM-$startDTM).totalminutes) minutes"
#Append the minutes value to the text file
"Import took $(($endDTM-$startDTM).totalminutes) minutes to complete." |
Out-File $logfile -Append
#SCRIPT ENDS
Your string comparison is incorrect. Below is how to fix it.
The change: -notlike $IDString => -notlike "*$ID*"
ForEach ($User In $Users)
{
# Retrieve values from the csv.
$ID = $User.HRRef
$ID | Out-File $logfile -Append
# Retrieve the SAMAccountName of the user from AD.
$UserDN = (Get-ADUser -LDAPFilter "(employeeID=$ID)").SAMAccountName
#Retrieve the Description of the user from AD.
$Description = (Get-ADUser -Identity $UserDN -Properties description).Description
$Description | Out-File $logfile -Append
# Make sure there is only one user with this employeeID.
If ($UserDN.Count -eq 1 -and $Description -notlike "*$IDString*")
{
Set-ADUser -Identity $UserDN -Description "$ID - $Description"
}
Else
{
"User with ID $ID either not found, or more than one user found." | Out-File $logfile -Append
}
}

Powershell - Order of script

I am trying to input a user using a variable and check active directory to confirm the full name of the user and pause the script before running the next command.
The script is running the pause command before the get-aduser command - see below script
#Enter Username
$username = read-host "Username"
Get-ADUser -Filter "Name -eq '$username'" | Select-Object name, samaccountname
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
#Removes user from groups
Get-ADPrincipalGroupMembership -Identity $username | where {$_.Name -notlike "Domain Users"} |% {Remove-ADPrincipalGroupMembership -Identity $uSername -MemberOf $_ -Confirm:$false}
write-output End
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
In my experience, Get-ADUser and similar commands can take a long time to run, possible up to 20 seconds or so. Rarely, I have found that it makes the code unusable due to some commands running before or after it. If you want to test to see if this is really the case for you, add this line in between every other line in your code:
Read-Host -Prompt "Press Enter to continue"
That way, you can test whether there is a real difference between when you put that line there, and if you don't. If there is actually a difference, you may have to look into start-sleep or wait.
I would do something like this to have the user validate, cause i think that is what you are after, before continuing to revoke the users group membership
Write-Host "`nEnter the UserName: " -NoNewline -ForegroundColor Yellow
$UserName = Read-Host
$UserName = Get-ADUser -Filter "Name -eq '$UserName'" | Select-Object Name, SamAccountName
Write-Host "`nRevoke membership of all groups for user" $UserName.Name "("$UserName.SamAccountName")?`n [Y]es, [N]o : " -ForegroundColor Yellow -NoNewline
$Confirmation = Read-Host
While ("y","yes","n","no" -notcontains $Confirmation) {
Write-Host "`nNot a valid input! Please try again ..." -ForegroundColor Red
Write-Host "`nRevoke membership of all groups for user" $UserName.Name "("$UserName.SamAccountName")?`n [Y]es, [N]o : " -ForegroundColor Yellow -NoNewline
$Confirmation = Read-Host
}
If ($Confirmation -eq "n" -or $Confirmation -eq "no") {
Write-Host "Aborted!" -ForegroundColor Red
Break
}
# Next step here!
# Get-ADPrincipalGroupMembership -Identity $UserName | where {$_.Name -notlike "Domain Users"} |% {Remove-ADPrincipalGroupMembership -Identity $UserName -MemberOf $_ -Confirm:$false}
Just another piece of code, these kind of changes needs some proper logging and error handling, while my code only logs to the console it can still be useful.
It uses confirm in place of 'pause' so the user can choose to continue or stop.
### CmdletBinding
# Alows the use of -Whatif(not used), -Confirm, -Verbose and -Debug.
# Reference: https://technet.microsoft.com/en-us/library/ff677563.aspx
# https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_functions_cmdletbindingattribute
# https://blogs.technet.microsoft.com/poshchap/2014/10/24/scripting-tips-and-tricks-cmdletbinding/
[CmdletBinding(
SupportsShouldProcess = $true,
ConfirmImpact=’High’
)]
# Script parameters.
Param(
[parameter(HelpMessage = "Command parram, not used.")]$Command = "nothing"
#Run with PowerShell Fix, reference: https://social.technet.microsoft.com/Forums/office/en-US/fe7fb473-7ed6-4397-9c95-120201c34847/problems-with-powershell-30?forum=winserverpowershell
)
#Console clean-up.
Clear-Host
# Set error action to Stop, if something happens and it isnt inside a trap (try/catch) then stop.
$ErrorActionPreference = "Stop"
# Controls the Verbose Output
$VerbosePreference = "Continue" #Optional
#Intial message for User execution, whitespace is for the progressbars.
"
Script: Remove-ADUserGroupMembership.ps1
"
Write-Verbose "Starting main loop."
While ($true){
#White space for in between questions.
Write-Host "
"
#Retrieve username from user input.
Write-Host "Provide the ADUser for ADGroup removal here:"
$Username = read-host "Username"
#Retrieve ADUser object from AD.
Write-Verbose "Querying Active Directory for user $Username"
Try {
$ADUser = Get-ADUser $Username
Write-Verbose "User Found, $($ADUser.Name) "
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
Write-Warning "Could not find user $Username in Active Directory, check spelling and try again."
Continue #this wil reset the while loop
}
Catch {
Write-Warning "Unknown Errror, Could not retrieve user $Username from Active Directory, please try again."
Continue #this wil reset the while loop
}
#Retrieve GroupMembership for user.
Write-Verbose "Querying Active Directory for GroupMembership of User $($ADUser.name), exluding Domain Users"
Try {
$GroupMembership = $ADUser | Get-ADPrincipalGroupMembership | where {$_.Name -notlike "Domain Users"}
Write-Verbose "Found $($GroupMembership.count) GroupMemberships for User $($ADUser.name) (Not inluding Domain Users)"
}
Catch {
Write-Warning "Unknown Errror, Could not retrieve GroupMembership for user $($ADUser.Name) from Active Directory, please try again."
Continue #this wil reset the while loop
}
#Remove GroupMembership for user.
if ($pscmdlet.ShouldProcess("$($ADUser.name)", "Remove-ADPrincipalGroupMembership {$($GroupMembership.count) Groups}")) {
Write-Verbose "Entering GroupMembership removal loop for user $($ADUser.name)"
Foreach ($Group in $GroupMembership) {
Try {
$ADUser | Remove-ADPrincipalGroupMembership -MemberOf $Group -WhatIf -Confirm:$true
Write-Verbose "$Group removed from from user $($ADUser.name)"
}
catch {
Write-Warning "An Error occured, could not remove group $Group from user $($ADUser.Name)"
Continue #this will skip this group.
}
}
}
else {
Write-Warning "Action Remove-ADPrincipalGroupMembership {$($GroupMembers.count) Groups} canceled for $($ADUser.name)"
}
Read-Host "Press Enter to exit."
break #exit from while loop
}