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 200

Every 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.

The short clarification up front

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 300

Northwind 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:

Manual joiner and leaver process before automation HR system contract signed Email to IT sometimes, often late Helpdesk ticket copy of a colleague Manual changes AD, licences, groups Average lead time for a new account: 2 to 5 working days Leavers disabled on average 9 days after the last working day "Copy permissions from colleague X" is the most common access request
Figure 1: The starting point. Every step depends on a person remembering to do it.

An internal audit in preparation for an ISO 27001 recertification found the following:

FindingDetail
Enabled accounts of former employees143 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 disablementFor 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 accessEmployees 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 staffSeasonal warehouse staff were created as "permanent" accounts and reactivated each season, including their passwords from the previous year.
Missing manager attributeAbout 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 400

The 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:

Target architecture for HR-driven lifecycle automation HR system source of truth hire / leave dates, dept Inbound provisioning API-driven (SCIM bulk upload) creates and updates accounts Active Directory synced via Connect Sync / Cloud Sync or cloud-only users in Entra ID Microsoft Entra ID user object hire and leave date, department, manager, employee type Lifecycle Workflows time, attribute, group triggers enable, email, TAP, groups, licences, disable, delete Entitlement management access packages per role requested or removed by workflow tasks Custom task extensions Logic Apps for ticketing, mailbox and OneDrive handling, non-Microsoft systems
Figure 2: HR data flows into the directory through provisioning; Lifecycle Workflows act on the dates and attributes that arrive there.

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

ItemRequirement
LicensingMicrosoft Entra ID Governance (add-on to Entra ID P1 or P2) or Microsoft Entra Suite, for the users processed by workflows.
Admin roleLifecycle Workflows Administrator (least privilege). Creating custom task extensions additionally requires permissions on the Azure Logic App.
Graph permissions for automationLifecycleWorkflows.ReadWrite.All to manage workflows; User-LifeCycleInfo.ReadWrite.All to write employeeLeaveDateTime; User.ReadWrite.All for other user attributes.
Temporary Access PassTAP 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 attributeRequired by every task that emails the manager or sends the TAP to the manager.
Usage locationRequired for the "Assign licenses to user" task, as for any direct licence assignment.
Hybrid usersFor 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 }
Microsoft Entra admin center › ID Governance › Lifecycle workflows › Workflow settings
Tenant-wide workflow settings
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
Figure 3: Settings that apply to all workflows in the tenant.

The data foundation: HR attributes

Time-based workflows key off three user properties:

PropertyTypical sourceUsed for
employeeHireDateHR start datePre-hire, day-one and post-onboarding workflows
employeeLeaveDateTimeHR termination datePre-offboarding, last-day and post-offboarding workflows
createdDateTimeSet by Entra IDWorkflows 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".

Mind the time portion

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.

Synchronised users and the date attributes

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 typeFires whenTypical use
Time-based attributeemployeeHireDate, employeeLeaveDateTime or createdDateTime plus an offset between -180 and +180 days is reachedPre-hire, day one, pre-offboarding, last day, post-offboarding
Attribute changeOne of the selected attributes changes on a user in scopeMovers (department, job title, cost centre)
Group membership changeA user is added to or removed from a specified groupRole changes managed through groups, sabbaticals
On-demandAn admin runs the workflow for selected usersImmediate 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:

