Make Unattended Scripts for Graph and Exchange

In the world of Office 365 administration, automation is the key to efficiency. As an Office 365 administrator, you’re likely familiar with the power of scripts to perform tasks within Microsoft Graph and Exchange Online. However, relying on manual interactive scripts can be time-consuming and potentially error-prone. That’s where the magic of app registrations comes into play. The process to convert manual Interactive Microsoft Graph API and Exchange Online scripts to unattended scripts is an easy and straightforward way to get some time back in your day.

In this blog post, we’ll explore the process of converting manual interactive Microsoft Graph and Exchange Online scripts into unattended scripts using app registrations. By doing so, you’ll unlock a new level of automation and reliability that can save you time and improve your overall administration workflow.

Why Move from Interactive to Unattended Scripts?

Interactive scripts require user input, making them suitable for scenarios that demand human intervention. However, when dealing with repetitive tasks that follow the same pattern, unattended scripts are the way to go. They allow you to automate processes without requiring constant supervision, reducing the risk of human errors and speeding up your workflow.

YouTube Video

if you want to watch the process and instead this is the first half of this video.

Automate your Scripting in the Cloud

Exchange Online, Microsoft Graph API, scripting, and App Registration Resources

All of the PowerShell scripts used in this process are available in my Git Hub Repo.

Microsoft’s Documentation

If you want to review options for this solution, here is all of Microsoft’s documentation on this topic.

In this process we’re taking this manual script that loops through user accounts and sorts them into groups based on mailbox type and convert it to an unattended script.

Manual script

#Connections
Connect-ExchangeOnline
Connect-MGgraph 
#Variables 
$UserMailboxGroupID = 'Some Group ID'
$UserMailboxGroupMembers = Get-MgGroupMember -GroupId $UserMailboxGroupID -All
$SharedMailboxGroupID = 'Some Group ID'
$SharedMailboxGroupMembers = Get-MgGroupMember -GroupId $SharedMailboxGroupID -All 
$UserMailboxes = Get-ExoRecipient -ResultSize Unlimited -RecipientTypeDetails UserMailbox
$SharedMailboxes = Get-ExoRecipient -ResultSize Unlimited -RecipientTypeDetails SharedMailbox
$RoomMailboxes = Get-ExoRecipient -ResultSize Unlimited -RecipientTypeDetails RoomMailbox
#Log File Location
$timestamp = Get-Date -Format 'yyyy-MM-dd_HH-mm-ss'
$UserLogFile = "C:\Temp\$timestamp-BackupGroupUserLog.txt"
$SharedLogFile = "C:\Temp\$timestamp-BackupGroupSharedLog.txt"
$RoomLogFile = "C:\Temp\$timestamp-BackupGroupRoomLog.txt"
# User Mailbox Processing 
foreach ($UserMailbox in $UserMailboxes) {
    if($UserMailboxGRoupMembers.Id -contains $UserMailbox.ExternalDirectoryObjectId) {
        Write-Host "$UserMailbox is a member of the Group"
        Add-Content -Path $UserLogFile "$UserMailbox is a member of the Group"
    } else {
        Write-Host "$UserMailbox is NOT member of the group. Let's fix that!"
        Add-Content -Path $UserLogFile "$UserMailbox NOT member of the group. Let's fix that!"
        New-MgGroupMember -GroupId $UserMailboxGroupID -DirectoryObjectId $UserMailbox.ExternalDirectoryObjectId
        Write-Host "$UserMailbox is a member of the group now!"
        Add-Content -Path $UserLogFile "$UserMailbox is a member of the group now!"
    }
}
# Shared Mailbox Processing
foreach ($SharedMailbox in $SharedMailboxes) {
    if($SharedMailboxGroupMembers.Id -contains $SharedMailbox.ExternalDirectoryObjectId) {
        Write-Host "$SharedMailbox is a member of the Group"
        Add-Content -Path $SharedLogFile "$SharedMailbox is a member of the Group"
    } else {
        Write-Host "$SharedMailbox is NOT member of the group. Let's fix that!"
        Add-Content -Path $SharedLogFile "$SharedMailbox NOT member of the group. Let's fix that!"
        New-MgGroupMember -GroupId $SharedMailboxGroupID -DirectoryObjectId $SharedMailbox.ExternalDirectoryObjectId
        Write-Host "$SharedMailbox is a member of the group now!"
        Add-Content -Path $SharedLogFile "$SharedMailbox is a member of the group now!"
    }
}
# Room Mailbox Processing
foreach ($RoomMailbox in $RoomMailboxes) {
    if($SharedMailboxGroupMembers.Id -contains $RoomMailbox.ExternalDirectoryObjectId) {
        Write-Host "$RoomMailbox is a member of the Group"
        Add-Content -Path $RoomLogFile "$RoomMailbox is a member of the group"
    } else {
        Write-Host "$RoomMailbox is NOT member of the group. Let's fix that!"
        Add-Content -Path $RoomLogFile "$RoomMailbox is NOT member of the group. Let's fix that!"
        New-MgGroupMember -GroupId $SharedMailboxesGroupID -DirectoryObjectId $RoomMailbox.ExternalDirectoryObjectId
        Write-Host "$RoomMailbox is a member of the group now!"
        Add-Content -Path $RoomLogFile "$RoomMailbox is a member of the group now!"
    }
}

