Joiners, movers and leavers are the most predictable events in identity management, and still the ones most often handled by tickets, spreadsheets and good intentions. Here is how to build a complete, HR-driven lifecycle with the native identity governance features of Microsoft Entra ID.
The challenge: identity lifecycle by ticket
Level 200Every organisation knows exactly three things about its employees in advance: when they start, when their role changes and when they leave. HR records these dates weeks or months before they become effective. And yet, in most environments I assess, IT learns about them through a ticket that arrives late, incomplete or not at all.
The consequences are familiar to anyone who has worked on a service desk:
- Day one without access. The new hire sits at a desk with a laptop and no working account, or with an account but without the groups, Teams and licences their role needs. The manager phones the helpdesk, the helpdesk escalates, and two days of productivity disappear.
- Movers who collect permissions. When someone changes department, they receive access to their new resources quickly, because they ask for it. Nobody asks to have their old access removed. After a few internal moves, a long-standing employee often has more access than anyone in the organisation, and nobody can explain why.
- Leavers who never really leave. The account is disabled a few days after the last working day, if the ticket arrived. Sessions and refresh tokens remain valid until they expire or are revoked. Group memberships, Teams ownerships and licences stay in place. Months later, the account is found in an audit, still licensed, still owner of a team, sometimes still able to sign in.
Orphaned accounts are not a theoretical risk. A former employee's account with a known password, an old service account named after a person, or an account that was "only disabled" but still holds a valid refresh token on a forgotten device are exactly the kind of foothold that attackers, and occasionally disgruntled former staff, use. On top of that, every licence assigned to a leaver is money spent on nothing.
Microsoft Entra ID Governance addresses this with three building blocks that work together: HR-driven inbound provisioning creates and updates accounts from the HR system, Lifecycle Workflows run scheduled or on-demand tasks around joiner, mover and leaver events, and entitlement management grants and removes access through access packages. This article focuses on Lifecycle Workflows, but it also shows where the other two fit, because one of the most common misunderstandings is what Lifecycle Workflows actually do and what they do not.
Lifecycle Workflows do not create user accounts. Account creation and attribute updates are the job of inbound provisioning (Workday, SAP SuccessFactors, or the API-driven provisioning endpoint for any other HR system), of Microsoft Entra Connect or Cloud Sync for Active Directory, or of your own scripts. Lifecycle Workflows take over once the account exists and automate everything that has to happen around the dates in its attributes: enabling, notifying, granting group and Teams membership, assigning licences, issuing a Temporary Access Pass, removing access, disabling and deleting.
A realistic case: 1,200 employees and a helpdesk queue
Level 200 to 300Northwind Logistics AG is a Swiss logistics company with about 1,200 employees across six sites, a high share of seasonal and temporary staff, and an on-premises Active Directory synchronised to Microsoft Entra ID with Microsoft Entra Connect Sync. HR uses a cloud-based HR system that is not Workday or SuccessFactors. Licensing is Microsoft 365 E3 for office staff and F3 for warehouse staff.
The joiner-mover-leaver process before the project looked like this:
An internal audit in preparation for an ISO 27001 recertification found the following:
| Finding | Detail |
|---|---|
| Enabled accounts of former employees | 143 enabled accounts belonged to people who had left, 61 of them more than six months earlier. 38 still had a Microsoft 365 licence assigned. |
| Delayed disablement | For leavers in the last 12 months, the median time between the last working day and account disablement was nine days. Sessions were never revoked explicitly. |
| Accumulated access | Employees with three or more internal moves had on average 2.4 times as many group memberships as colleagues in the same role who had never moved. |
| Seasonal staff | Seasonal warehouse staff were created as "permanent" accounts and reactivated each season, including their passwords from the previous year. |
| Missing manager attribute | About 20 percent of accounts had no manager set, so there was nobody to notify or to ask for approval. |
Nothing on this list is unusual. The root cause is always the same: the lifecycle depends on a chain of manual hand-offs between HR, line managers and IT, and each hand-off loses information or time. The target for the project was simple to state: HR data becomes the single trigger, and everything that can be derived from that data happens automatically and on time.
The rest of this article walks through how Northwind implemented that, step by step, and where the limits of the native tools are.
Building the automated lifecycle
Level 300 to 400The architecture: who does what
Before configuring a single workflow, it helps to be very clear about responsibilities. In the target design, every component has exactly one job:
The important design principle: the workflows never decide who someone is. They only react to attributes. If the hire date, leave date, department or manager is wrong, the workflow does the wrong thing at exactly the right time. Data quality is therefore the first deliverable of the project, not an afterthought.
Prerequisites, licensing and tenant settings
| Item | Requirement |
|---|---|
| Licensing | Microsoft Entra ID Governance (add-on to Entra ID P1 or P2) or Microsoft Entra Suite, for the users processed by workflows. |
| Admin role | Lifecycle Workflows Administrator (least privilege). Creating custom task extensions additionally requires permissions on the Azure Logic App. |
| Graph permissions for automation | LifecycleWorkflows.ReadWrite.All to manage workflows; User-LifeCycleInfo.ReadWrite.All to write employeeLeaveDateTime; User.ReadWrite.All for other user attributes. |
| Temporary Access Pass | TAP must be enabled in the authentication methods policy if you want workflows to generate one; the lifetime set in the task must be within the range the policy allows. |
| Manager attribute | Required by every task that emails the manager or sends the TAP to the manager. |
| Usage location | Required for the "Assign licenses to user" task, as for any direct licence assignment. |
| Hybrid users | For enable, disable and delete tasks against AD-synced accounts: Microsoft Entra provisioning agent (version 1.1.1586.0 or later), suitable gMSA permissions and, for deletion, the AD Recycle Bin. |
Two tenant-wide settings influence every workflow: how often scheduled workflows are evaluated, and how emails look and who sends them. The schedule interval defaults to three hours and can be set between 1 and 24 hours. For onboarding scenarios with tight timing (a TAP that must be valid at 07:00), a shorter interval is worth it.
PowerShell (Graph): tenant-wide Lifecycle Workflows settings
Connect-MgGraph -Scopes "LifecycleWorkflows.ReadWrite.All"
# Current settings
Get-MgIdentityGovernanceLifecycleWorkflowSetting | Format-List
# Evaluate scheduled workflows every hour, send emails from a verified domain with company branding
Update-MgIdentityGovernanceLifecycleWorkflowSetting `
-WorkflowScheduleIntervalInHours 1 `
-EmailSettings @{ senderDomain = "northwind.example"; useCompanyBranding = $true }
- Workflow schedule
- 1 hourAllowed range 1 to 24 hours, default 3
- Email domain
- northwind.exampleA verified domain of your tenant
- Use company branding banner logo
- OnUses the banner logo from company branding
The data foundation: HR attributes
Time-based workflows key off three user properties:
| Property | Typical source | Used for |
|---|---|---|
employeeHireDate | HR start date | Pre-hire, day-one and post-onboarding workflows |
employeeLeaveDateTime | HR termination date | Pre-offboarding, last-day and post-offboarding workflows |
createdDateTime | Set by Entra ID | Workflows relative to account creation. For synced users this is the date the account arrived in Entra ID, not its creation in AD. |
Scoping rules typically use department, employeeType, companyName, country or extension attributes. Agree with HR on a stable, controlled vocabulary for these values; a scope rule on department eq 'Logistics' silently stops matching the day someone writes "Logistik".
Both date properties are full date-time values in UTC. A hire date of 2026-11-02T00:00:00Z is 01:00 in Zurich during winter time. If a workflow generates a TAP with the hire date as start time and an eight-hour lifetime, the pass expires at 09:00 local time. Agree on a convention with HR (for example 07:00 local time converted to UTC for hire dates and 17:00 local time for leave dates) and apply it consistently in the provisioning mapping.
Getting the dates into Entra ID
For cloud-only users or for quick tests, you can set the values with Microsoft Graph. Note that employeeLeaveDateTime requires its own permission:
PowerShell (Graph): set hire and leave dates
Connect-MgGraph -Scopes "User.ReadWrite.All","User-LifeCycleInfo.ReadWrite.All"
$userId = "anna.keller@northwind.example"
# Hire date: 2 November 2026, 07:00 Zurich (CET = UTC+1)
Update-MgUser -UserId $userId -EmployeeHireDate ([datetime]"2026-11-02T06:00:00Z")
# Leave date via a direct Graph request
Invoke-MgGraphRequest -Method PATCH -Uri "https://graph.microsoft.com/v1.0/users/$userId" `
-Body @{ employeeLeaveDateTime = "2027-03-31T16:00:00Z" }
Get-MgUser -UserId $userId -Property displayName,employeeHireDate,employeeLeaveDateTime,department,employeeType |
Select-Object DisplayName, EmployeeHireDate, EmployeeLeaveDateTime, Department, EmployeeType
In production, these values come from the HR system. Northwind's HR platform has no native Entra connector, so the team used API-driven inbound provisioning: a scheduled job exports changed employee records from HR and posts them as a SCIM bulk request to the provisioning endpoint. The provisioning service then creates or updates the accounts in Active Directory (through the provisioning agent) or directly in Entra ID, including the date attributes.
PowerShell: post an HR record to the API-driven provisioning endpoint
# App permission required: SynchronizationData-User.Upload
# $spId and $jobId come from the API-driven provisioning app (Enterprise application > Provisioning)
$uri = "https://graph.microsoft.com/v1.0/servicePrincipals/$spId/synchronization/jobs/$jobId/bulkUpload"
$payload = @{
schemas = @("urn:ietf:params:scim:api:messages:2.0:BulkRequest")
Operations = @(
@{
method = "POST"
bulkId = "e-10482"
path = "/Users"
data = @{
schemas = @("urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User")
externalId = "10482"
userName = "anna.keller@northwind.example"
name = @{ givenName = "Anna"; familyName = "Keller" }
active = $false # created disabled; see joiner section
title = "Dispatcher"
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" = @{
employeeNumber = "10482"
department = "Logistics"
manager = @{ value = "10017" }
}
}
}
)
failOnErrors = $null
} | ConvertTo-Json -Depth 10
Invoke-MgGraphRequest -Method POST -Uri $uri -Body $payload -ContentType "application/scim+json"
Hire and leave dates are not part of the standard SCIM user schema. You add them as custom schema attributes in the provisioning app and map them to employeeHireDate and employeeLeaveDateTime (or, when provisioning into Active Directory, to the AD attributes you then synchronise). The attribute mapping is where the time zone convention from above is implemented.
If accounts are mastered in Active Directory, the dates must travel through your synchronisation engine. Both Microsoft Entra Cloud Sync and Connect Sync can map AD attributes to employeeHireDate and employeeLeaveDateTime; Microsoft's article "How to synchronize attributes for Lifecycle workflows" describes the mappings. Changes are only visible to Lifecycle Workflows after the next sync cycle.
Data quality checks before the first workflow
PowerShell (Graph): find users that would break workflows
Connect-MgGraph -Scopes "User.Read.All"
$users = Get-MgUser -All -Filter "userType eq 'Member' and accountEnabled eq true" `
-Property id,displayName,userPrincipalName,department,employeeType,employeeHireDate,usageLocation,mail `
-ExpandProperty manager
$issues = $users | ForEach-Object {
[PSCustomObject]@{
User = $_.UserPrincipalName
NoManager = -not $_.Manager.Id
NoHireDate = -not $_.EmployeeHireDate
NoDepartment = [string]::IsNullOrWhiteSpace($_.Department)
NoUsageLoc = [string]::IsNullOrWhiteSpace($_.UsageLocation)
NoMail = [string]::IsNullOrWhiteSpace($_.Mail)
}
} | Where-Object { $_.NoManager -or $_.NoHireDate -or $_.NoDepartment -or $_.NoUsageLoc -or $_.NoMail }
$issues | Export-Csv .\LCW-DataQuality.csv -NoTypeInformation -Encoding UTF8
"{0} of {1} users have at least one gap" -f $issues.Count, $users.Count
Anatomy of a workflow
Every workflow consists of four parts: a category (joiner, mover or leaver), execution conditions (a trigger plus a scope), an ordered list of tasks, and the scheduling switch. Microsoft ships templates for the common scenarios, which are a good starting point in the portal.
| Trigger type | Fires when | Typical use |
|---|---|---|
| Time-based attribute | employeeHireDate, employeeLeaveDateTime or createdDateTime plus an offset between -180 and +180 days is reached | Pre-hire, day one, pre-offboarding, last day, post-offboarding |
| Attribute change | One of the selected attributes changes on a user in scope | Movers (department, job title, cost centre) |
| Group membership change | A user is added to or removed from a specified group | Role changes managed through groups, sabbaticals |
| On-demand | An admin runs the workflow for selected users | Immediate termination, testing, catching up |
The scope is a rule in the familiar Entra filter syntax, for example (department eq 'Logistics') and (employeeType eq 'Employee'). Tasks run in the order you define. Each task has a continueOnError flag that decides whether a failure stops the remaining tasks, which is an important design decision for leaver workflows: you do not want a failed email to prevent the account from being disabled.
Each built-in task has a fixed taskDefinitionId. Rather than hardcoding these GUIDs in scripts, I look them up at runtime:
PowerShell (Graph): list task definitions
Get-MgIdentityGovernanceLifecycleWorkflowTaskDefinition -All |
Sort-Object Category, DisplayName |
Format-Table DisplayName, Category, Id -AutoSize
# Helper used in the following examples
$td = @{}
Get-MgIdentityGovernanceLifecycleWorkflowTaskDefinition -All | ForEach-Object { $td[$_.DisplayName] = $_.Id }
The display names returned by your tenant are the keys for the $td lookup table below. If a name differs slightly in your tenant (Microsoft occasionally adjusts wording), check the output of the first command. For reference, these are the IDs of the tasks used in this article as documented by Microsoft:
| Task | taskDefinitionId | Category |
|---|---|---|
| Generate Temporary Access Pass and send via email to user's manager | 1b555e50-7f65-41d5-b514-5894a026d10d | Joiner |
| Send welcome email to new hire | 70b29d51-b59a-4773-9280-8841dfd3f2ea | Joiner |
| Send onboarding reminder email | 3C860712-2D37-42A4-928F-5C93935D26A1 | Joiner |
| Enable user account | 6fc52c9d-398b-4305-9763-15f42c1676fc | Joiner, Leaver |
| Assign licenses to user | 683c87a4-2ad4-420b-97d4-220d90afcd24 | Joiner, Mover |
| Add user to groups | 22085229-5809-45e8-97fd-270d28d66910 | Joiner, Leaver, Mover |
| Add user to teams | e440ed8d-25a1-4618-84ce-091ed5be5594 | Joiner, Leaver, Mover |
| Request user access package assignment | c1ec1e76-f374-4375-aaa6-0bb6bd4c60be | Joiner, Mover |
| Send email to notify manager of user move | aab41899-9972-422a-9d97-f626014578b7 | Mover |
| Remove all access package assignments for user | 42ae2956-193d-4f39-be06-691b8ac4fa1d | Leaver, Mover |
| Send email to manager before user's last day | 52853a3e-f4e5-4eb8-bb24-1ac09a1da935 | Leaver |
| Disable user account | 1dfdfcc7-52fa-4c2e-bf3a-e3919cc12950 | Leaver |
| Revoke all refresh tokens for user | 509589a4-0466-4471-829e-49c5e502bdee | Leaver, Mover |
| Remove users from all groups | b3a31406-2a15-4c9a-b25b-a658fa5f07fc | Leaver |
| Remove user from all teams | 81f7b200-2816-4b3b-8c5d-dc556f07b024 | Leaver |
| Cancel all pending access package assignment requests for user | 498770d9-bab7-4e4c-b73d-5ded82a1d0b3 | Leaver |
| Remove all license assignments from user | 8fa97d28-3e52-4985-b3a9-a1126f9b8b4e | Leaver |
| Delete user | 8d18588d-9ad3-4c0f-99d0-ec215f0e3dff | Leaver |
Joiner workflows
Northwind split onboarding into two workflows, because the tasks belong to different moments:
The idea is that the account exists early, receives its licences and memberships before day one so that mailbox provisioning and Teams membership have completed, and is only enabled on the first day. Northwind's provisioning creates new accounts in a disabled state. One detail is easy to miss here: the attribute mapping that controls the account state (accountEnabled for cloud users, accountDisabled for AD) must be set to apply only during object creation. Otherwise the next provisioning cycle disables the account again right after the workflow enabled it. The TAP is generated on day one as well, because the TAP task uses the hire date as the start time and requires that the user has no other authentication methods or sign-ins yet.
PowerShell (Graph): pre-hire workflow
Connect-MgGraph -Scopes "LifecycleWorkflows.ReadWrite.All"
$preHire = @{
category = "joiner"
displayName = "Joiner | Logistics | Pre-hire (-7d)"
description = "Licences, groups and Teams one week before start; reminder to manager"
isEnabled = $true
isSchedulingEnabled = $false # enable after testing
executionConditions = @{
"@odata.type" = "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions"
scope = @{
"@odata.type" = "#microsoft.graph.identityGovernance.ruleBasedSubjectSet"
rule = "(department eq 'Logistics') and (employeeType eq 'Employee')"
}
trigger = @{
"@odata.type" = "#microsoft.graph.identityGovernance.timeBasedAttributeTrigger"
timeBasedAttribute = "employeeHireDate"
offsetInDays = -7
}
}
tasks = @(
@{
taskDefinitionId = "683c87a4-2ad4-420b-97d4-220d90afcd24" # Assign licenses to user
displayName = "Assign Microsoft 365 F3"
isEnabled = $true; continueOnError = $true
arguments = @(@{ name = "licenses"; value = "<skuId-of-SPE_F1>" })
},
@{
taskDefinitionId = "22085229-5809-45e8-97fd-270d28d66910" # Add user to groups
displayName = "Add to Logistics security groups"
isEnabled = $true; continueOnError = $true
arguments = @(@{ name = "groupID"; value = "<group-id-SG-Logistics-All>" })
},
@{
taskDefinitionId = "e440ed8d-25a1-4618-84ce-091ed5be5594" # Add user to teams
displayName = "Add to team Logistics Operations"
isEnabled = $true; continueOnError = $true
arguments = @(@{ name = "teamID"; value = "<team-group-id>" })
},
@{
taskDefinitionId = "3C860712-2D37-42A4-928F-5C93935D26A1" # Send onboarding reminder email
displayName = "Remind manager"
isEnabled = $true; continueOnError = $true
arguments = @()
}
)
}
$wfPreHire = New-MgIdentityGovernanceLifecycleWorkflow -BodyParameter $preHire
$wfPreHire.Id
The SKU ID for the licence task is the GUID of the product, not its string ID. You get it with Get-MgSubscribedSku | Select-Object SkuPartNumber, SkuId.
PowerShell (Graph): day-one workflow
$dayOne = @{
category = "joiner"
displayName = "Joiner | Logistics | Day one"
description = "Enable account, TAP to manager, welcome email"
isEnabled = $true
isSchedulingEnabled = $false
executionConditions = @{
"@odata.type" = "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions"
scope = @{
"@odata.type" = "#microsoft.graph.identityGovernance.ruleBasedSubjectSet"
rule = "(department eq 'Logistics') and (employeeType eq 'Employee')"
}
trigger = @{
"@odata.type" = "#microsoft.graph.identityGovernance.timeBasedAttributeTrigger"
timeBasedAttribute = "employeeHireDate"
offsetInDays = 0
}
}
tasks = @(
@{
taskDefinitionId = "6fc52c9d-398b-4305-9763-15f42c1676fc" # Enable user account
displayName = "Enable account (also in AD)"
isEnabled = $true; continueOnError = $false
arguments = @(@{ name = "enableOnPremisesAccount"; value = "true" })
},
@{
taskDefinitionId = "1b555e50-7f65-41d5-b514-5894a026d10d" # Generate TAP and send to manager
displayName = "TAP for first sign-in"
isEnabled = $true; continueOnError = $true
arguments = @(
@{ name = "tapLifetimeMinutes"; value = "480" },
@{ name = "tapIsUsableOnce"; value = "false" }
)
},
@{
taskDefinitionId = "70b29d51-b59a-4773-9280-8841dfd3f2ea" # Send welcome email
displayName = "Welcome email"
isEnabled = $true; continueOnError = $true
arguments = @(
@{ name = "customSubject"; value = "Welcome to Northwind, {{user.givenName}}" },
@{ name = "customBody"; value = "Your manager {{managerDisplayName}} will help you with your first sign-in." }
)
}
)
}
$wfDayOne = New-MgIdentityGovernanceLifecycleWorkflow -BodyParameter $dayOne
Custom email text supports dynamic attributes such as {{user.givenName}}, {{user.department}}, {{managerDisplayName}} and, in the TAP task only, {{temporaryAccessPass}}. Microsoft introduced the {{user.graphPropertyName}} format for newly customised emails; older workflows keep their previous placeholder format. The welcome email goes to the user's mail attribute, which only works if the mailbox is reachable, so many organisations send it to the manager or a private address held by HR via a custom extension.
The group tasks support Microsoft 365 groups and cloud-only security groups. Mail-enabled security groups, distribution lists, dynamic groups and role-assignable groups are not supported, and neither are groups synchronised from AD. For licences, there are two clean options: the "Assign licenses to user" task, or a cloud security group with group-based licensing that the workflow adds the user to. Northwind used the group approach, because removing the user from the group later removes the licence as well.
- Trigger type
- Time based attribute
- Days from event / Event timing
- 0 / On
- Event user attribute
- employeeHireDate
- Scope type / Rule
- Rule based(department eq 'Logistics') and (employeeType eq 'Employee')
- Tasks
- 1 Enable user account2 Generate TAP and send email, 3 Send welcome email
- Schedule workflow
- OffSwitched on after on-demand tests
Mover workflows
Movers are where most organisations give up, because "what changes" depends on where someone comes from and where they go. The trick is to stop thinking about the old access at all and let access packages carry the role-based access. Northwind defined one access package per department that contains the department's groups, Teams and SharePoint sites. The mover workflow for each target department then does three things:
- Schedule removal of all existing access package assignments (Microsoft's mover templates default to 15 days, giving the person time to hand over).
- Request the access package of the new department.
- Notify the manager about the move.
PowerShell (Graph): mover workflow for people moving into Sales
$moverSales = @{
category = "mover"
displayName = "Mover | into Sales"
description = "Department changed to Sales: rotate access packages, notify manager"
isEnabled = $true
isSchedulingEnabled = $false
executionConditions = @{
"@odata.type" = "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions"
scope = @{
"@odata.type" = "#microsoft.graph.identityGovernance.ruleBasedSubjectSet"
rule = "(department eq 'Sales') and (employeeType eq 'Employee')"
}
trigger = @{
"@odata.type" = "#microsoft.graph.identityGovernance.attributeChangeTrigger"
triggerAttributes = @(@{ name = "department" })
}
}
tasks = @(
@{
taskDefinitionId = "42ae2956-193d-4f39-be06-691b8ac4fa1d" # Remove all access package assignments
displayName = "Remove previous role access in 15 days"
isEnabled = $true; continueOnError = $true
arguments = @(@{ name = "daysUntilExpiration"; value = "15" })
},
@{
taskDefinitionId = "c1ec1e76-f374-4375-aaa6-0bb6bd4c60be" # Request user access package assignment
displayName = "Request Sales access package"
isEnabled = $true; continueOnError = $true
arguments = @(
@{ name = "accessPackageId"; value = "<access-package-id-Sales>" },
@{ name = "assignmentPolicyId"; value = "<policy-id-Sales-auto>" }
)
},
@{
taskDefinitionId = "aab41899-9972-422a-9d97-f626014578b7" # Notify manager of user move
displayName = "Notify manager"
isEnabled = $true; continueOnError = $true
arguments = @()
}
)
}
New-MgIdentityGovernanceLifecycleWorkflow -BodyParameter $moverSales
The removal task schedules the expiry of the assignments that exist when it runs. Running it before the request task ensures that the new Sales package is not caught by the scheduled removal. If the access package policy requires approval, the request task respects that only when the entitlement management setting "Enforce policy approval setting for admin direct assignments" is enabled; otherwise the assignment is created directly.
With six departments, Northwind ended up with six mover workflows that share the same structure. That sounds like duplication, but it keeps each workflow simple, readable and testable, and the PowerShell above turns it into a loop over a small configuration table.
Leaver workflows
The leaver side has the highest security value, and it is also where task order and error handling matter most. Northwind's design uses three scheduled workflows plus one on-demand workflow:
PowerShell (Graph): last-day workflow
$lastDay = @{
category = "leaver"
displayName = "Leaver | All employees | Last day"
description = "Disable, revoke sessions, remove memberships and access packages"
isEnabled = $true
isSchedulingEnabled = $false
executionConditions = @{
"@odata.type" = "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions"
scope = @{
"@odata.type" = "#microsoft.graph.identityGovernance.ruleBasedSubjectSet"
rule = "(employeeType eq 'Employee') or (employeeType eq 'Contractor')"
}
trigger = @{
"@odata.type" = "#microsoft.graph.identityGovernance.timeBasedAttributeTrigger"
timeBasedAttribute = "employeeLeaveDateTime"
offsetInDays = 0
}
}
tasks = @(
@{ taskDefinitionId = "1dfdfcc7-52fa-4c2e-bf3a-e3919cc12950"; displayName = "Disable account (Entra and AD)"
isEnabled = $true; continueOnError = $true
arguments = @(@{ name = "disableOnPremisesAccount"; value = "true" }) },
@{ taskDefinitionId = "509589a4-0466-4471-829e-49c5e502bdee"; displayName = "Revoke refresh tokens"
isEnabled = $true; continueOnError = $true; arguments = @() },
@{ taskDefinitionId = "498770d9-bab7-4e4c-b73d-5ded82a1d0b3"; displayName = "Cancel pending access requests"
isEnabled = $true; continueOnError = $true; arguments = @() },
@{ taskDefinitionId = "42ae2956-193d-4f39-be06-691b8ac4fa1d"; displayName = "Remove all access packages"
isEnabled = $true; continueOnError = $true
arguments = @(@{ name = "daysUntilExpiration"; value = "0" }) }, # immediate removal; confirm in an on-demand test run
@{ taskDefinitionId = "b3a31406-2a15-4c9a-b25b-a658fa5f07fc"; displayName = "Remove from all groups"
isEnabled = $true; continueOnError = $true; arguments = @() },
@{ taskDefinitionId = "81f7b200-2816-4b3b-8c5d-dc556f07b024"; displayName = "Remove from all Teams"
isEnabled = $true; continueOnError = $true; arguments = @() }
)
}
New-MgIdentityGovernanceLifecycleWorkflow -BodyParameter $lastDay
Every task in the leaver workflow has continueOnError = $true. That is deliberate: if removing a team membership fails because the user is the last owner, the remaining tasks must still run. Failures appear in the run history and are handled by a person, but the account is already disabled and its sessions are revoked.
What "disabled" really means
Disabling the account prevents new sign-ins and new tokens. Existing access tokens remain valid until they expire, typically within about an hour, unless the application supports Continuous Access Evaluation (Exchange Online, SharePoint Online and Teams do), in which case the disable and the token revocation are enforced in near real time. The "Revoke all refresh tokens for user" task invalidates refresh tokens and browser session cookies, so that cached sessions on forgotten devices cannot be used to get new access tokens. Both tasks together are the minimum for a clean leaver.
The gaps the group tasks leave
- Dynamic groups are not touched by "Remove users from all groups". A disabled user stays in every dynamic group whose rule still matches. Add
(user.accountEnabled -eq true)to the rules of dynamic groups that grant access. - Distribution lists, mail-enabled security groups and AD-synced groups are also out of scope for the group tasks. Handle them in AD (for synced groups) or through a custom extension.
- Role-assignable groups and directory roles: users with Entra role assignments, or members or owners of role-assignable groups, are not supported by the enable, disable and delete tasks. Admin accounts need their own offboarding process, ideally with PIM so that no permanent assignments exist in the first place.
Post-offboarding and data retention
The post-offboarding workflow (leave date + 30 days) removes direct licence assignments and deletes the account. Before you enable it, make sure your retention design is in place, because licence removal and deletion have consequences for data:
| Data | What happens on deletion | What to configure beforehand |
|---|---|---|
| Mailbox | Soft-deleted with the user and recoverable for 30 days. If a hold or retention policy applies, it becomes an inactive mailbox and is retained. | A Microsoft Purview retention policy covering Exchange mailboxes, or convert to a shared mailbox before deletion if colleagues need ongoing access. |
| OneDrive | Retained for the period set in OrphanedPersonalSitesRetentionPeriod (default 30 days, up to 3,650); the manager receives access if access delegation is enabled. | Set-SPOTenant -OrphanedPersonalSitesRetentionPeriod 90 and a retention policy for OneDrive if needed. |
| Entra user object | Soft-deleted, restorable for 30 days, then permanently removed. | Nothing, but document the restore procedure. |
PowerShell (Graph): post-offboarding workflow
$postOff = @{
category = "leaver"
displayName = "Leaver | All employees | +30 days"
description = "Remove direct licences and delete account"
isEnabled = $true
isSchedulingEnabled = $false
executionConditions = @{
"@odata.type" = "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions"
scope = @{
"@odata.type" = "#microsoft.graph.identityGovernance.ruleBasedSubjectSet"
rule = "(employeeType eq 'Employee') or (employeeType eq 'Contractor')"
}
trigger = @{
"@odata.type" = "#microsoft.graph.identityGovernance.timeBasedAttributeTrigger"
timeBasedAttribute = "employeeLeaveDateTime"
offsetInDays = 30
}
}
tasks = @(
@{ taskDefinitionId = "8fa97d28-3e52-4985-b3a9-a1126f9b8b4e"; displayName = "Remove all direct licences"
isEnabled = $true; continueOnError = $true; arguments = @() },
@{ taskDefinitionId = "8d18588d-9ad3-4c0f-99d0-ec215f0e3dff"; displayName = "Delete account (Entra and AD)"
isEnabled = $true; continueOnError = $false
arguments = @(@{ name = "deleteOnPremisesAccount"; value = "true" }) }
)
}
New-MgIdentityGovernanceLifecycleWorkflow -BodyParameter $postOff
Immediate termination
Not every departure is planned. For terminations that must take effect now, Northwind created a copy of the last-day workflow without a schedule and runs it on demand. HR and security operations have a documented procedure that one person with the Lifecycle Workflows Administrator role can execute in under a minute:
PowerShell (Graph): run a workflow on demand for one user
$wfId = "<id-of-Leaver-Immediate-termination>"
$userId = (Get-MgUser -UserId "former.employee@northwind.example").Id
Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/v1.0/identityGovernance/lifecycleWorkflows/workflows/$wfId/activate" `
-Body @{ subjects = @(@{ id = $userId }) }
- Select users
- former.employee@northwind.example
- Tasks that will run
- Disable user account, Revoke all refresh tokensCancel pending requests, Remove access packages, Remove from all groups and Teams
- Action
- Run workflow
Users synchronised from Active Directory
Most Lifecycle Workflow tasks work for synchronised users out of the box: emails, TAP, cloud group and Teams membership, licences, access packages and token revocation all operate on cloud objects. The exceptions are the tasks that change the account itself. Enabling, disabling and deleting a synchronised account in Entra ID alone would be overwritten by the next sync, so these tasks need to act in Active Directory. That requires:
- The Microsoft Entra provisioning agent, version 1.1.1586.0 or later, installed with the extension configuration "HR-driven provisioning / Microsoft Entra Connect Sync". You do not need a Cloud Sync configuration, and it coexists with Connect Sync.
- A gMSA for the agent with permissions to enable, disable and delete user objects in the relevant OUs.
- The Active Directory Recycle Bin enabled, if you want to use the delete task.
- The task argument
enableOnPremisesAccount,disableOnPremisesAccountordeleteOnPremisesAccountset totrue, as in the examples above.
PowerShell (AD): check prerequisites on-premises
# Recycle Bin enabled?
Get-ADOptionalFeature -Filter 'Name -like "Recycle Bin Feature"' | Select-Object Name, EnabledScopes
# Enable it (irreversible, forest-wide)
Enable-ADOptionalFeature -Identity "Recycle Bin Feature" -Scope ForestOrConfigurationSet -Target "northwind.local"
# Provisioning agent version on the agent server
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" |
Where-Object DisplayName -like "*Provisioning Agent*" | Select-Object DisplayName, DisplayVersion
Changes made in AD flow back to Entra ID with the next synchronisation cycle, so there is a short window in which the Entra object still shows as enabled. That is why the token revocation task matters for hybrid users as well.
Custom task extensions for everything else
Built-in tasks cover identity and access. Real offboarding also includes a ticket for the laptop, an out-of-office message, mailbox delegation, a payroll system, a badge system. Custom task extensions call an Azure Logic App from a workflow and pass the user's context. The Logic App can then do anything you can automate, including starting an Azure Automation runbook for Exchange Online PowerShell tasks.
Two behaviours are available: launch and continue (fire and forget) and launch and wait, where the workflow pauses until the Logic App calls back with success or failure, or a timeout is reached. Use "wait" when later tasks depend on the result.
- Task behavior
- Launch and waitWorkflow continues when the Logic App reports back
- Response
- Allow response from any appOr restrict to the Logic App's managed identity
- Logic App
- Create new Logic AppCreated with the trigger and authorization policy preconfigured
Northwind's mailbox extension calls a runbook that uses the Automation account's managed identity (with the Exchange Administrator role, or a narrower custom role group) to set an automatic reply, convert the mailbox to a shared mailbox and give the manager access:
PowerShell (Azure Automation runbook): leaver mailbox handling
param(
[Parameter(Mandatory)] [string] $UserPrincipalName,
[Parameter(Mandatory)] [string] $ManagerUpn
)
Connect-ExchangeOnline -ManagedIdentity -Organization "northwind.onmicrosoft.com"
$text = "Thank you for your message. I have left Northwind Logistics. " +
"Please contact $ManagerUpn for any open topics."
Set-MailboxAutoReplyConfiguration -Identity $UserPrincipalName `
-AutoReplyState Enabled -InternalMessage $text -ExternalMessage $text -ExternalAudience All
Set-Mailbox -Identity $UserPrincipalName -Type Shared
Add-MailboxPermission -Identity $UserPrincipalName -User $ManagerUpn -AccessRights FullAccess -AutoMapping $true
Disconnect-ExchangeOnline -Confirm:$false
A shared mailbox under 50 GB without an archive or hold does not need a licence. A shared mailbox on litigation hold, with an archive or above 50 GB does. If your post-offboarding workflow removes all licences, check those conditions first.
Testing, operations and monitoring
Every workflow in this article was created with isSchedulingEnabled = $false. That is the safe default for new workflows: you can run them on demand for a test user, check every task result, and only then switch on scheduling.
- Create test users with the attributes your scope rule expects, a manager and the right usage location.
- Run the workflow on demand for the test user.
- Check the run in Workflow history (user processing results and task processing results).
- Fix, rerun, and only then enable the schedule.
PowerShell (Graph): inspect runs and failed tasks
$wfId = "<workflow-id>"
$base = "https://graph.microsoft.com/v1.0/identityGovernance/lifecycleWorkflows/workflows/$wfId"
# Recent runs
(Invoke-MgGraphRequest -Method GET -Uri "$base/runs").value |
Select-Object id, processingStatus, startedDateTime, completedDateTime,
totalUsersCount, failedUsersCount, successfulUsersCount
# Per-user results with failures
(Invoke-MgGraphRequest -Method GET -Uri "$base/userProcessingResults").value |
Where-Object { $_.processingStatus -ne "completed" } |
Select-Object id, processingStatus, scheduledDateTime, failedTasksCount
# Enable scheduling after successful tests
Update-MgIdentityGovernanceLifecycleWorkflow -WorkflowId $wfId -IsSchedulingEnabled:$true
A few operational rules that save trouble later:
- Workflows are versioned. Changing tasks or execution conditions creates a new version; the history shows which version processed which user. Name and describe workflows so that someone else understands them in a year.
- Review failures weekly. Most failures are data problems: missing manager, missing mail attribute, unsupported group type. Feed them back to HR or fix the mapping.
- Watch the boundaries. Time-based offsets go from -180 to +180 days. Processing happens at the schedule interval, not at the exact minute of the attribute.
- Protect the workflows themselves. Whoever can edit a leaver workflow can disable or delete accounts at scale. Assign the Lifecycle Workflows Administrator role through PIM with approval.
Rollout plan
Northwind rolled out in this order, and I would recommend the same to most organisations:
- Data first: agree on attribute conventions with HR, populate hire and leave dates, managers and employee types; fix the data quality report until it is close to empty.
- Leaver, last day: the highest security value. Before enabling it, clean up the existing orphaned accounts with an on-demand run of the same workflow.
- Joiner, day one and pre-hire: visible quick win for managers and new hires.
- Leaver, pre- and post-offboarding: after the retention design is confirmed.
- Movers: once access packages per department exist.
- Custom extensions: ticketing, mailbox, other systems.
The results after six months: new employees had a working account with all role-based access on their first morning, leavers were disabled and their sessions revoked on the last working day without anyone opening a ticket, and the 143 orphaned accounts from the audit were gone. The helpdesk did not lose work; it lost the part of its work that nobody enjoyed.
Why this matters
Level 200 to 300Orphaned accounts are an open door. An enabled account that nobody uses and nobody watches is the ideal target for password spraying and credential stuffing, because no legitimate user will notice the failed sign-ins or the suspicious successful one. A leaver process that depends on a ticket will eventually miss someone. A process that depends on the HR leave date will not, as long as the date is maintained.
Auditors ask for evidence, not intentions. ISO/IEC 27001:2022 contains explicit controls for identity management (5.16), access rights (5.18) and responsibilities after termination or change of employment (6.5). Swiss revFADP and the GDPR require appropriate technical and organisational measures to protect personal data, which includes making sure former employees can no longer access it. A workflow run history that shows, per user, when the account was disabled and which memberships were removed is exactly the evidence these reviews need.
Movers are the silent risk. Access creep is rarely discussed because no incident starts with "an employee had too much access". But when an account is compromised, the blast radius is defined by its accumulated permissions. Rotating role-based access on every move keeps that radius small.
It pays for itself. Licences assigned to leavers, hours spent on repetitive tickets and the cost of new hires waiting for access add up quickly. In most organisations with a few hundred employees, the Entra ID Governance licence is justified by licence reclamation and helpdesk effort alone, before any security benefit is counted.
It changes the first and last impression. A new employee who can sign in with a Temporary Access Pass on day one, set up passwordless authentication and find their team already waiting experiences an organisation that has its act together. The same is true, in a quieter way, for a leaver whose access ends cleanly and respectfully on the agreed date.
Checklist
- Entra ID Governance or Entra Suite licences for users in scope.
- HR is the source for hire date, leave date, department, employee type and manager; conventions for time of day agreed.
- Inbound provisioning (Workday, SuccessFactors or API-driven) creates and updates accounts; account state mapping applies only on creation.
- Data quality report close to empty before scheduling any workflow.
- Workflow schedule interval and email settings configured.
- Joiner: pre-hire and day-one workflows with licences, groups, Teams, TAP and welcome email.
- Mover: access packages per role, mover workflows per target department.
- Leaver: last-day workflow with disable, token revocation and removal of memberships and access packages.
- Immediate termination workflow and documented procedure.
- Retention policies for mailboxes and OneDrive before enabling deletion.
- Dynamic group rules exclude disabled accounts.
- Provisioning agent, gMSA permissions and AD Recycle Bin for hybrid users.
- Custom extensions for mailbox, ticketing and non-Microsoft systems.
- Lifecycle Workflows Administrator role assigned through PIM; failures reviewed weekly.