TasktaskDefinitionIdCategory
Generate Temporary Access Pass and send via email to user's manager1b555e50-7f65-41d5-b514-5894a026d10dJoiner
Send welcome email to new hire70b29d51-b59a-4773-9280-8841dfd3f2eaJoiner
Send onboarding reminder email3C860712-2D37-42A4-928F-5C93935D26A1Joiner
Enable user account6fc52c9d-398b-4305-9763-15f42c1676fcJoiner, Leaver
Assign licenses to user683c87a4-2ad4-420b-97d4-220d90afcd24Joiner, Mover
Add user to groups22085229-5809-45e8-97fd-270d28d66910Joiner, Leaver, Mover
Add user to teamse440ed8d-25a1-4618-84ce-091ed5be5594Joiner, Leaver, Mover
Request user access package assignmentc1ec1e76-f374-4375-aaa6-0bb6bd4c60beJoiner, Mover
Send email to notify manager of user moveaab41899-9972-422a-9d97-f626014578b7Mover
Remove all access package assignments for user42ae2956-193d-4f39-be06-691b8ac4fa1dLeaver, Mover
Send email to manager before user's last day52853a3e-f4e5-4eb8-bb24-1ac09a1da935Leaver
Disable user account1dfdfcc7-52fa-4c2e-bf3a-e3919cc12950Leaver
Revoke all refresh tokens for user509589a4-0466-4471-829e-49c5e502bdeeLeaver, Mover
Remove users from all groupsb3a31406-2a15-4c9a-b25b-a658fa5f07fcLeaver
Remove user from all teams81f7b200-2816-4b3b-8c5d-dc556f07b024Leaver
Cancel all pending access package assignment requests for user498770d9-bab7-4e4c-b73d-5ded82a1d0b3Leaver
Remove all license assignments from user8fa97d28-3e52-4985-b3a9-a1126f9b8b4eLeaver
Delete user8d18588d-9ad3-4c0f-99d0-ec215f0e3dffLeaver

Joiner workflows

Northwind split onboarding into two workflows, because the tasks belong to different moments:

Joiner timeline Hire date - 7 days Pre-hire workflow reminder to manager licences, groups, Teams Hire date Day-one workflow enable account, TAP to manager, welcome email Hire date + 7 Post-onboarding training access package, optional reminder
Figure 4: Joiner workflows along the hire date.

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
Email placeholders

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.

Group and Teams task limits

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.

Microsoft Entra admin center › ID Governance › Lifecycle workflows › Workflows › Joiner | Logistics | Day one
Execution conditions and tasks
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
Figure 5: The day-one workflow as it appears in the admin center.

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:

  1. Schedule removal of all existing access package assignments (Microsoft's mover templates default to 15 days, giving the person time to hand over).
  2. Request the access package of the new department.
  3. 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
Order of the access package tasks

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:

Leaver timeline Leave date - 7 Pre-offboarding email to manager: hand-over, data, devices Leave date Last day disable, revoke tokens, remove groups, Teams and access packages, mailbox extension Leave date + 30 Post-offboarding remove licences, delete account On-demand "immediate termination" runs the last-day tasks at once, for any user, at any time.
Figure 6: Leaver workflows along the leave date.

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:

DataWhat happens on deletionWhat to configure beforehand
MailboxSoft-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.
OneDriveRetained 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 objectSoft-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 }) }
Microsoft Entra admin center › ID Governance › Lifecycle workflows › Workflows › Leaver | Immediate termination
Run on demand
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
Figure 7: The on-demand run used for immediate terminations.

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, disableOnPremisesAccount or deleteOnPremisesAccount set to true, 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.

Microsoft Entra admin center › ID Governance › Lifecycle workflows › Custom extensions › Create custom extension
Leaver mailbox handling
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
Figure 8: A custom task extension that waits for the Logic App result.

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
Shared mailboxes and licences

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.

  1. Create test users with the attributes your scope rule expects, a manager and the right usage location.
  2. Run the workflow on demand for the test user.
  3. Check the run in Workflow history (user processing results and task processing results).
  4. 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:

  1. 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.
  2. 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.
  3. Joiner, day one and pre-hire: visible quick win for managers and new hires.
  4. Leaver, pre- and post-offboarding: after the retention design is confirmed.
  5. Movers: once access packages per department exist.
  6. 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 300

Orphaned 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.

Task names, task definition IDs and limits reflect Microsoft Entra ID Governance as documented in October 2026. Microsoft adds tasks and templates regularly; check the "Lifecycle Workflow built-in tasks" article on Microsoft Learn before building automation that depends on specific IDs, and test every workflow on demand before enabling its schedule.