Unattended Script

#Connection to M365 using Azure Automation and non interactive login 
Connect-ExchangeOnline -CertificateThumbPrint 'CertThumbprint' -AppID 'AppID' -Organization 'tenant Url'
Connect-MGgraph -ClientID 'AppID' -TenantId 'GUID of Tenant' -CertificateThumbPrint 'CertThumbprint'
#Variables 
$UserMailboxGroupID = 'Some Group ID'
$UserMailboxGroupMembers = Get-MgGroupMember -GroupId $UserMailboxGroupID -All
$SharedMailboxGroupID = 'Some Group ID'
$SharedMailboxGroupMembers = Get-MgGroupMember -GroupId $SharedMailboxGroupID -All 
$UserMailboxes = Get-ExoRecipient -ResultSize Unlimited -RecipientTypeDetails UserMailbox
$SharedMailboxes = Get-ExoRecipient -ResultSize Unlimited -RecipientTypeDetails SharedMailbox
$RoomMailboxes = Get-ExoRecipient -ResultSize Unlimited -RecipientTypeDetails RoomMailbox
# User Mailbox Processing 
foreach ($UserMailbox in $UserMailboxes) {
    if($UserMailboxGroupMembers.Id -contains $UserMailbox.ExternalDirectoryObjectId) {
        Write-Output "$UserMailbox is a member of the Group"
    } else {
        Write-Output "$UserMailbox is NOT member of the group. Let's fix that!"
        New-MgGroupMember -GroupId $UserMailboxGroupID -DirectoryObjectId $UserMailbox.ExternalDirectoryObjectId
        Write-Output "$UserMailbox is a member of the group now!"
    }
}
# Shared Mailbox Processing
foreach ($SharedMailbox in $SharedMailboxes) {
    if($SharedMailboxGroupMembers.Id -contains $SharedMailbox.ExternalDirectoryObjectId) {
        Write-Output "$SharedMailbox is a member of the Group"
    } else {
        Write-Output "$SharedMailbox is NOT member of the group. Let's fix that!"
        New-MgGroupMember -GroupId $SharedMailboxGroupID -DirectoryObjectId $SharedMailbox.ExternalDirectoryObjectId
        Write-Output "$SharedMailbox is a member of the group now!"
    }
}
# Room Mailbox Processing
foreach ($RoomMailbox in $RoomMailboxes) {
    if($SharedMailboxGroupMembers.Id -contains $RoomMailbox.ExternalDirectoryObjectId) {
        Write-Output "$RoomMailbox is a member of the Group"
    } else {
        Write-Output "$RoomMailbox is NOT member of the group. Let's fix that!"
        New-MgGroupMember -GroupId $SharedMailboxesGroupID -DirectoryObjectId $RoomMailbox.ExternalDirectoryObjectId
        Write-Output "$RoomMailbox is a member of the group now!"
    }
} 

More Scripts with comments are available in the GitHub Repo for reference.

Step-by-Step Conversion Process:

  • Create an App Registration: Start by registering your app in the Azure Active Directory portal. This app registration will represent your script’s identity and permissions.

Using an account that has Application Administrator Role or higher, click on New Registration to get started.

Give the App Registration an Intuitive name and select the first account type of single tenant.

The Redirect URI is less important in this specific use case but fill in the domain of your environment.

Now you have sucessfully created an App Registration.

Make note of the following items, you’ll need them later in your script.

  • Application (client) ID
  • Directory (tenant) ID
  • Configure Permissions: Specify the necessary permissions your script requires to access Microsoft Graph and Exchange Online. Be mindful to grant the least privileges necessary to perform the tasks.
    • Exchange Online requires you to edit the manifest of the app registration to allow the Exchange Manage As App permission. After that all additional roles and permissions are added through the traditional routes.

Follow the Microsoft Guide to replace this section of the manifest and enable the Manage as App permission for Exchange Online.

