From 41610c5f0ef89bf363b6ba405d1df87912d54353 Mon Sep 17 00:00:00 2001 From: AdminDroid <49208841+admindroid-community@users.noreply.github.com> Date: Sat, 14 Oct 2023 11:01:59 +0530 Subject: [PATCH] Automate M365 User Offboarding Process Automate 13+ M365 Users' Offboarding Process --- .../M365UserOffBoarding.ps1 | 591 ++++++++++++++++++ 1 file changed, 591 insertions(+) create mode 100644 Automate M365 User Offboarding/M365UserOffBoarding.ps1 diff --git a/Automate M365 User Offboarding/M365UserOffBoarding.ps1 b/Automate M365 User Offboarding/M365UserOffBoarding.ps1 new file mode 100644 index 0000000..44560cd --- /dev/null +++ b/Automate M365 User Offboarding/M365UserOffBoarding.ps1 @@ -0,0 +1,591 @@ +<# +============================================================================================= +Name: Automate Microsoft 365 User Offboarding with PowerShell +Description: This script can perform 14 Microsoft 365 offboarding activities. +website: blog.Admindroid.com +Script by: AdminDroid Team + +For detailed Script execution: https://blog.admindroid.com/automate-microsoft-365-user-offboarding-with-powershell +============================================================================================== + +param( +[string]$TenantId, +[string]$ClientId, +[string]$CertificateThumbprint, +[string]$CSVFilePath, +[String]$UPNs +) +Function ConnectModules +{ + $MsGraphBetaModule = Get-Module Microsoft.Graph.Beta -ListAvailable + if($MsGraphBetaModule -eq $null) + { + Write-host "Important: Microsoft Graph Beta module is unavailable. It is mandatory to have this module installed in the system to run the script successfully." + $confirm = Read-Host Are you sure you want to install Microsoft Graph Beta module? [Y] Yes [N] No + if($confirm -match "[yY]") + { + Write-host "Installing Microsoft Graph Beta module..." + Install-Module Microsoft.Graph.Beta -Scope CurrentUser -AllowClobber + Write-host "Microsoft Graph Beta module is installed in the machine successfully" -ForegroundColor Magenta + } + else + { + Write-host "Exiting. `nNote: Microsoft Graph Beta module must be available in your system to run the script" -ForegroundColor Red + Exit + } + } + $ExchangeOnlineModule = Get-Module ExchangeOnlineManagement -ListAvailable + if($ExchangeOnlineModule -eq $null) + { + Write-host "Important: Exchange Online module is unavailable. It is mandatory to have this module installed in the system to run the script successfully." + $confirm = Read-Host Are you sure you want to install Exchange Online module? [Y] Yes [N] No + if($confirm -match "[yY]") + { + Write-host "Installing Exchange Online module..." + Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser + Write-host "Exchange Online Module is installed in the machine successfully" -ForegroundColor Magenta + } + else + { + Write-host "Exiting. `nNote: Exchange Online module must be available in your system to run the script" + Exit + } + } + Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null + Disconnect-ExchangeOnline -Confirm:$false + Write-Host "Connecting modules(Microsoft Graph and Exchange Online module)...`n" + try{ + if($TenantId -ne "" -and $ClientId -ne "" -and $CertificateThumbprint -ne "") + { + Connect-MgGraph -TenantId $TenantId -ClientId $ClientId -CertificateThumbprint $CertificateThumbprint -ErrorAction SilentlyContinue -ErrorVariable ConnectionError|Out-Null + if($ConnectionError -ne $null) + { + Write-Host $ConnectionError -Foregroundcolor Red + Exit + } + $Scopes = (Get-MgContext).Scopes + $ApplicationPermissions=@("Directory.ReadWrite.All","AppRoleAssignment.ReadWrite.All","User.EnableDisableAccount.All","RoleManagement.ReadWrite.Directory") + foreach($Permission in $ApplicationPermissions) + { + if($Scopes -notcontains $Permission) + { + Write-Host "Note: Your application required the following graph application permissions: Directory.ReadWrite.All,AppRoleAssignment.ReadWrite.All,User.EnableDisableAccount.All,RoleManagement.ReadWrite.Directory" -ForegroundColor Yellow + Exit + } + } + Connect-ExchangeOnline -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -Organization (Get-MgBetaDomain | Where-Object {$_.isInitial}).Id -ShowBanner:$false + } + else + { + Connect-MgGraph -ErrorAction SilentlyContinue -Errorvariable ConnectionError |Out-Null + if($ConnectionError -ne $null) + { + Write-Host $ConnectionError -Foregroundcolor Red + Exit + } + Connect-ExchangeOnline -UserPrincipalName (Get-MgContext).Account -ShowBanner:$false + } + } + catch + { + Write-Host $_.Exception.message -ForegroundColor Red + Exit + } + Write-Host "Microsoft Graph Beta PowerShell module is connected successfully" -ForegroundColor Cyan + Write-Host "Exchange Online module is connected successfully" -ForegroundColor Cyan +} + +Function DisableUser +{ + try{ + Update-MgBetaUser -UserId $UPN -AccountEnabled:$false + $Script:DisableUserAction = "Success" + } + catch + { + $Script:DisableUserAction = "Failed" + $ErrorLog = "$($UPN) - Disable User Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } +} + +Function ResetPasswordToRandom +{ + $Password = -join ((48..57) + (65..90) + (97..122) | ForEach-Object { [char]$_ } | Get-Random -Count 8) + $log = "$UPN - $Password" + $Pwd = ConvertTo-SecureString $Password -AsPlainText –Force + try{ + $Passwordprofile = @{ + forceChangePasswordNextSignIn = $true + password = $Pwd + } + Update-MgBetaUser -UserId $UPN -PasswordProfile $Passwordprofile + $log>>$PasswordLogFile + $Script:ResetPasswordToRandomAction = "Success" + } + catch + { + $Script:ResetPasswordToRandomAction ="Failed" + $ErrorLog = "$($UPN) - Reset Password To Random Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } +} +Function ResetOfficeName +{ + try{ + Update-MgBetaUser -UserId $UPN -OfficeLocation "EXD" + $Script:ResetOfficeNameAction = "Success" + } + catch + { + $Script:ResetOfficeNameAction = "Failed" + $ErrorLog = "$($UPN) - Reset Office Name Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } +} + +Function RemoveMobileNumber +{ + try{ + Update-MgBetaUser -UserId $UPN -MobilePhone ' ' + $Script:RemoveMobileNumberAction = "Success" + } + catch + { + $Script:RemoveMobileNumberAction = "Failed" + $ErrorLog = "$($UPN) - Remove Mobile Number Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } +} + +Function RemoveGroupMemberships +{ + #Remove memberships from group + $groupMemberships = $Memberships|?{$_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.group'} + foreach($Membership in $groupMemberships) + { + try{ + Remove-MgBetaGroupMemberByRef -GroupId $Membership.Id -DirectoryObjectId $UserId -ErrorAction SilentlyContinue -ErrorVariable MemberRemovalErr + if($MemberRemovalErr) + { + Remove-DistributionGroupMember -Identity $Membership.Id -Member $UserId -BypassSecurityGroupManagerCheck -Confirm:$false + } + } + catch + { + $ErrorLog = "$($UPN) - GroupId($($Membership.Id)) - Remove Group Memberships Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } + } + #Remove ownerships from group + $UserOwnerships = Get-MgBetaUserOwnedObject -UserId $UPN|?{$_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.group'} + foreach($UserOwnership in $UserOwnerships) + { + try{ + Remove-MgBetaGroupOwnerByRef -GroupId $UserOwnership.Id -DirectoryObjectId $UserId -ErrorAction SilentlyContinue -ErrorVariable OwnerRemovalErr + if($OwnerRemovalErr) + { + $ErrorLog = "$($UPN) - GroupId($($UserOwnership.Id)) - Remove Group Memberships Action - "+$OwnerRemovalErr.Exception.Message + $ErrorLog>>$ErrorsLogFile + } + } + catch + { + $ErrorLog = "$($UPN) - GroupId($($UserOwnership.Id)) - Remove Group Memberships Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } + } + $DistributionGroupOwnerships = Get-DistributionGroup | where {$_.ManagedBy -contains "$UserId"} + foreach($DistributionGroupOwnership in $DistributionGroupOwnerships) + { + Set-DistributionGroup -Identity $DistributionGroupOwnership.Identity -BypassSecurityGroupManagerCheck -ManagedBy @{Remove=$UPN} -ErrorAction SilentlyContinue -ErrorVariable OwnerRemovalErr + if($OwnerRemovalErr) + { + $ErrorLog = "$($UPN) - GroupId($($DistributionGroupOwnership.ExternalDirectoryObjectId)) - Remove Group Memberships Action - "+$OwnerRemovalErr.Exception.Message + $ErrorLog>>$ErrorsLogFile + } + } + if($ErrorLog -eq $null) + { + $Script:RemoveGroupMembershipsAction = "Success" + } + elseif($groupMemberships -eq $null -and $UserOwnerships -eq $null -and $DistributionGroupOwnerships -eq $null) + { + $Script:RemoveGroupMembershipsAction = "No group memberships" + } + else + { + $Script:RemoveGroupMembershipsAction = "Failed" + } + +} + +Function RemoveAdminRoles +{ + $AdminRoles = $Memberships|?{$_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.directoryRole'} + if($AdminRoles -eq $null) + { + $Script:RemoveAdminRolesAction = "No admin roles" + } + else + { + foreach($AdminRole in $AdminRoles) + { + try{ + Remove-MgBetaDirectoryRoleMemberByRef -DirectoryObjectId $UserId -DirectoryRoleId $AdminRole.Id + } + catch + { + $ErrorLog = "$($UPN) - Role Id($($Role.DisplayName)) Remove Admin Roles Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } + } + if($ErrorLog -eq $null) + { + $Script:RemoveAdminRolesAction = "Success" + } + else + { + $Script:RemoveAdminRolesAction = "Failed" + } + } +} +Function RemoveAppRoleAssignments +{ + $AppRoleAssignments = Get-MgBetaUserAppRoleAssignment -UserId $UPN + if($AppRoleAssignments -ne $null) + { + $AppRoleAssignments | ForEach-Object { + try{ + Remove-MgBetaUserAppRoleAssignment -AppRoleAssignmentID $_.Id -UserId $UPN + } + catch + { + $ErrorLog = "$($UPN) - Remove App Role Assignments Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } + } + if($ErrorLog -eq $null) + { + $Script:RemoveAppRoleAssignmentsAction = "Success" + } + else + { + $Script:RemoveAppRoleAssignmentsAction = "Failed" + } + } + else + { + $Script:RemoveAppRoleAssignmentsAction = "No app role assignments" + } +} + +Function HideFromAddressList +{ + if($MailBoxAvailability -eq 'No') + { + $Script:HideFromAddressListAction = "No Exchange license assigned to user" + return + } + try{ + Set-Mailbox -Identity $UPN -HiddenFromAddressListsEnabled $true + $Script:HideFromAddressListAction = "Success" + } + catch + { + $Script:HideFromAddressListAction = "Failed" + $ErrorLog = "$($UPN) - Hide From Address List Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } +} +Function RemoveEmailAlias +{ + if($MailBoxAvailability -eq 'No') + { + $Script:RemoveEmailAliasAction = "No Exchange license assigned to user" + return + } + try{ + $EmailAliases=Get-Mailbox $UPN| select -ExpandProperty emailaddresses| ?{$_.StartsWith("smtp")} + if($EmailAliases -eq $null) + { + $Script:RemoveEmailAliasAction = "No alias" + } + else + { + Set-Mailbox $UPN -EmailAddresses @{Remove=$EmailAliases} -WarningAction SilentlyContinue + $Script:RemoveEmailAliasAction = "Success" + } + } + catch + { + $Script:RemoveEmailAliasAction = "Failed" + $ErrorLog = "$($UPN) - Remove Email Alias Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } +} + +Function WipingMobileDevice +{ + if($MailBoxAvailability -eq 'No') + { + $MobileDeviceAction = "No Exchange license assigned to user" + return + } + try{ + $MobileDevice = Get-MobileDevice -Mailbox $UPN + $MobileDevice| Clear-MobileDevice + $Script:MobileDeviceAction = "Success" + } + catch + { + $Script:MobileDeviceAction = "Failed" + $ErrorLog = "$($UPN) - Wiping Mobile Device Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } +} + +Function DeleteInboxRule +{ + if($MailBoxAvailability -eq 'No') + { + $Script:DeleteInboxRuleAction = "No Exchange license assigned to user" + return + } + try{ + $MailboxRule = Get-InboxRule -Mailbox $UPN + $MailboxRule| Remove-InboxRule -Confirm:$False + $Script:DeleteInboxRuleAction = "Success" + } + catch + { + $Script:DeleteInboxRuleAction = "No inbox rule" + } +} + +Function ConvertToSharedMailbox +{ + if($MailBoxAvailability -eq 'No') + { + $Script:ConvertToSharedMailboxAction = "No Exchange license assigned to user" + return + } + try{ + Set-Mailbox -Identity $UPN -Type Shared -WarningAction SilentlyContinue + $Script:ConvertToSharedMailboxAction = "Success" + } + catch + { + $Script:ConvertToSharedMailboxAction = "Failed" + $ErrorLog = "$($UPN) - Convert To Shared Mailbox Action - "+$Error[0].Exception.Message + $ErrorLog>>$ErrorsLogFile + } +} + +Function RemoveLicense +{ + $Licenses = Get-MgBetaUserLicenseDetail -UserId $UPN + if($Licenses -ne $null) + { + Set-MgBetaUserLicense -UserId $UPN -RemoveLicenses @($Licenses.SkuId) -AddLicenses @() -ErrorAction SilentlyContinue -ErrorVariable LicenseError | Out-Null + if($LicenseError) + { + $Script:RemoveLicenseAction = "Failed" + $ErrorLog = "$($UPN) - Remove License Action - "+$LicenseError.Exception.Message + $ErrorLog>>$ErrorsLogFile + } + else + { + $Script:RemoveLicenseAction = "Removed licenses - $($Licenses.SkuPartNumber -join ',')" + } + } + else + { + $Script:RemoveLicenseAction = "No license" + } +} + +Function SignOutFromAllSessions +{ + Revoke-MgBetaUserSignInSession -UserId $UPN | Out-Null + $Script:SignOutFromAllSessionsAction = "Success" +} + +Function Disconnect_Modules +{ + Disconnect-MgGraph -ErrorAction SilentlyContinue| Out-Null + Disconnect-ExchangeOnline -Confirm:$false + Exit +} + +Function main +{ + ConnectModules + #Importing CSV file + if($CSVFilePath -ne "") + { + $CSVFilePath = $CSVFilePath.Trim() + try{ + $UPNCSVFile = Import-Csv -Path $CSVFilePath -Header UserPrincipalName + [array]$UPNs = $UPNCSVFile.UserPrincipalName + } + catch + { + Write-Host $_.Exception.Message -ForegroundColor Red + Exit + } + } + elseif($UPNs -ne "") + { + [array]$UPNs = $UPNs.Split(',') + } + else + { + $UPNs = Read-Host `nEnter the UserPrincipalName of the user you want to offboard + if($UPNs -ne "") + { + [array]$UPNs = $UPNs -split ',' + } + else + { + Write-Host You must provide UPN of the user to offboard. -ForegroundColor Red + Disconnect_Modules + } + } + $Location = Get-Location + $ExportCSV = "$Location\M365UserOffBoarding_StatusFile_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm-ss` tt).ToString()).csv" + $PasswordLogFile = "$Location\PasswordLogFile_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm-ss` tt).ToString()).txt" + $InvalidUserLogFile = "$Location\InvalidUsersLogFile$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm-ss` tt).ToString()).txt" + $ErrorsLogFile = "$Location\ErrorsLogFile$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm-ss` tt).ToString()).txt" + $AvailabiltyOfInvalidUser = $false + + Write-Host "`nWe can perform below operations.`n" -ForegroundColor Cyan + Write-Host " 1. Disable user" -ForegroundColor Yellow + Write-Host " 2. Reset password to random" -ForegroundColor Yellow + Write-Host " 3. Reset Office name" -ForegroundColor Yellow + Write-Host " 4. Remove Mobile number" -ForegroundColor Yellow + Write-Host " 5. Remove group memberships" -ForegroundColor Yellow + Write-Host " 6. Remove admin roles" -ForegroundColor Yellow + Write-Host " 7. Remove app role assignments" -ForegroundColor Yellow + Write-Host " 8. Hide from address list" -ForegroundColor Yellow + Write-Host " 9. Remove email alias" -ForegroundColor Yellow + Write-Host " 10. Wiping mobile device" -ForegroundColor Yellow + Write-Host " 11. Delete inbox rule" -ForegroundColor Yellow + Write-Host " 12. Convert to shared mailbox" -ForegroundColor Yellow + Write-Host " 13. Remove license" -ForegroundColor Yellow + Write-Host " 14. Sign-out from all sessions" -ForegroundColor Yellow + Write-Host " 15. All the above operations" -ForegroundColor Yellow + $Actions=Read-Host "`nPlease choose the action to continue" + if($Actions -eq "") + { + Write-Host "`nPlease choose the action from the above." -ForegroundColor Red + Exit + } + $Actions = $Actions.Trim() + $Actions = $Actions.Split(',') + $CheckActions = Compare-Object -Referenceobject $Actions -DifferenceObject @(1..15) + if($CheckActions|?{$_.SideIndicator -eq "<="}) + { + Write-Host "`nPlease choose the correct action number from the above actions." -ForegroundColor Red + Disconnect_Modules + } + Foreach($UPN in $UPNs) + { + $UPN = $UPN.Trim() + Write-Progress "Processing $UPN" + $Script:Status = "$UPN - " + $User = Get-MgBetaUser -UserId $UPN -ErrorAction SilentlyContinue + $UserId = $User.Id + if($User -eq $null) + { + $InvalidUser= "$UPN" + $InvalidUser>>$InvalidUserLogFile + Continue + } + $MailBox = Get-Mailbox -Identity $UPN -RecipientTypeDetails UserMailbox -ErrorAction SilentlyContinue + if($MailBox -ne $null) + { + $MailBoxAvailability = "Yes" + } + else + { + $MailBoxAvailability = "No" + } + if($Actions -contains 15) + { + $Actions = 1..14 + } + if($Actions -contains 5 -or $Actions -contains 6) # To get memberships of the user (group and roles) + { + $Memberships = Get-MgBetaUserMemberOf -UserId $UPN + } + foreach($Action in $Actions) + { + switch($Action){ + 1 { DisableUser ; break } + 2 { ResetPasswordToRandom ; break } + 3 { ResetOfficeName ; break } + 4 { RemoveMobileNumber ; break } + 5 { RemoveGroupMemberships ; break } + 6 { RemoveAdminRoles ; break } + 7 { RemoveAppRoleAssignments ; break } + 8 { HideFromAddressList ; break } + 9 { RemoveEmailAlias ; break } + 10 { WipingMobileDevice ; break } + 11 { DeleteInboxRule ; break } + 12 { ConvertToSharedMailbox ; break } + 13 { RemoveLicense ; break } + 14 { SignOutFromAllSessions ; break } + Default { + Write-Host "No action found. Please provide valid input" -ForegroundColor Red + Disconnect_Modules + } + } + } + #This is for to set mailbox availablity value only if actions contains mailbox related actions. Otherwise its value set to be null. + $MailboxRelatedActions = Compare-Object $Actions @(8,9,10,11,12,13) -IncludeEqual -ExcludeDifferent + if($MailboxRelatedActions.count -eq 0) + { + $MailBoxAvailability = "" + } + if($MailBoxAvailability -eq "No") + { + $MailBoxAvailability = "No Exchange license assigned to user" + } + $Result = [PSCustomObject]@{ + 'UPN'=$UPN;'Disable User'=$DisableUserAction;'Reset Password To Random'=$ResetPasswordToRandomAction;'Reset OfficeName'=$ResetOfficeNameAction;'Remove Mobile Number'=$RemoveMobileNumberAction; + 'Remove Group Memberships'=$RemoveGroupMembershipsAction;'Remove Admin Roles'=$RemoveAdminRolesAction;'Remove AppRole Assignments'=$RemoveAppRoleAssignmentsAction;'Exchange User'=$MailBoxAvailability; + 'Hide From Address List'= $HideFromAddressListAction;'Remove Email Alias'=$RemoveEmailAliasAction;'Wiping Mobile Device'=$MobileDeviceAction; + 'Delete Inbox Rule'=$DeleteInboxRuleAction;'ConvertToSharedMailbox'=$ConvertToSharedMailboxAction;'Remove License' =$RemoveLicenseAction;'SignOut From All Sessions'=$SignOutFromAllSessionsAction;} + $Result | Export-csv -Path $ExportCSV -Append -NoTypeInformation + $Variables = @("DisableUserAction","ResetPasswordToRandomAction","ResetOfficeNameAction","RemoveMobileNumberAction","RemoveGroupMembershipsAction","RemoveAdminRolesAction","RemoveAppRoleAssignmentsAction","MailBoxAvailability","HideFromAddressListAction","RemoveEmailAliasAction","MobileDeviceAction","DeleteInboxRuleAction","ConvertToSharedMailboxAction","RemoveLicenseAction","SignOutFromAllSessionsAction") + $Variables | ForEach-Object {Clear-Variable -Name $_ -ErrorAction SilentlyContinue} + } + Write-Host `nScript executed successfully -ForegroundColor Green + if(Test-Path -Path $ExportCSV) + { + Write-Host `nStatus file available in -NoNewline -Foregroundcolor Yellow; Write-Host " $ExportCSV" + $Prompt = New-Object -ComObject wscript.shell + $UserInput = $Prompt.popup("Do you want to open output file?",` 0,"Open Output File",4) + if ($UserInput -eq 6) + { + Invoke-Item "$ExportCSV" + } + } + if(Test-Path -Path $InvalidUserLogFile) + { + Write-Host `nInvalid users log file available in -NoNewline -Foregroundcolor Yellow; Write-Host " $InvalidUserLogFile" + } + if(Test-Path -Path $ErrorsLogFile) + { + Write-Host `nErrors log file available in -NoNewline -Foregroundcolor Yellow; Write-Host " $InvalidUserLogFile .You can see the reason for failed actions." + } + if($Actions -contains 15 -or $Actions -contains 2) + { + Write-Host `nPassword log file available in -NoNewline -Foregroundcolor Yellow; Write-Host " $PasswordLogFile" + } + Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green + Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n`n + Disconnect_Modules +} +. main \ No newline at end of file