You can use a powershell remove former vendor access workflow to disable a former supplier’s Microsoft 365 account, revoke active sign-in sessions, remove approved group memberships, and produce an audit record of every action. Automation handles repeatable identity changes quickly, but a person must still verify that the vendor engagement has ended, approve the request, and identify any business records or services that must remain available.
This approach supports ISO 27001 Annex A control 5.19, Information Security in Supplier Relationships, which requires defined and implemented processes for managing security risks associated with supplier products and services. For a small business owner, the practical goal is simple: no former vendor should retain a working account, an active session, or hidden access through a shared group after the engagement ends.
What can be automated and what still needs a human decision?
A PowerShell removal workflow is best for actions that are consistent, reversible where necessary, and recorded in Microsoft 365 or another identity platform. It is not a replacement for confirming the contract end date, settling retained records, or deciding whether a vendor-owned application can be safely removed.
| Task | Automate? | Reason |
|---|---|---|
| Disable a Microsoft Entra guest account | Yes | This immediately prevents new sign-ins while preserving the account for evidence and review. |
| Revoke refresh tokens and active sessions | Yes | This reduces the window in which an already authenticated vendor can continue working. |
| Remove access to named security groups | Yes | Groups such as Client-Documents-Read and Practice-Management-Users are predictable access paths. |
| Delete the vendor account permanently | Usually no | Keep it disabled until billing, document ownership, and audit questions are resolved. |
| Remove a vendor-managed application or API integration | Not without review | The application may run payroll, file transfers, backups, or a client portal. |
| Confirm all supplier obligations are complete | No | A contract owner must verify the offboarding date, retained data, and handover requirements. |
For example, a 32-person accounting firm may use an outside IT provider for Microsoft 365 administration and a separate document-scanning contractor during tax season. The office manager should approve each departure in a simple vendor register before the automated job runs. The script can disable the contractor’s guest account, but it cannot determine whether that contractor still needs to upload the final batch of scanned client records.
How does powershell remove former vendor access safely?
The safest pattern is an approved CSV queue. Each row represents one vendor identity, one change ticket, and a defined set of groups to remove. The script below refuses unapproved rows, checks that the account is a guest account, disables sign-in first, revokes sessions, then removes only the group IDs listed in the approved request.
Install the Microsoft Graph PowerShell SDK on the machine or automation worker that will run the script. Use an Entra application with certificate-based authentication rather than storing an administrator password in the script. The application needs delegated administrative approval for the equivalent application permissions: User.ReadWrite.All, GroupMember.ReadWrite.All, and Directory.ReadWrite.All.
Import-Module Microsoft.Graph.Users
Import-Module Microsoft.Graph.Groups
param(
[string]$InputCsv = "C:\VendorOffboarding\approved-vendors.csv",
[string]$TenantId = "contoso.onmicrosoft.com",
[string]$ClientId = "6b5f0f7b-91a3-4a6f-8b26-95b087fc2032",
[string]$CertificateThumbprint = "A12B34C56D78E90F12A34B56C78D90E12F34A567",
[switch]$Execute
)
$ErrorActionPreference = "Stop"
$results = @()
Connect-MgGraph -TenantId $TenantId `
-ClientId $ClientId `
-CertificateThumbprint $CertificateThumbprint `
-NoWelcome
foreach ($record in Import-Csv $InputCsv) {
$timestamp = Get-Date -Format "s"
try {
if ($record.Approved -ne "YES") {
throw "Row is not approved."
}
if ([datetime]$record.EndDate -gt (Get-Date)) {
throw "End date has not yet passed."
}
$user = Get-MgUser -UserId $record.UserPrincipalName `
-Property "id,displayName,userType,accountEnabled"
if ($user.UserType -ne "Guest") {
throw "Account is not a guest account; require manual review."
}
if (-not $Execute) {
$results += [pscustomobject]@{
Time = $timestamp; Ticket = $record.Ticket
User = $record.UserPrincipalName; Status = "PLAN ONLY"
Detail = "Would disable account, revoke sessions, and remove listed groups."
}
continue
}
Update-MgUser -UserId $user.Id -AccountEnabled:$false
Revoke-MgUserSignInSession -UserId $user.Id
foreach ($groupId in ($record.GroupIds -split ";")) {
if ($groupId.Trim()) {
try {
Remove-MgGroupMemberByRef `
-GroupId $groupId.Trim() `
-DirectoryObjectId $user.Id
$results += [pscustomobject]@{
Time = $timestamp; Ticket = $record.Ticket
User = $record.UserPrincipalName; Status = "REMOVED"
Detail = "Removed from group $($groupId.Trim())"
}
}
catch {
$results += [pscustomobject]@{
Time = $timestamp; Ticket = $record.Ticket
User = $record.UserPrincipalName; Status = "GROUP REVIEW"
Detail = "Could not remove group $($groupId.Trim()): $($_.Exception.Message)"
}
}
}
}
$results += [pscustomobject]@{
Time = $timestamp; Ticket = $record.Ticket
User = $record.UserPrincipalName; Status = "DISABLED"
Detail = "Account disabled and sign-in sessions revoked."
}
}
catch {
$results += [pscustomobject]@{
Time = $timestamp; Ticket = $record.Ticket
User = $record.UserPrincipalName; Status = "FAILED"
Detail = $_.Exception.Message
}
}
}
$results | Export-Csv "C:\VendorOffboarding\logs\vendor-offboarding-$((Get-Date).ToString('yyyyMMdd-HHmmss')).csv" -NoTypeInformation
Disconnect-MgGraph
Use a CSV like this, with real Entra group object IDs rather than group names. Group IDs prevent accidental removal from the wrong group when names are similar.
Ticket,Approved,EndDate,UserPrincipalName,GroupIds
CHG-2026-041,YES,2026-07-18,alex@vendor-example.com,"2df4d9b8-1234-45ab-9999-1b7b2dd34aa1;9a7ce284-5678-40cd-8888-3d2f11cae902"
Run the script once without -Execute to create a plan-only log. Review that log against the approval ticket, then run it with -Execute. This two-step PowerShell vendor offboarding process is easier to control than allowing an automated task to act on every row immediately.
How should the job run on a schedule or through CI/CD?
For most SMBs, an Azure Automation runbook or a locked-down Windows automation server is more practical than running the job from an owner’s laptop. Store the script in a private Git repository, require a second person to approve changes, and schedule the job daily after normal business hours. Use a certificate stored in the automation account or service account profile; never place a Global Administrator password in a CSV file, script, or shared drive.
$trigger = New-ScheduledTaskTrigger -Daily -At 6:00pm
$action = New-ScheduledTaskAction `
-Execute "pwsh.exe" `
-Argument "-NoProfile -File C:\VendorOffboarding\Remove-VendorAccess.ps1 -Execute"
Register-ScheduledTask `
-TaskName "Vendor Access Offboarding" `
-Action $action `
-Trigger $trigger `
-Description "Runs approved Microsoft 365 vendor offboarding requests."
A 24-person legal practice, for instance, can have its operations manager place approved vendor departures into the queue before 5:00 p.m. The scheduled task runs at 6:00 p.m., after staff have finished client work, and writes an immutable copy of the result log to a restricted SharePoint library or Azure storage account.
What monitoring and alerting hooks should be added?
The automation should create evidence, not merely make changes. Send a daily summary to the business owner and ensure failures become visible before the former supplier has another opportunity to sign in.
- Send an email or Teams alert whenever a row has a
FAILEDorGROUP REVIEWstatus. - Retain the CSV result log with the ticket number, date, account name, actions taken, and error details.
- Review Entra sign-in logs for the disabled account for seven days after offboarding.
- Create an alert for attempts to sign in to a disabled guest account, especially from a new country or unfamiliar IP address.
- Review the vendor register monthly for accounts with an end date but no completed offboarding log.
This monitoring layer turns a vendor-access removal script into evidence that your ISO 27001 supplier relationship process is working, rather than an undocumented technical action.
What happens when the automated vendor offboarding process fails?
Failure handling matters because partial removal can create false confidence. The script disables the account before attempting group removal, so a group error does not leave the vendor able to sign in. However, the owner still needs a clear response for each failure type.
| Failure mode | Immediate response | Follow-up |
|---|---|---|
| Vendor account cannot be found | Mark the ticket for review; do not assume access is gone. | Search for alternate email addresses, personal accounts, and shared credentials. |
| Account is a Member rather than Guest | Stop automation and require management approval. | Confirm whether the vendor was incorrectly created as an internal user. |
| Group removal fails | Keep the account disabled and alert the owner. | Check nested groups, Teams membership, SharePoint direct permissions, and application roles. |
| Certificate authentication fails | Do not switch to a shared administrator password. | Renew the certificate, verify app permissions, and rerun the approved row. |
| Critical vendor service stops working | Do not re-enable the account automatically. | Use a time-limited, documented exception approved by the service owner. |
Your next step is to create a one-page vendor offboarding register, add your current suppliers and their end dates, and test the script in plan-only mode with one inactive guest account.