Click Save to Save your Changes.
Then head over to API Permissions to add your Exchange Online and Microsoft Graph API Permissions.

When you first arrive at the API Permissions Page you’ll see the Listing for Exchange Online. You can grant admin permission for this one now. Or you can add all your API Access Rights for Exchange Online, Microsoft Graph API, and any other APIs your script needs. Then Approve all of them at once.

Click Add and start adding all of your API Permissions.

In this case, I’m adding the following APIs

Exchange Online

  • Exchange.ManageAsApp

Microsoft Graph API

  • Group.ReadWrite.All
  • GroupMember.ReadWrite.All

Then Click on Grant admin consent for Organization Name to approve all the Exchange Online, Microsoft Graph API, and other APIs all at once.

Next, we need to add an Exchange Online Azure Role to the App Registration.

In the Azure portal search for Azure Roles and it will Bring you to the correct place.

Seach for Exchange Recipient Administrator or other required roles and select it.

This script only requires Security Reader so search for the name of your App Registration and add it to your role.

  • Generate App Secret or Certificate: Obtain an app secret or certificate to securely authenticate your unattended script without manual input.
    • App Secret is crossed out because Microsoft Graph doesn’t support it so we’re going to generate a Self-Signed certificate and use it for both Exchange Online and Graph APIs

You can copy and paste the certificate commands from here.

Just make sure to replace all of the Microsoft demo stuff like contoso with the values for your organization.

By default, the commands with export the certificate in the root of your user profile. you can provide a full path if you want the certificate exported somewhere else.

Go to the Certificates & Secrets section for your App Registration and select Certificates. Then click upload.

Upload Your Certificate to your App Registration.

Select your Certificate and upload it. Also give it a description to make it easier to track down in the future.

After the certificate is uploaded, Open the certificate on your computer to get the full thumbprint. You need that in the next step to make Unattended Scripts for Graph and Exchange.

  • Update Script Code: Modify your existing script to use the credentials obtained from the app registration. Replace any interactive authentication methods with the app’s authentication flow.
    • This part is straight forward you update your connection properties from the manual prompts to everything we just configured.
    • The changes are below and there are multiple examples on my GitHub.
    • You need the following items from above or your tenant
      • App Registration ID – This is AppID below
      • The Certificate Thumbprint – This is CertThumbprint Below
      • The Tenant ID – Guid of Tenant from Directory ID in the App Registration.
      • Organization – This the Tenant URL in the syntax of Organization.OnMicrosoft.com
        • This was created when you first set up your Microsoft Tenant and they needed a place holder domain to route e-mail until you brought your production email domains into Microsoft 365.
        • Mine is TDSheridanLab.onmicrosoft.com for example
#Manual Script Prompting Sign ins
#Connections
Connect-ExchangeOnline
Connect-MGgraph 
#Connection to M365 using Azure Automation and non interactive login 
Connect-ExchangeOnline -CertificateThumbPrint 'CertThumbprint' -AppID 'AppID' -Organization 'tenant Url'
Connect-MGgraph -ClientID 'AppID' -TenantId 'GUID of Tenant' -CertificateThumbPrint 'CertThumbprint'
  • Testing and Debugging: Thoroughly test your unattended script in a controlled environment before deploying it in a production setting. Address any issues that arise during testing.
    • The App Registration sign in and audit logs are your friend here. but beware there is an up to 20-minute delay in when you test your script and when the logs are available to view in the portal.
  • Implement Error Handling: Ensure your unattended script includes comprehensive error handling mechanisms. This will help identify and address any issues that might occur during execution.
  • Schedule Execution: Depending on your needs, schedule your unattended script to run at specific intervals using tools like PowerShell’s Task Scheduler or Azure Automation.

Benefits of Unattended Scripts:

  1. Consistency: Unattended scripts execute the same way every time, eliminating the variability introduced by human intervention.
  2. Time Efficiency: Automated tasks run in the background, allowing you to focus on other important aspects of your role.
  3. Reduced Errors: App registrations provide a secure and reliable method for authentication, minimizing the risk of mistakes that manual scripts might introduce.
  4. Scalability: Unattended scripts can easily be scaled up to handle larger workloads without requiring additional human effort.

By making the transition from manual interactive scripts and converting to Microsoft Graph & Exchange unattended scripts using app registrations, You’ll elevate your Office 365 administration game to new heights. The increased efficiency, reliability, and time savings will empower you to focus on strategic initiatives and higher-value tasks, leaving routine maintenance in the hands of automation.

So, take the leap and unlock the true potential of Office 365 administration with unattended scripts. Your future self will thank you for it.

Next, we’ll show you how to automate this script in Azure Automation.