Automated Microsoft 365 Security Posture Monitoring with Maester

It’s not often that someone releases a new product that is, totally free, open source and awesome. When it first popped up in my feed, I stopped what I was doing, ran 5 lines of code and received a beautifully designed security report telling me everything I needed to improve in my Microsoft 365 tenant. Better yet, it’s just a PowerShell module, so I can automate it anyway I like. 

The product is Maester. An open-source PowerShell tool developed by @Merill Fernando, @Fabian Bader & @Thomas Nuanheim.

Keep reading below to follow my experience from installing the module and running the report. Then automating the report using Azure Automation.

How to install the Maester PowerShell module

There are two dependencies for the Maester module, these are:

  • Microsoft.Graph.Authentication (v2.2.0): Utilised for obtaining access tokens for Microsoft Graph. 
  • Pester (v5.5.0): A popular testing framework for PowerShell.

To install the Maester PowerShell module, run the following commands: (both dependencies will be deployed automatically if you skipped the first line)

Install-Module Pester -SkipPublisherCheck -Force -Scope CurrentUser
Install-Module Maester -Scope CurrentUser

md maester-tests
cd maester-tests
Install-MaesterTests .\tests

Run a Microsoft 365 security report

Running the pre-defined security assessment for your Microsoft 365 tenant could not be easier, simply connect to Microsoft Graph and invoke the primary script.

To connect to Microsoft Graph, Maester includes a custom cmdlet ‘Connect-Maester‘ which utilises the Connect-MgGraph cmdlet and some predefined scopes, these are: 

  • Directory.Read.All
  • Policy.Read.All
  • Reports.Read.All
  • DirectoryRecommendations.Read.All

The Get-MtGraphScope cmdlet will also expose which permissions are consented to by default using the Connect-Maester cmdlet, this will be useful as the tool expands and hence the scopes change.

Connect to Maester using the provided commands

Use the below commands to connect to Microsoft Graph using the Maester option.

#Connect using the default scopes

#Connect using the default scopes + send mail as you permission
Connect-Maester -SendMail

Connect to Maester using the Microsoft Graph PowerShell SDK

Use the below command to connect to Microsoft Graph using the Graph SDK.

