Forum Discussion
Automating Microsoft Entra ID User Lifecycle Management with PowerShell and Microsoft Graph
Introduction — The Problem
Managing user accounts throughout the employee lifecycle is one of the common responsibilities of an Identity or Microsoft 365 administrator.
When an employee joins an organization, administrators typically create the required identity, assign licenses, configure access, and provision devices and applications.
The more challenging part often comes when an employee leaves.
A typical offboarding process may require administrators to:
- Identify employees whose employment has ended
- Disable the Microsoft Entra ID account
- Remove directly assigned licenses
- Record what actions were performed
- Notify administrators or the appropriate support team
- Maintain evidence for auditing and troubleshooting
If these tasks are performed manually, there is a risk of inconsistent execution, delayed account disablement, forgotten licenses, and incomplete audit records.
To explore this problem from an administrator's perspective, I built a PowerShell-based automation project using Microsoft Graph.
The project is:
Microsoft Entra ID User Lifecycle Automation with PowerShell
The goal is to provide a controlled and auditable workflow for detecting expired users and performing defined offboarding actions.
1. Why User Lifecycle Automation Matters
Identity lifecycle management is not simply about creating and deleting accounts.
It is about controlling access throughout the user's relationship with the organization.
A simplified lifecycle can be represented as:
Joiner ↓ User Account Created ↓ Access & Licenses Assigned ↓ Employee Active ↓ Mover / Role Change ↓ Employee Leaves ↓ Offboarding ↓ Access Revocation
For the leaver stage, organizations need reliable processes to make sure that accounts do not remain active after an employee's departure.
Microsoft Entra ID provides native Lifecycle Workflows capabilities for Joiner-Mover-Leaver scenarios. These workflows use tasks and execution conditions to automate lifecycle activities.
This project takes a different approach.
Instead of replacing native Microsoft capabilities, it demonstrates how administrators can build a custom PowerShell and Microsoft Graph automation layer when they need scripting flexibility, custom reporting, administrator-controlled execution, or a learning platform for Graph automation.
2. Solution Architecture
The project is designed around a simple lifecycle pipeline:
Employee Created ↓ Expiry / Leave Date Assigned ↓ Daily Automation Runs ↓ Find Expired Users ↓ Validate User ↓ Disable Entra ID Account ↓ Remove Direct Licenses ↓ Create Audit Log ↓ Send Administrator Notification
The workflow is divided into separate PowerShell scripts instead of placing everything into a single large script.
This separation provides several advantages:
- Easier troubleshooting
- Easier testing
- Clear permissions per function
- Reusable individual components
- Better maintainability
- Easier future integration with scheduling or orchestration systems
The project also includes an orchestrator script that coordinates the individual lifecycle stages.
3. Technology Stack
The project uses the following technologies:
Microsoft Entra ID
Microsoft Entra ID provides the identity platform where users, accounts, licenses, and lifecycle-related attributes are managed.
The project uses the user's:
employeeLeaveDateTime
property as the lifecycle date.
Microsoft documents employeeLeaveDateTime as an attribute that can be used with leaver lifecycle workflows.
Microsoft Graph
Microsoft Graph provides the API layer used by the PowerShell scripts.
The project uses Graph operations for:
- Reading users
- Updating user attributes
- Disabling accounts
- Reading license information
- Removing directly assigned licenses
- Sending administrator notifications
PowerShell
PowerShell provides the automation and orchestration layer.
The project is designed for Windows PowerShell 5.1+ and uses Microsoft Graph PowerShell modules.
4. Automation Workflow
The complete workflow consists of several stages.
Stage 1 — Assign an Expiry Date
The administrator can assign an employee leave date to an Entra ID user.
The project stores this information using:
employeeLeaveDateTime
For example:
Employee Leave Date ↓ 2026-10-01
The project then uses this value during its daily detection process.
Microsoft Graph supports updating employeeLeaveDateTime using Update-MgUser.
Stage 2 — Detect Expired Users
The detection script retrieves users and checks their configured leave dates.
The basic logic is:
Leave Date < Today's Date ↓ Expired
Users whose leave date is today are not treated as expired by this implementation.
The detection stage is intentionally read-only.
It does not:
- Disable users
- Remove licenses
- Modify accounts
- Send notifications
- Write lifecycle audit events
Instead, it generates a report that can be reviewed before taking action.
Stage 3 — Disable Expired Users
Once an expired and enabled user is identified, the disable stage can be executed.
The project re-checks the user before making the change.
This is important because the state may have changed between detection and execution.
The script verifies that:
User still exists ↓ Account is still enabled ↓ Leave date is still expired ↓ Disable account
This reduces the chance of acting on stale information.
Stage 4 — Remove Directly Assigned Licenses
After successful account disablement, the automation can remove directly assigned licenses.
The project deliberately distinguishes between:
Direct license assignment
and
Group-based license assignment
For direct assignments, Microsoft Graph's Set-MgUserLicense API supports adding and removing licenses.
For group-based licensing, removing the user's license directly is generally not the correct lifecycle operation.
Instead, the appropriate approach is to manage the user's membership in the licensing group according to the organization's access model.
Therefore, this project focuses on directly assigned licenses.
Stage 5 — Audit
Every important lifecycle action is recorded in an audit CSV.
The audit information includes fields such as:
- Event ID
- Timestamp
- Action
- Display Name
- User Principal Name
- Employee Leave Date
- Previous Account Status
- New Account Status
- Result
- Performed By
- Windows User
- Computer Name
- Tenant ID
- Notification Status
- Notification Timestamp
This provides an execution history that can be used for:
- Troubleshooting
- Operational review
- Compliance evidence
- Change tracking
- Incident investigation
The audit process also handles migration from an older audit schema so that historical records can be retained when the schema evolves.
Stage 6 — Administrator Notification
After successful lifecycle actions are recorded, the notification script can send a summary to the administrator.
The notification contains information such as:
- Event ID
- Timestamp
- Action
- User
- Leave date
- Previous account status
- New account status
- Result
- Performed By
- Computer
The project uses Microsoft Graph's Send-MgUserMail functionality for email notification.
This means the automation does not require a separate SMTP implementation.
5. Script-by-Script Explanation
The repository is structured into individual scripts.
01 — Set User Expiry Date
Scripts/01-Set-UserExpiryDate.ps1
Purpose:
Set or update the user's employeeLeaveDateTime.
This provides the lifecycle date that the detection process evaluates.
02 — Get Expired Users
Scripts/02-Get-ExpiredUsers.ps1
Purpose:
Detect users whose configured leave date has passed.
Characteristics:
- Read-only
- Retrieves users from Microsoft Entra ID
- Checks employeeLeaveDateTime
- Identifies expired users
- Generates a CSV report
Required permission:
User.Read.All
03 — Disable Expired Users
Scripts/03-Disable-ExpiredUsers.ps1
Purpose:
Disable expired accounts that are still enabled.
The script performs validation before changing the account.
It also supports controlled execution and preview scenarios.
04 — Remove User Licenses
Scripts/04-Remove-UserLicenses.ps1
Purpose:
Remove directly assigned licenses from a user.
The script:
- Retrieves the user
- Retrieves assigned licenses
- Maps license IDs to license information
- Displays the licenses
- Confirms the action when running interactively
- Removes direct assignments
- Re-reads the user
- Verifies the result
- Generates a report
Required permission includes:
LicenseAssignment.ReadWrite.All
Microsoft documents this permission for direct license assignment operations through Graph.
05 — Write Audit Log
Scripts/05-Write-AuditLog.ps1
Purpose:
Create a structured record of lifecycle actions.
The script is responsible for maintaining the audit trail rather than performing the lifecycle action itself.
This separation makes it possible to reuse the audit mechanism for future lifecycle operations.
06 — Send Administrator Notification
Scripts/06-Send-AdminNotification.ps1
Purpose:
Send an administrator notification containing successful lifecycle actions.
The notification process also updates the audit records to indicate that the corresponding events were notified.
Run — Lifecycle Orchestrator
Scripts/Run-EntraUserLifecycle.ps1
This is the main orchestration script.
It coordinates the lifecycle stages:
02 Detection ↓ 03 Disable ↓ 04 License Removal ↓ 05 Audit ↓ 06 Notification
The orchestrator is designed to keep the individual scripts modular while providing a single operational entry point.
6. Safety Model
One of the most important design goals of the project is preventing accidental destructive operations.
Disabling an account and removing licenses are destructive administrative actions.
For that reason, the project supports multiple execution modes.
WhatIf Mode
The administrator can execute the workflow in preview mode.
The purpose is to answer:
"What would this automation do if I ran it for real?"
The WhatIf process should:
- Detect expired users
- Display the users that would be affected
- Show planned actions
- Avoid modifying Entra ID
- Avoid removing licenses
- Avoid creating lifecycle audit events
- Avoid sending lifecycle notification emails
This makes WhatIf useful for initial validation and troubleshooting.
Interactive Confirmation
Normal execution uses administrator confirmation before destructive actions.
The lifecycle process separates the decisions:
Disable expired accounts? ↓ Yes / No ↓ Disable accounts ↓ Remove direct licenses? ↓ Yes / No
This is intentionally different from automatically performing every action immediately.
Force Mode
For controlled automation scenarios, the project also supports:
-Force
Force mode bypasses interactive confirmation.
This is useful when the orchestrator is executed through a scheduled task or another controlled automation mechanism.
The administrator should only use Force mode after validating the environment and lifecycle rules.
7. License Management
License management requires particular attention.
A user can have licenses assigned directly or through group-based licensing.
For example:
User ├── Direct License └── Group-Based License
This project handles the direct license scenario.
Direct licenses can be removed using Microsoft Graph.
Group-based licensing should instead be managed through the appropriate licensing group.
This distinction is important because simply removing a license from a user does not necessarily represent the correct access-management action when the license is inherited through group membership.
The project therefore avoids presenting license removal as a universal "remove all access" operation.
8. Audit Logging
Automation without auditability can create another operational problem.
If an automation disables an account at 2:00 AM, administrators should be able to determine:
- Which user was affected?
- When did the action occur?
- What action was performed?
- Was the operation successful?
- Who executed the automation?
- From which computer?
- Was a notification sent?
The project records these details in a structured CSV audit file.
Example lifecycle event:
Action: Disable Result: Success Previous Status: Enabled New Status: Disabled Performed By: Administrator Computer: Management Server
A second event can record license removal:
Action: RemoveLicense Result: Success
This provides a simple but useful operational audit trail.
9. Administrator Notifications
After successful actions are completed, administrators should not need to manually open CSV files every day just to determine whether the automation worked.
The notification component provides a summary of successful actions.
The email can contain multiple lifecycle events in a single message.
For example:
Entra ID Lifecycle Automation - 2 Action(s) Action Result ------------------------- Disable Success RemoveLicense Success
The message also contains contextual information such as the administrator account and computer from which the automation was executed.
The project uses Microsoft Graph mail functionality rather than implementing a separate SMTP service. Microsoft provides Send-MgUserMail for sending messages through Graph.
10. Permissions & Least Privilege
Permission design is an important part of Microsoft Graph automation.
The project separates permissions according to script responsibilities.
A simplified model is:
| Script | Purpose | Required Permission |
|---|---|---|
| 01 | Set expiry date | User.ReadWrite.All / lifecycle attribute permissions depending on implementation |
| 02 | Detect users | User.Read.All |
| 03 | Disable users | User.Read.All + User.ReadWrite.All |
| 04 | Remove direct licenses | User.Read.All + LicenseAssignment.ReadWrite.All |
| 05 | Audit | Local file access |
| 06 | Send notification | User.Read.All + Mail.Send |
The actual production permission model should be reviewed and reduced according to the authentication method and organizational requirements.
Microsoft's documentation recommends using the least-privileged permission available for the required operation.
For example, the detection script only needs to read users, so it should not require write permissions simply because another script in the project needs them.
This separation is one of the areas I intend to improve further as the project evolves.
11. Testing
Before using lifecycle automation against production users, testing is essential.
The project was tested using controlled test accounts.
The testing process included:
Expiry Date Testing
A test user's employeeLeaveDateTime was configured.
The detection script was then used to verify that the user appeared in the expected report.
Detection Testing
The detection process was tested against users with:
- No expiry date
- Future expiry date
- Current date
- Expired date
- Expired and enabled status
- Expired and already disabled status
WhatIf Testing
The full orchestration process was tested in WhatIf mode to verify that expired users were detected without performing destructive operations.
Disable Testing
A controlled test user was used to verify account disablement.
The automation successfully changed the account from:
Enabled
to:
Disabled
License Testing
A test license was assigned directly to the test account.
The automation then detected and removed the direct license.
The result was verified by reading the user's assigned licenses again.
Audit Testing
The resulting Disable and RemoveLicense actions were recorded in the audit log.
Notification Testing
The notification process successfully generated an administrator email containing the lifecycle actions and their results.
The testing approach is documented in the repository's Test Plan.
12. GitHub Repository
The complete project, including scripts and documentation, is available on GitHub:
https://github.com/habibnp01-prog/Entra-ID-User-Lifecycle-Automation?utm_source=chatgpt.com
The repository contains:
README.md CHANGELOG.md CONTRIBUTING.md Scripts/ ├── 01-Set-UserExpiryDate.ps1 ├── 02-Get-ExpiredUsers.ps1 ├── 03-Disable-ExpiredUsers.ps1 ├── 04-Remove-UserLicenses.ps1 ├── 05-Write-AuditLog.ps1 ├── 06-Send-AdminNotification.ps1 └── Run-EntraUserLifecycle.ps1 Documentation/ ├── Architecture.md ├── Installation.md ├── Permissions.md ├── Security.md ├── Test-Plan.md └── Troubleshooting.md
The repository is intended not only to demonstrate the final automation but also to document how an administrator can understand, test, troubleshoot, and extend it.
13. Future Improvements
The current implementation focuses on a controlled and understandable core lifecycle.
There are several areas that can be improved in future releases.
Certificate-Based Authentication
The current implementation can be used interactively with Microsoft Graph.
A future production-oriented version can use application authentication with a certificate instead of relying on an interactive administrator session.
Least-Privilege Application Permissions
The application permission model can be further refined so that each automation scenario receives only the permissions it actually requires.
Scheduled Execution
The orchestrator can be integrated with:
- Windows Task Scheduler
- Azure Automation
- Azure Functions
- Other enterprise scheduling platforms
Configuration-Driven Execution
Future versions can move administrator settings into a controlled configuration file.
For example:
Admin Email Report Location Notification Settings Execution Mode Lifecycle Rules
Improved Group-Based Licensing
A future version can provide controlled handling of group-based license assignment by identifying licensing groups and managing group membership according to defined rules.
Pester Testing
Automated PowerShell unit tests can be added to validate functions and lifecycle logic.
GitHub Actions
The project can use GitHub Actions to automatically validate PowerShell scripts when changes are submitted.
Additional Lifecycle Actions
Future lifecycle stages could include:
- Group membership removal
- Microsoft Teams access review
- Application access review
- Device cleanup
- Session/token revocation
- Manager notification
- Service desk ticket creation
These features would need careful security and permission analysis before implementation.
14. Conclusion
User lifecycle management is a fundamental part of identity administration.
A reliable offboarding process should not depend entirely on an administrator remembering every step.
This project demonstrates how Microsoft Entra ID, Microsoft Graph, and PowerShell can be combined to create a controlled lifecycle automation workflow.
The key design principles behind the project are:
- Automate repetitive administrative tasks
- Separate detection from execution
- Validate before destructive actions
- Provide WhatIf capability
- Require confirmation for manual destructive operations
- Support controlled Force execution
- Maintain an audit trail
- Notify administrators
- Distinguish direct licensing from group-based licensing
- Document permissions
- Test before production deployment
Microsoft Entra Lifecycle Workflows already provides native capabilities for Joiner-Mover-Leaver automation, and organizations should evaluate those capabilities first when they meet the requirement.
The purpose of this project is not to claim that custom PowerShell should replace native Entra Lifecycle Workflows.
Instead, it demonstrates how administrators can understand the underlying identity lifecycle process, work with Microsoft Graph, build controlled automation, and extend the solution for scenarios where custom scripting and operational control are useful.
I would welcome feedback from other Microsoft Entra ID, Microsoft 365, Identity, and PowerShell administrators on the architecture, security model, testing approach, and ideas for future improvements.
GitHub:
https://github.com/habibnp01-prog/Entra-ID-User-Lifecycle-Automation?utm_source=chatgpt.com
#MicrosoftEntraID #MicrosoftGraph #PowerShell #Microsoft365 #Identity #IAM #Automation #Azure #CyberSecurity #IdentityManagement #MicrosoftCommunity