Automate employee offboarding tasks after their last day of work using Lifecycle Workflows APIs
Article
This tutorial provides step-by-step guidance for configuring offboarding tasks for employees after their last day of work using Lifecycle workflows APIs in Microsoft Graph.
This post off-boarding scenario will run a scheduled workflow and accomplish the following tasks:
Remove all licenses for user
Remove user from all Teams
Delete user account
Prerequisites
To complete this tutorial, you need the following resources and privileges:
Sign in to an API client such as Graph Explorer, Postman, or create your own client app to call Microsoft Graph. To call Microsoft Graph APIs in this tutorial, you need to use an account with the Lifecycle Administrator or Global Administrator Azure AD role.
Grant yourself the following LifecycleWorkflows.ReadWrite.All delegated permission.
Create a test user account that you'll use to represent an employee leaving your organization. This test user account will be deleted when the workflow runs. Assign licenses and Teams memberships to the test user account.
Create a "leaver" workflow
Request
The following request creates an offboarding workflow with the following settings:
It can currently be run on-demand but not on schedule. This step will allow us to validate the workflow using the test user's account. The workflow will be updated to run on schedule later in this tutorial.
The workflow runs seven days after the employee's employeeLeaveDateTime, and if they are in the "Marketing" department.
Three workflow tasks are enabled to run in sequence: the user is unassigned all licenses, then removed from all teams, then their user account is deleted.
POST https://graph.microsoft.com/beta/identityGovernance/LifecycleWorkflows/workflows
Content-type: application/json
{
"category": "leaver",
"displayName": "Post-Offboarding of an employee",
"description": "Configure offboarding tasks for employees after their last day of work",
"isEnabled": true,
"isSchedulingEnabled": false,
"executionConditions": {
"@odata.type": "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions",
"scope": {
"@odata.type": "#microsoft.graph.identityGovernance.ruleBasedSubjectSet",
"rule": "department eq 'Marketing'"
},
"trigger": {
"@odata.type": "#microsoft.graph.identityGovernance.timeBasedAttributeTrigger",
"timeBasedAttribute": "employeeLeaveDateTime",
"offsetInDays": 7
}
},
"tasks": [
{
"category": "leaver",
"continueOnError": false,
"description": "Remove all licenses assigned to the user",
"displayName": "Remove all licenses for user",
"executionSequence": 1,
"isEnabled": true,
"taskDefinitionId": "8fa97d28-3e52-4985-b3a9-a1126f9b8b4e",
"arguments": []
},
{
"category": "leaver",
"continueOnError": false,
"description": "Remove user from all Teams memberships",
"displayName": "Remove user from all Teams",
"executionSequence": 2,
"isEnabled": true,
"taskDefinitionId": "81f7b200-2816-4b3b-8c5d-dc556f07b024",
"arguments": []
},
{
"category": "leaver",
"continueOnError": false,
"description": "Delete user account in Azure AD",
"displayName": "Delete User Account",
"executionSequence": 3,
"isEnabled": true,
"taskDefinitionId": "8d18588d-9ad3-4c0f-99d0-ec215f0e3dff",
"arguments": []
}
]
}
// Code snippets are only available for the latest version. Current version is 5.x
var graphClient = new GraphServiceClient(requestAdapter);
var requestBody = new Microsoft.Graph.Beta.Models.IdentityGovernance.Workflow
{
Category = Microsoft.Graph.Beta.Models.IdentityGovernance.LifecycleWorkflowCategory.Leaver,
DisplayName = "Post-Offboarding of an employee",
Description = "Configure offboarding tasks for employees after their last day of work",
IsEnabled = true,
IsSchedulingEnabled = false,
ExecutionConditions = new Microsoft.Graph.Beta.Models.IdentityGovernance.TriggerAndScopeBasedConditions
{
OdataType = "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions",
Scope = new Microsoft.Graph.Beta.Models.IdentityGovernance.RuleBasedSubjectSet
{
OdataType = "#microsoft.graph.identityGovernance.ruleBasedSubjectSet",
Rule = "department eq 'Marketing'",
},
Trigger = new Microsoft.Graph.Beta.Models.IdentityGovernance.TimeBasedAttributeTrigger
{
OdataType = "#microsoft.graph.identityGovernance.timeBasedAttributeTrigger",
TimeBasedAttribute = Microsoft.Graph.Beta.Models.IdentityGovernance.WorkflowTriggerTimeBasedAttribute.EmployeeLeaveDateTime,
OffsetInDays = 7,
},
},
Tasks = new List<Microsoft.Graph.Beta.Models.IdentityGovernance.TaskObject>
{
new Microsoft.Graph.Beta.Models.IdentityGovernance.TaskObject
{
Category = Microsoft.Graph.Beta.Models.IdentityGovernance.LifecycleTaskCategory.Leaver,
ContinueOnError = false,
Description = "Remove all licenses assigned to the user",
DisplayName = "Remove all licenses for user",
ExecutionSequence = 1,
IsEnabled = true,
TaskDefinitionId = "8fa97d28-3e52-4985-b3a9-a1126f9b8b4e",
Arguments = new List<KeyValuePair>
{
},
},
new Microsoft.Graph.Beta.Models.IdentityGovernance.TaskObject
{
Category = Microsoft.Graph.Beta.Models.IdentityGovernance.LifecycleTaskCategory.Leaver,
ContinueOnError = false,
Description = "Remove user from all Teams memberships",
DisplayName = "Remove user from all Teams",
ExecutionSequence = 2,
IsEnabled = true,
TaskDefinitionId = "81f7b200-2816-4b3b-8c5d-dc556f07b024",
Arguments = new List<KeyValuePair>
{
},
},
new Microsoft.Graph.Beta.Models.IdentityGovernance.TaskObject
{
Category = Microsoft.Graph.Beta.Models.IdentityGovernance.LifecycleTaskCategory.Leaver,
ContinueOnError = false,
Description = "Delete user account in Azure AD",
DisplayName = "Delete User Account",
ExecutionSequence = 3,
IsEnabled = true,
TaskDefinitionId = "8d18588d-9ad3-4c0f-99d0-ec215f0e3dff",
Arguments = new List<KeyValuePair>
{
},
},
},
};
var result = await graphClient.IdentityGovernance.LifecycleWorkflows.Workflows.PostAsync(requestBody);
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new Workflow();
$requestBody->setCategory(new LifecycleWorkflowCategory('leaver'));
$requestBody->setDisplayName('Post-Offboarding of an employee');
$requestBody->setDescription('Configure offboarding tasks for employees after their last day of work');
$requestBody->setIsEnabled(true);
$requestBody->setIsSchedulingEnabled(false);
$executionConditions = new TriggerAndScopeBasedConditions();
$executionConditions->setOdataType('#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions');
$executionConditionsScope = new RuleBasedSubjectSet();
$executionConditionsScope->setOdataType('#microsoft.graph.identityGovernance.ruleBasedSubjectSet');
$executionConditionsScope->setRule('department eq \'Marketing\'');
$executionConditions->setScope($executionConditionsScope);
$executionConditionsTrigger = new TimeBasedAttributeTrigger();
$executionConditionsTrigger->setOdataType('#microsoft.graph.identityGovernance.timeBasedAttributeTrigger');
$executionConditionsTrigger->setTimeBasedAttribute(new WorkflowTriggerTimeBasedAttribute('employeeLeaveDateTime'));
$executionConditionsTrigger->setOffsetInDays(7);
$executionConditions->setTrigger($executionConditionsTrigger);
$requestBody->setExecutionConditions($executionConditions);
$tasksTask1 = new Task();
$tasksTask1->setCategory(new LifecycleTaskCategory('leaver'));
$tasksTask1->setContinueOnError(false);
$tasksTask1->setDescription('Remove all licenses assigned to the user');
$tasksTask1->setDisplayName('Remove all licenses for user');
$tasksTask1->setExecutionSequence(1);
$tasksTask1->setIsEnabled(true);
$tasksTask1->setTaskDefinitionId('8fa97d28-3e52-4985-b3a9-a1126f9b8b4e');
$tasksTask1->setArguments([ ]);
$tasksArray []= $tasksTask1;
$tasksTask2 = new Task();
$tasksTask2->setCategory(new LifecycleTaskCategory('leaver'));
$tasksTask2->setContinueOnError(false);
$tasksTask2->setDescription('Remove user from all Teams memberships');
$tasksTask2->setDisplayName('Remove user from all Teams');
$tasksTask2->setExecutionSequence(2);
$tasksTask2->setIsEnabled(true);
$tasksTask2->setTaskDefinitionId('81f7b200-2816-4b3b-8c5d-dc556f07b024');
$tasksTask2->setArguments([ ]);
$tasksArray []= $tasksTask2;
$tasksTask3 = new Task();
$tasksTask3->setCategory(new LifecycleTaskCategory('leaver'));
$tasksTask3->setContinueOnError(false);
$tasksTask3->setDescription('Delete user account in Azure AD');
$tasksTask3->setDisplayName('Delete User Account');
$tasksTask3->setExecutionSequence(3);
$tasksTask3->setIsEnabled(true);
$tasksTask3->setTaskDefinitionId('8d18588d-9ad3-4c0f-99d0-ec215f0e3dff');
$tasksTask3->setArguments([ ]);
$tasksArray []= $tasksTask3;
$requestBody->setTasks($tasksArray);
$result = $graphServiceClient->identityGovernance()->lifecycleWorkflows()->workflows()->post($requestBody)->wait();
HTTP/1.1 201 Created
Content-Type: application/json
{
"@odata.context": "https://graph.microsoft.com/beta/$metadata#identityGovernance/lifecycleWorkflows/workflows/$entity",
"category": "leaver",
"description": "Configure offboarding tasks for employees after their last day of work",
"displayName": "Post-Offboarding of an employee",
"lastModifiedDateTime": "2022-10-03T18:29:10.8412536Z",
"createdDateTime": "2022-10-03T18:29:10.8412352Z",
"deletedDateTime": null,
"id": "15239232-66ed-445b-8292-2f5bbb2eb833",
"isEnabled": true,
"isSchedulingEnabled": false,
"nextScheduleRunDateTime": null,
"version": 1,
"executionConditions": {
"@odata.type": "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions",
"scope": {
"@odata.type": "#microsoft.graph.identityGovernance.ruleBasedSubjectSet",
"rule": "department eq 'Marketing'"
},
"trigger": {
"@odata.type": "#microsoft.graph.identityGovernance.timeBasedAttributeTrigger",
"timeBasedAttribute": "employeeLeaveDateTime",
"offsetInDays": 7
}
}
}
Run the workflow
Request
Because the workflow hasn't been scheduled to run, it must be run manually, on-demand. In the following request, the user for whom the workflow will run is identified by ID df744d9e-2148-4922-88a8-633896c1e929.
While you configured execution conditions for the workflow, this request to run the workflow on demand ignores the execution conditions. For example, if the user identified by ID df744d9e-2148-4922-88a8-633896c1e929 isn't in the "Marketing" department or their employeeLeaveDateTime is set to null, this command will still run the tasks that were defined in the workflow, for the user.
// Code snippets are only available for the latest version. Current version is 5.x
var graphClient = new GraphServiceClient(requestAdapter);
var requestBody = new Microsoft.Graph.Beta.IdentityGovernance.LifecycleWorkflows.Workflows.Item.MicrosoftGraphIdentityGovernanceActivate.ActivatePostRequestBody
{
Subjects = new List<User>
{
new User
{
Id = "df744d9e-2148-4922-88a8-633896c1e929",
},
},
};
await graphClient.IdentityGovernance.LifecycleWorkflows.Workflows["{workflow-id}"].MicrosoftGraphIdentityGovernanceActivate.PostAsync(requestBody);
// THE CLI IS IN PREVIEW. NON-PRODUCTION USE ONLY
mgc-beta identity-governance lifecycle-workflows workflows microsoft-graph-identity-governance-activate post --workflow-id {workflow-id}
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new ActivatePostRequestBody();
$subjectsUser1 = new User();
$subjectsUser1->setId('df744d9e-2148-4922-88a8-633896c1e929');
$subjectsArray []= $subjectsUser1;
$requestBody->setSubjects($subjectsArray);
$graphServiceClient->identityGovernance()->lifecycleWorkflows()->workflows()->byWorkflowId('workflow-id')->microsoftGraphIdentityGovernanceActivate()->post($requestBody)->wait();
# THE PYTHON SDK IS IN PREVIEW. FOR NON-PRODUCTION USE ONLY
graph_client = GraphServiceClient(request_adapter)
request_body = ActivatePostRequestBody(
subjects = [
User(
id = "df744d9e-2148-4922-88a8-633896c1e929",
),
]
)
await graph_client.identity_governance.lifecycle_workflows.workflows.by_workflow_id('workflow-id').microsoft_graph_identity_governance_activate.post(body = request_body)
GET https://graph.microsoft.com/beta/identityGovernance/LifecycleWorkflows/workflows/15239232-66ed-445b-8292-2f5bbb2eb833/userProcessingResults
// Code snippets are only available for the latest version. Current version is 5.x
var graphClient = new GraphServiceClient(requestAdapter);
var result = await graphClient.IdentityGovernance.LifecycleWorkflows.Workflows["{workflow-id}"].UserProcessingResults.GetAsync();
// THE CLI IS IN PREVIEW. NON-PRODUCTION USE ONLY
mgc-beta identity-governance lifecycle-workflows workflows user-processing-results list --workflow-id {workflow-id}
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$result = $graphServiceClient->identityGovernance()->lifecycleWorkflows()->workflows()->byWorkflowId('workflow-id')->userProcessingResults()->get()->wait();
# THE PYTHON SDK IS IN PREVIEW. FOR NON-PRODUCTION USE ONLY
graph_client = GraphServiceClient(request_adapter)
result = await graph_client.identity_governance.lifecycle_workflows.workflows.by_workflow_id('workflow-id').user_processing_results.get()
GET https://graph.microsoft.com/beta/identityGovernance/LifecycleWorkflows/workflows/15239232-66ed-445b-8292-2f5bbb2eb833/userProcessingResults/summary(startDateTime=2022-10-01T00:00:00Z,endDateTime=2022-10-30T00:00:00Z)
// Code snippets are only available for the latest version. Current version is 5.x
var graphClient = new GraphServiceClient(requestAdapter);
var result = await graphClient.IdentityGovernance.LifecycleWorkflows.Workflows["{workflow-id}"].UserProcessingResults.MicrosoftGraphIdentityGovernanceSummaryWithStartDateTimeWithEndDateTime(DateTimeOffset.Parse("{endDateTime}"),DateTimeOffset.Parse("{startDateTime}")).GetAsync();
// THE CLI IS IN PREVIEW. NON-PRODUCTION USE ONLY
mgc-beta identity-governance lifecycle-workflows workflows user-processing-results microsoft-graph-identity-governance-summary-with-start-date-time-with-end-date-time get --start-date-time {start-date-time-id} --end-date-time {end-date-time-id} --workflow-id {workflow-id}
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$result = $graphServiceClient->identityGovernance()->lifecycleWorkflows()->workflows()->byWorkflowId('workflow-id')->userProcessingResults()->microsoftGraphIdentityGovernanceSummaryWithStartDateTimeWithEndDateTime(new \DateTime('{endDateTime}'),new \DateTime('{startDateTime}'))->get()->wait();
# THE PYTHON SDK IS IN PREVIEW. FOR NON-PRODUCTION USE ONLY
graph_client = GraphServiceClient(request_adapter)
result = await graph_client.identity_governance.lifecycle_workflows.workflows.by_workflow_id('workflow-id').user_processing_results.microsoft_graph_identity_governance_summary(start_date_time={start_date_time},end_date_time={end_date_time}.get()
GET https://graph.microsoft.com/beta/identityGovernance/LifecycleWorkflows/workflows/15239232-66ed-445b-8292-2f5bbb2eb833/userProcessingResults/40efc576-840f-47d0-ab95-5abca800f8a2/taskProcessingResults
// Code snippets are only available for the latest version. Current version is 5.x
var graphClient = new GraphServiceClient(requestAdapter);
var result = await graphClient.IdentityGovernance.LifecycleWorkflows.Workflows["{workflow-id}"].UserProcessingResults["{userProcessingResult-id}"].TaskProcessingResults.GetAsync();
// THE CLI IS IN PREVIEW. NON-PRODUCTION USE ONLY
mgc-beta identity-governance lifecycle-workflows workflows user-processing-results task-processing-results list --workflow-id {workflow-id} --user-processing-result-id {userProcessingResult-id}
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$result = $graphServiceClient->identityGovernance()->lifecycleWorkflows()->workflows()->byWorkflowId('workflow-id')->userProcessingResults()->byUserProcessingResultId('userProcessingResult-id')->taskProcessingResults()->get()->wait();
# THE PYTHON SDK IS IN PREVIEW. FOR NON-PRODUCTION USE ONLY
graph_client = GraphServiceClient(request_adapter)
result = await graph_client.identity_governance.lifecycle_workflows.workflows.by_workflow_id('workflow-id').user_processing_results.by_user_processing_result_id('userProcessingResult-id').task_processing_results.get()
HTTP/1.1 200 OK
Content-Type: application/json
{
"@odata.context": "https://graph.microsoft.com/beta/$metadata#identityGovernance/lifecycleWorkflows/workflows('15239232-66ed-445b-8292-2f5bbb2eb833')/userProcessingResults('40efc576-840f-47d0-ab95-5abca800f8a2')/taskProcessingResults",
"value": [
{
"completedDateTime": "2022-10-03T18:30:50.483365Z",
"createdDateTime": "2022-10-03T18:30:47.6125438Z",
"id": "78650318-7238-4e7e-852f-2c36cbeff340",
"processingStatus": "completed",
"startedDateTime": "2022-10-03T18:30:50.0549446Z",
"failureReason": null,
"subject": {
"id": "df744d9e-2148-4922-88a8-633896c1e929"
},
"task": {
"category": "leaver",
"continueOnError": false,
"description": "Remove all licenses assigned to the user",
"displayName": "Remove all licenses for user",
"executionSequence": 1,
"id": "f71246b2-269c-4ba6-ab8e-afc1a05114cb",
"isEnabled": true,
"taskDefinitionId": "8fa97d28-3e52-4985-b3a9-a1126f9b8b4e",
"arguments": []
}
},
{
"completedDateTime": "2022-10-03T18:30:57.6034021Z",
"createdDateTime": "2022-10-03T18:30:47.8824313Z",
"id": "3d2e459d-5614-42e4-952b-0e917b5f6646",
"processingStatus": "completed",
"startedDateTime": "2022-10-03T18:30:53.6770279Z",
"failureReason": null,
"subject": {
"id": "df744d9e-2148-4922-88a8-633896c1e929"
},
"task": {
"category": "leaver",
"continueOnError": false,
"description": "Remove user from all Teams memberships",
"displayName": "Remove user from all Teams",
"executionSequence": 2,
"id": "ed545f03-e8d8-45fb-9cbd-15c937f2a866",
"isEnabled": true,
"taskDefinitionId": "81f7b200-2816-4b3b-8c5d-dc556f07b024",
"arguments": []
}
},
{
"completedDateTime": "2022-10-03T18:31:00.0894515Z",
"createdDateTime": "2022-10-03T18:30:48.0004721Z",
"id": "03359fa6-c63c-4573-92c2-4c9518ca98aa",
"processingStatus": "completed",
"startedDateTime": "2022-10-03T18:30:59.6195169Z",
"failureReason": null,
"subject": {
"id": "df744d9e-2148-4922-88a8-633896c1e929"
},
"task": {
"category": "leaver",
"continueOnError": false,
"description": "Delete user account in Azure AD",
"displayName": "Delete User Account",
"executionSequence": 3,
"id": "b4cefaa0-6ceb-461d-bbf5-ec69246463fd",
"isEnabled": true,
"taskDefinitionId": "8d18588d-9ad3-4c0f-99d0-ec215f0e3dff",
"arguments": []
}
}
]
}
[Optional] Schedule the workflow to run automatically
After running your workflow on-demand and checking that everything is working fine, you may want to enable the workflow so that it can run automatically on a tenant-defined schedule. To enable the workflow schedule, you may run the following request.
// Code snippets are only available for the latest version. Current version is 5.x
var graphClient = new GraphServiceClient(requestAdapter);
var requestBody = new Microsoft.Graph.Beta.Models.IdentityGovernance.Workflow
{
IsEnabled = true,
IsSchedulingEnabled = true,
};
var result = await graphClient.IdentityGovernance.LifecycleWorkflows.Workflows["{workflow-id}"].PatchAsync(requestBody);
// THE CLI IS IN PREVIEW. NON-PRODUCTION USE ONLY
mgc-beta identity-governance lifecycle-workflows workflows patch --workflow-id {workflow-id} --body '{\
"isEnabled": true,\
"isSchedulingEnabled": true\
}\
'
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new Workflow();
$requestBody->setIsEnabled(true);
$requestBody->setIsSchedulingEnabled(true);
$result = $graphServiceClient->identityGovernance()->lifecycleWorkflows()->workflows()->byWorkflowId('workflow-id')->patch($requestBody)->wait();
# THE PYTHON SDK IS IN PREVIEW. FOR NON-PRODUCTION USE ONLY
graph_client = GraphServiceClient(request_adapter)
request_body = Workflow(
is_enabled = True,
is_scheduling_enabled = True,
)
result = await graph_client.identity_governance.lifecycle_workflows.workflows.by_workflow_id('workflow-id').patch(body = request_body)
When a workflow is scheduled, Lifecycle Workflows will check every three hours for users in the associated execution condition and execute the configured tasks for those users. You can customize this recurrence from between one hour to 24 hours.