$Scopes = @(

Connect-MgGraph -Scopes $Scopes

Run the built-in security assessment

Use the below command to run the built-in security assessment. By default, the output will be located in the current path of your PowerShell session plus ./test-results.


You can modify the output path with the following example

Invoke-Maester -OutputFolder C:\temp

After a minute or two, your report should automatically open and look like the following:

Maester report example
Maester report example

You can then select the info button next to each test to review detailed information on the test and test results. 

Expanded result
Expanded result

Automating the report with Azure Automation

The great thing about Maester is that it is designed to be run using DevOps services, currently supporting Azure DevOps and GitHub Actions. (Both hyperlinks will take you to the relevant docs).

In this section, I will show you how to automate the report using Azure Automation. Follow the below steps to setup automated reporting with Maester using Azure Automation:

Step 1: Create an Automation Account

1. Login to and search for Automation Accounts.

2. Click Create.

3. Select your subscription, and resource group, then define the account name and region.

4. Click Next, leave System assigned managed identity selected and click Next.

5. Complete the final pages and click Create.

Step 2: Assign permissions to the System-assigned managed identity

Now the Automation Account has been created, a service principal (or Managed Identity) will also have been created. To run the Maester report using this Managed Identity, the required permissions (as listed above) must be granted to it.

To grant the necessary permissions to the Managed Identity, use the below script and when prompted, sign in with a Global Administrator account. 

Ensure you modify the first line with the name of the Automation Account you created earlier (this will match the name of the System-assigned managed identity that was created).

(Remember, the Mail.Send permission in the application context, applied to all mail users, consider creating an application access policies to limit the scope of Exchange access.).

$ManagedIdentityName = "M365SecurityAssessment"

Connect-MgGraph -Scopes $Scopes

$Scopes = @(

$permissions =  @(

$getPerms = (Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'").approles | Where {$_.Value -in $permissions}
$ManagedIdentity = (Get-MgServicePrincipal -Filter "DisplayName eq '$ManagedIdentityName'")
$GraphID = (Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'").id

foreach ($perm in $getPerms){
    New-MgBetaServicePrincipalAppRoleAssignment -ServicePrincipalId $ManagedIdentity.Id `
    -PrincipalId $ManagedIdentity.Id -ResourceId $GraphID -AppRoleId $

Step 3: Load the required PowerShell modules

1. Open your Automation account and select Runtime Environments.

2. Click Create.

3. Define a Name, select PowerShell as the language and Runtime version as 7.2.

4. Click Next.

5. On the Packages tab, click Add from gallery and select the following packages:

  • Maester
  • Microsoft.Graph.Authentication
  • Pester
  • Microsoft.Graph.Users.Actions (optional to send the Zipped report files via email)

6. Click Next and Create.

Step 4: Create a new Runbook

1. Under Process Automation click Create.

2. Select Create new next to Runbook, then define a name for the Runbook.

3. Next to Runbook type, select PowerShell, then choose the Runtime Environment you previously created.

4. Click Create. You will be taken to the Edit in portal page.

5. Copy and paste this essential code:

The below example will connect to Microsoft Graph using the managed identity, generate the report in a custom location and email said report to the recipient you define (ensure you update the $MailRecipient variable below).

Connect-MgGraph -Identity

#Define mail recipient
$MailRecipient = "Define Sender/Recipient"

#create output folder
$date = (Get-Date).tostring("yyyyMMdd-HHmm")
$FileName = "MaesterReport" + $Date + ".zip"

$TempOutputFolder = $env:TEMP + $date
if (!(Test-Path $TempOutputFolder -PathType Container)) {
    New-Item -ItemType Directory -Force -Path $TempOutputFolder

#Run Maester report
cd $env:TEMP
md maester-tests
cd maester-tests
Install-MaesterTests .\tests
Invoke-Maester -MailUserId $MailRecipient -MailRecipient $MailRecipient -OutputFolder $TempOutputFolder

Once the Runbook has completed its first cycle, you will receive the security report via email.

Maester test report email
Maester test report email

The only issue is that the report utilises a simplified HTML replacement that cannot be interested in like the .HTML file output. To also receive a separate file with a Zipped copy of the source report file, add the following code to your Runbook.

#Compress output to Zip
Compress-Archive -Path $TempOutputFolder -DestinationPath "$TempOutputFolder\$FileName"

#Send Zip attachment with Microsoft Graph
$Attachment = "$TempOutputFolder\$FileName"
$MailAttachement = [Convert]::ToBase64String([IO.File]::ReadAllBytes($Attachment))
$body = @{
	message = @{
		subject = "Maester Test Results Files"
		body = @{
			contentType = "Text"
			content = "Please see your Maester Test Results files attached"
		toRecipients = @(
				emailAddress = @{
					address = "$MailRecipient"
        attachments = @(
				"@odata.type" = "#microsoft.graph.fileAttachment"
				name = "$FileName"
				contentType = "text/plain"
				contentBytes = $MailAttachement
	saveToSentItems = "false"
Send-MgUserMail -UserId $MailRecipient -BodyParameter $body

You will then receive a separate email with the source files attached.

Maester report files
Maester report files

Define a report schedule

The final step is to define how often you want the report to run. When you do this, you should ensure you schedule it at a frequency where you will have the time to meaningfully review and action any necessary changes. I recommended running it monthly. Follow the below steps to configure a monthly schedule.

1. From the Azure Portal, open your Automation Account.

2. Under Shared Resources, select Schedules.

3. Click Add a schedule and define a name.

4. Set the Recurrence to Recurring, select Recur every 1 Month and set Run on last day of month to Yes.

5. Click Create.

6. Under Process Automation, select Runbooks and open your Runbook.

7. Click Schedules > Add a schedule.

8. Choose your schedule and click OK.

You have now successfully deployed Maester using Azure Automation!

Daniel Bradley

My name is Daniel Bradley and I work with Microsoft 365 and Azure as an Engineer and Consultant. I enjoy writing technical content for you and engaging with the community. All opinions are my own.

Leave a Reply