DriftMaester is a plug-and-play Azure Automation implementation of Maester with drift detection, trend tracking, and email reporting.
It helps the (slightly?) less tech savvy or time-constrained IT admins to not only run Maester, but to run it regularly and get alerted when their compliance drifts.
iex ((Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/jflieben/DriftMaester/main/Install-DriftMaester.ps1').Content)- Automatically deploys an Azure Automation setup (resource group, automation account, runtime, runbooks, storage).
- Sets correct authorizations in Graph, Azure and Exchange
- Runs Maester checks on a schedule (daily, weekly, or monthly).
- Stores result history in secure Azure Blob Storage.
- Detects configuration drift and highlights regressions each run
- Sends HTML reports by email.
- Writes a machine-readable NUnit test-results XML to blob storage each run, so CI systems like Azure DevOps can publish and trend the results (see Azure DevOps integration).
- Keeps its runtime modules and package set updated using the update runbook, so each run has the latest tests / Maester version.
DriftMaester runs in your tenant, if you want an even more hands-off option, consider the hosted option by Maester's original author, Merill.
Install-DriftMaester.ps1: interactive installer and updater for Azure resources and permissions.Update-DriftMaesterPermissions.ps1: lightweight script that only reconciles the managed identity's Entra ID directory roles and API permissions (no Azure resource changes), used when Maester adds tests that require more permissionsDriftMaester.Permissions.ps1: shared definitions (Graph/SharePoint/Exchange application permissions and directory roles) used by both the installer and the permission update script, so the two never drift apart.Runbooks/Invoke-DriftMaester.ps1: main runbook that executes Maester, compares results, mails reports, and writes an NUnit results XML to blob storage.Runbooks/Update-DriftMaester.ps1: runtime/package maintenance runbook.Runbooks/Remove-DriftMaester.ps1: uninstall runbook.Pipelines/azure-pipelines-driftmaester.yml: sample Azure DevOps pipeline that publishes the results XML to the Tests tab.docs/azure-devops-pipeline.md: guide for the Azure DevOps integration.
flowchart LR
A[Operator runs Install-DriftMaester.ps1] --> B[Azure Resource Group]
B --> C[Automation Account + Managed Identity]
C --> D[Runtime Environment driftmaester]
C --> E[Runbooks: Update + Invoke]
C --> F[Schedules + Job Schedules]
C --> G[Storage Account + maester container]
E --> H[Update runbook installs required modules/packages]
E --> I[Invoke runbook executes Maester checks]
I --> G
I --> J[HTML mail report to recipients]
- PowerShell 7+
- Az PowerShell modules installed locally:
Az.AccountsAz.ResourcesAz.AutomationAz.Storage
- Microsoft Graph PowerShell module:
Microsoft.Graph.Authentication
- Exchange Online module:
ExchangeOnlineManagement
Local requirements are automatically handled by the installer. If a required module is missing, Install-DriftMaester.ps1 installs it automatically.
You should run install with a Global Administator OR an account that can:
- Create/update resource groups and Azure Automation resources.
- Assign RBAC roles in the target resource group.
- Assign root-scope Reader roles to the managed identity (
/and/providers/Microsoft.aadiam). - Grant Microsoft Graph app roles and directory roles to the managed identity.
- Grant the SharePoint Online
Sites.FullControl.Allapp role to the managed identity, which the SharePoint Online tests need to read tenant settings. - Create Exchange Online service principal and management role assignment.
For root-scope assignment, the installer attempts normal assignment first. If denied, it temporarily elevates access using:
POST /providers/Microsoft.Authorization/elevateAccess?api-version=2015-07-01
and automatically removes the temporary elevation assignment afterward.
The runtime environment is configured with default packages and then maintained by the update runbook.
Some checks need additional access outside the Azure resources and Entra permissions configured by the installer. Configure these before enabling the related scans if you wish to include them
Copilot Studio, Power Platform, Dynamics, and Dataverse
Dataverse access is required for the Copilot Studio security tests that evaluate Copilot Studio agent configurations.
Create an application user in Power Platform:
- Go to the Power Platform Admin Center.
- Select the environment you want DriftMaester to evaluate.
- Go to Settings > Users + permissions > Application users.
- Click New app user > Add an app.
- Search for
driftmaesterand select it. - Select the correct business unit.
- Assign a security role with read access:
Basic Userfor simplicity, or- A custom role, for example
Maester Security Reader, with organization-level read access onAgent (bot),Agent component (botcomponent),User (systemuser), andConnection Reference (connectionreference).
- Click Create.
After this is configured, enable the workload in the installer with -IncludeCopilotAndDataverse $true, or tick the matching checkbox in the GUI. If you've already installed you can rerun the installer or you can modify the scheduled job in Azure directly.
Azure DevOps
For Azure DevOps scans, the DriftMaester managed identity must be added to the Azure DevOps organization at https://dev.azure.com/ORGNAMEHERE/_settings/users and granted access to every project that should be scanned.
For maximum test coverage, also add the DriftMaester managed identity to Security > Permissions > Project Collection Administrators.
Copy/paste ready command to run directly from GitHub:
iex ((Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/jflieben/DriftMaester/main/Install-DriftMaester.ps1').Content)- Follow prompts in order:
- Select Azure subscription.
- Enter resource group name.
- Enter deployment location (press Enter for
westeurope). - Choose run frequency, run time, and time zone.
- Enter report recipients and optional sender/org/tenant/subject/report behavior/workload settings.
- Approve sign-in prompts for Azure, Graph, and Exchange Online when requested.
Runs automatically if any required parameter is missing:
.\Install-DriftMaester.ps1The native PowerShell form opens on Windows:
- Select your Azure subscription
- Enter resource group name, location
- Choose run frequency (daily, weekly, monthly), time, and time zone
- Enter email recipients and optional settings
- Choose whether reports are sent only when drift is detected, or after every run
- Choose whether Copilot, Power Platform, Dynamics, and Dataverse checks are included
Installer preview:
On non-Windows platforms, provide all required parameters or use the console prompts.
For scripting or when you have all parameters ready, supply them all:
.\Install-DriftMaester.ps1 -Subscription "sub-id" -ResourceGroup "rg-drift" `
-Recipients "admin@company.com" -Frequency "daily" -TimeOfDay "02:00" -TimeZone "W. Europe Standard Time" `
-AlwaysSendReport $true -IncludeCopilotAndDataverse $trueThe installer runs mostly silently except for logins to Graph and Exchange if you're not already logged in.
-AlwaysSendReport $true passes AlwaysSendReport to the scheduled Invoke-DriftMaester runbook. By default this is $false, which means DriftMaester sends mail on the first run and when drift is detected. Set it to $true when you want a report after every scheduled run, even if nothing changed.
-TimeZone "W. Europe Standard Time" sets the time zone used by the Azure Automation schedules. If omitted, the installer uses the local system time zone. Use the time zone id expected by Azure Automation, for example W. Europe Standard Time.
-IncludeCopilotAndDataverse $true passes includeCopilotAndDataverse to the scheduled Invoke-DriftMaester runbook. By default this is $false, which skips Copilot Studio, Power Platform, Dynamics, and Dataverse-backed Maester checks and suppresses Dataverse connection warnings. Set it to $true when the managed identity has access to the Dataverse environment and you want those checks included.
Mixed Mode (Partial Parameters)
Provide some parameters and use GUI for the rest:
.\Install-DriftMaester.ps1 -Subscription "sub-id" -Recipients "admin@company.com"The GUI launches with those fields pre-populated.
- Resource group (if missing)
- Automation account with system-assigned managed identity
- Storage account and
maesterblob container - Runtime environment
driftmaester - Runbooks
Invoke-DriftMaesterandUpdate-DriftMaester - Schedules and job schedules
- Azure RBAC role assignments for managed identity
- Root-scope Reader role assignments for managed identity
- Graph API app-role assignments and directory roles
- Exchange Online service principal +
View-Only Configurationassignment
| Area | Granted by installer | Why |
|---|---|---|
| Azure RBAC (resource group) | Reader | Read tenant resources referenced by checks |
| Azure RBAC (storage account) | Storage Blob Data Contributor | Store reports/history and cached test content |
| Azure RBAC (automation account) | Automation Contributor | Manage runbook jobs and schedules |
| Azure RBAC (root scopes) | Reader on / and /providers/Microsoft.aadiam |
Tenant-wide Azure visibility needed by some checks |
| Microsoft Graph app roles | Listed in RequiredGraphApplicationPermissions in installer |
Required by Maester Graph workloads |
| Exchange app role | Exchange.ManageAsApp |
Exchange Online and compliance checks |
| Directory roles | Global Reader, Teams Reader, Azure DevOps Administrator (optional) | Workload coverage for corresponding tests |
At install completion, DriftMaester prints an access report with the effective grant set.
- Azure Automation: Basic tier run time cost applies. If jobs run long and often, monthly cost can exceed free minutes.
- Azure Storage: Blob storage and transaction costs for result history, reports, and optional cache/config artifacts.
This follows official documentation at Maester
- DriftMaester runs automatically on its configured schedule.
- Update runbook runs one hour before invoke runbook.
- Invoke runbook stores results and sends report emails.
Report preview:
Email summary received after a run:
You can start either runbook manually from Azure Automation:
Update-DriftMaesterfirst (optional but recommended)Invoke-DriftMaester
DriftMaester runs the Maester tests and drift detection unattended in Azure Automation, so you do not need to move that logic into a pipeline the way the Maester Azure DevOps guide does. Instead, every run writes an NUnit test-results XML to blob storage, and a small, stateless pipeline downloads the latest results, checks they are recent, and publishes them to the Azure DevOps Tests tab. Regressions and improvements across runs then show up in the native test history.
See docs/azure-devops-pipeline.md for setup and Pipelines/azure-pipelines-driftmaester.yml for the sample pipeline.
Re-running Install-DriftMaester.ps1 is supported and intended.
Idempotent behavior includes:
- Reuse existing resources where possible.
- Reapply runbook content from this repository.
- Recreate deterministic schedules/job schedules.
- Skip role assignments and permissions already present.
When new Maester tests need extra Graph/SharePoint/Exchange application permissions or Entra ID directory
roles, you don't have to re-run the full installer. Update-DriftMaesterPermissions.ps1 reconciles just the
managed identity's API permissions and directory roles from the shared DriftMaester.Permissions.ps1
definitions. It never touches Azure resources, runbooks, schedules, storage, Azure RBAC, or Exchange mail-send
RBAC, and it is idempotent (existing grants are skipped).
.\Update-DriftMaesterPermissions.ps1 -Subscription "sub-id-or-name" -ResourceGroup "rg-driftmaester"Or straight from GitHub:
iex ((Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/jflieben/DriftMaester/main/Update-DriftMaesterPermissions.ps1').Content)Pass -DevOps to also (re)assign the Azure DevOps Administrator directory role; it is skipped
otherwise.
Use the uninstaller for controlled cleanup:
.\Remove-DriftMaester.ps1 -Subscription "sub-id-or-name" -ResourceGroup "rg-name"Notes:
- If the resource group is tagged as DriftMaester-managed, full group deletion is offered by default.
- In shared resource groups, scoped cleanup is performed (runbooks/schedules/permissions; optional data removal).
- Use
-KeepDatato preserve storage and historical artifacts.
- Re-run installer to reset everything
- Run
Update-DriftMaestermanually and check runbook output.
- Ensure account is Global Administrator and elevation is allowed.
- Use an account with enough admin rights for consent, app role assignment, and EXO RBAC.
- Verify tenant context in prompts before confirming.
- The installer reuses your Azure sign-in for Microsoft Graph, so you normally sign in once and no separate Graph prompt appears. If that account lacks the required Entra permissions, it falls back to an interactive Graph sign-in and you can use a Global Administrator account there.
- If that interactive fallback fails with a type load error such as
Could not load type 'Microsoft.Identity.Client.IMsalSFHttpClientFactory'and no sign-in browser appears, run the installer in a fresh PowerShell session. PnP.PowerShell, MicrosoftTeams and ExchangeOnlineManagement each ship their ownMicrosoft.Identity.Client(MSAL) and .NET keeps only the first version loaded in a session. The installer reports which module loaded the conflicting copy.
-RunNowwaits for the update runbook to finish and then starts the invoke runbook.- If update fails, invoke is not started.
ReportDeliverysupportsauto,attach, andlinkmodes.- If attachment payload approaches Graph
sendMailrequest limits, oversized attachments are skipped and warnings are logged.
- Invoke runbook sends a failure notification email when execution fails.
- Managed identity is used for runbook runtime auth.
- Results are stored in your own Azure Storage account.
- Email is sent through Graph API using configured sender behavior.
- Review role assignments and granted app permissions before production use.
Free for non-commercial use.
Although I've contributed to the open source Maester project, I am not directly affiliated with it and this tool only serves as a wrapper / feature add to make your life easier :)
Commercial use requires a license: https://jsolve.nl/commercial-use.html
- Site: https://jsolve.nl
- Blog: https://www.lieben.nu/liebensraum/
- Author: Jos Lieben (JSolve B.V.)


