Introduction
Monitoring changes made to Microsoft 365 Groups is a critical concern for system administrators. PowerShell scripts developed several years ago require complete modernization to adapt to the architectural changes in the Microsoft 365 ecosystem. This article presents an advanced technical approach to implementing an automated group change reporting system.
Obsolescence of old scripts
PowerShell scripts dating from 2016 use deprecated authentication methods such as the Credential parameter for Exchange Online PowerShell, which will be removed in July 2026.
Technical architecture of the reporting system
The modern approach relies on leveraging unified audit logs rather than maintaining local XML files. This architecture offers several technical advantages:
- Elimination of local dependencies: No need to maintain configuration files on disk
- Centralized source of truth: Audit events provide complete history
- Improved scalability: Compatible with Azure Automation environments
- Enhanced security: Modern authentication via Microsoft Graph
Required technical components
The system relies on several critical PowerShell modules:
1# Required modules for implementation2Import-Module Microsoft.Graph.Authentication3Import-Module Microsoft.Graph.Groups4Import-Module ExchangeOnlineManagement5Import-Module ImportExcel # Optional for XLSX exportImplementation of the advanced reporting script
Establishing secure connections
The first step is to establish authenticated connections to Microsoft APIs:
1# Connect to Exchange Online with modern authentication2Connect-ExchangeOnline -UseRPCEncryption3 4# Connect to Microsoft Graph with specific permissions5Connect-MgGraph -Scopes "Group.Read.All", "AuditLog.Read.All", "Mail.Send"Retrieving Microsoft 365 Groups
Using Microsoft Graph to obtain the complete list of groups:
1# Optimized retrieval of Microsoft 365 Groups2$M365Groups = Get-MgGroup -Filter "groupTypes/any(c:c eq 'Unified')" -All3 4# Create a hash table to optimize lookups5$GroupsHashTable = @{}6foreach ($Group in $M365Groups) {7 $GroupsHashTable[$Group.Id] = @{8 DisplayName = $Group.DisplayName9 Mail = $Group.Mail10 CreatedDateTime = $Group.CreatedDateTime11 Visibility = $Group.Visibility12 }13}Querying unified audit logs
Searching for audit events from the last 30 days:
1# Define the audit period2$StartDate = (Get-Date).AddDays(-30)3$EndDate = Get-Date4 5# Search for group modification events6$AuditSearchResults = Search-UnifiedAuditLog `7 -StartDate $StartDate `8 -EndDate $EndDate `9 -Operations "Add group", "Update group", "Delete group" `10 -ResultSize 500011 12# Filter for Microsoft 365 Groups only13$GroupAuditEvents = $AuditSearchResults | Where-Object {14 $_.AuditData | ConvertFrom-Json | Where-Object {15 $_.ModifiedProperties -contains "GroupType" -and16 $_.ModifiedProperties.NewValue -contains "Unified"17 }18}Processing and analyzing audit data
In-depth analysis of collected events:
1# Initialize result collections2$NewGroups = [System.Collections.Generic.List[PSObject]]::new()3$UpdatedGroups = [System.Collections.Generic.List[PSObject]]::new()4$DeletedGroups = [System.Collections.Generic.List[PSObject]]::new()5 6# Process audit events7foreach ($Event in $GroupAuditEvents) {8 $AuditData = $Event.AuditData | ConvertFrom-Json9 10 switch ($Event.Operations) {11 "Add group" {12 $NewGroups.Add([PSCustomObject]@{13 GroupName = $AuditData.ObjectId14 CreatedBy = $AuditData.UserId15 CreationTime = $Event.CreationDate16 ClientApplication = $AuditData.ClientApplication17 })18 }19 "Update group" {20 $UpdatedGroups.Add([PSCustomObject]@{21 GroupName = $AuditData.ObjectId22 ModifiedBy = $AuditData.UserId23 ModificationTime = $Event.CreationDate24 ModifiedProperties = $AuditData.ModifiedProperties25 })26 }27 "Delete group" {28 $DeletedGroups.Add([PSCustomObject]@{29 GroupName = $AuditData.ObjectId30 DeletedBy = $AuditData.UserId31 DeletionTime = $Event.CreationDate32 })33 }34 }35}Generating advanced HTML report
Creating an HTML report with professional formatting:
1# HTML template with inline CSS2$HTMLTemplate = @"3<!DOCTYPE html>4<html>5<head>6 <title>Microsoft 365 Groups Modification Report</title>7 <style>8 body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }9 .header { background-color: #0078d4; color: white; padding: 20px; }10 .section { margin: 20px 0; }11 table { border-collapse: collapse; width: 100%; }12 th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }13 th { background-color: #f2f2f2; }14 .new { background-color: #d4edda; }15 .updated { background-color: #fff3cd; }16 .deleted { background-color: #f8d7da; }17 </style>18</head>19<body>20 <div class="header">21 <h1>Microsoft 365 Groups Modification Report</h1>22 <p>Period: {0} to {1}</p>23 </div>24"@ -f $StartDate.ToString("yyyy-MM-dd"), $EndDate.ToString("yyyy-MM-dd")25 26# Add data sections27$HTMLReport = $HTMLTemplate28$HTMLReport += "<div class='section new'><h2>New groups ({0})</h2>" -f $NewGroups.Count29# [HTML table generation code...]30$HTMLReport += "</body></html>"Exporting data and automated sending
Finalization with export and email delivery:
1# Export to Excel if ImportExcel module is available2if (Get-Module -ListAvailable -Name ImportExcel) {3 $ExportPath = "C:\Reports\M365Groups_$(Get-Date -Format 'yyyyMMdd').xlsx"4 $NewGroups | Export-Excel -Path $ExportPath -WorksheetName "New" -AutoSize5 $UpdatedGroups | Export-Excel -Path $ExportPath -WorksheetName "Modified" -AutoSize -Append6 $DeletedGroups | Export-Excel -Path $ExportPath -WorksheetName "Deleted" -AutoSize -Append7} else {8 # Fallback to CSV9 $ExportPath = "C:\Reports\M365Groups_$(Get-Date -Format 'yyyyMMdd').csv"10 $AllChanges | Export-Csv -Path $ExportPath -NoTypeInformation -Encoding UTF811}12 13# Send report via email using Microsoft Graph14$Message = @{15 Subject = "Microsoft 365 Groups Modification Report - $(Get-Date -Format 'dd/MM/yyyy')"16 Body = @{17 ContentType = "HTML"18 Content = $HTMLReport19 }20 ToRecipients = @(21 @{22 EmailAddress = @{23 Address = "admin@contoso.com"24 }25 }26 )27 Attachments = @(28 @{29 "@odata.type" = "#microsoft.graph.fileAttachment"30 Name = Split-Path $ExportPath -Leaf31 ContentBytes = [Convert]::ToBase64String([IO.File]::ReadAllBytes($ExportPath))32 }33 )34}35 36Send-MgUserMail -UserId "service@contoso.com" -Message $MessageOptimizations and performance considerations
Optimizing Graph queries
For environments with many groups, implement pagination and use OData filters to reduce network load.
Delta Query API limitations
Although Microsoft Graph offers a Delta Query feature for groups, it has important limitations:
- The PowerShell SDK does not expose the necessary delta link
- Requires prior baseline establishment
- Implementation complexity for marginal performance gain
1# Example of Delta query (limited)2# Note: Requires direct REST implementation3$DeltaUri = "https://graph.microsoft.com/v1.0/groups/delta"Integration with Azure Automation
The script can be easily adapted for Azure Automation with the following modifications:
Managed identity configuration
1# Authentication via managed identity2Connect-MgGraph -Identity3 4# Retrieve parameters from Azure Key Vault5$RecipientEmail = Get-AzKeyVaultSecret -VaultName "MyKeyVault" -Name "ReportRecipient" -AsPlainTextScheduling executions
Creating a scheduled Runbook for automatic execution:
- Recommended frequency: Weekly or monthly
- Execution window: Off-peak hours to optimize performance
- Error handling: Implement retry logic and alerts
| Method | Advantages | Disadvantages |
|---|---|---|
| Local script | Full control, easy debugging | Manual maintenance, local dependencies |
| Azure Automation | Automated execution, managed identities | Configuration complexity, Azure costs |
| Azure Functions | Serverless, automatic scaling | Runtime limitations, learning curve |
Analysis of modification patterns
Analysis of the data reveals interesting patterns:
- System modifications: Primarily via Office 365 Exchange Online app
- Automated applications: Group Configuration Processor, Microsoft Approval Management
- User actions: Generally via Teams or SharePoint Online
Technical insight
Automatic modifications represent approximately 70% of audit events, reflecting Microsoft 365's internal synchronization processes.
Security and compliance
Minimum required permissions
The principle of least privilege must be applied:
1# Microsoft Graph permissions strictly necessary2$RequiredScopes = @(3 "Group.Read.All", # Read groups4 "AuditLog.Read.All", # Access audit logs5 "Mail.Send" # Send emails6)Sensitive data management
- Export encryption: Using Azure Information Protection
- Log retention: Compliance with governance policies
- Report access: Strict RBAC control
Technical conclusion
This modernized approach for reporting Microsoft 365 Groups changes provides a robust and scalable solution. The use of Graph APIs and unified audit logs ensures superior accuracy and reliability compared to methods based on local configuration files.
The script can be downloaded from the Office 365 for IT Pros GitHub repository and adapted according to your environment's specific needs.



