In today’s blog, I will address a question from one of our community members, who is looking to create a report for tracking Windows 11 upgrades via Azure Automation Runbook and Microsoft Intune. He has tried to gather enrolled devices details using a runbook but hasn’t found a solution yet. In this post, we will demonstrate how to generate a report on Windows 11 upgrade tracking with Intune and Azure Automation.

Content
How to create an azure automation account
I have already written a very detailed blog on how to get started with Azure Automamtion and Runbooks. It’s best to read this blog first. You have to make sure that the Graph.Authentication module is installed and you need API graph permissions for:
- Mail.Send
- DeviceManagementManagedDevices.Read.All
How to get the report
You can find the script in my GitHub repository or here. You have to copy the script into you runbook:
# Variables
$MailTo = ""
$MailSender = ""
# Authenticate and connect to Microsoft Graph
Connect-AzAccount -Identity
$token = Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com"
#Connect to Microsoft Graph API
Connect-MgGraph -AccessToken $token.Token
# Get Windows devices from Intune
$devices = Get-MgDeviceManagementManagedDevice -all -Filter "contains(operatingSystem,'Windows')"
# Filter Windows 11 devices
$windows11Devices = $devices | Where-Object { $_.OsVersion -ge "10.0.22000" }
# Calculate device counts
$totalDevices = $devices.Count
$windows11DevicesCount = $windows11Devices.Count
# Calculate pie chart percentages
$windows11Percentage = ($windows11DevicesCount / $totalDevices) * 100
$otherWindowsPercentage = 100 - $windows11Percentage
# Create HTML report
$html = @"
<style>
body {
font-family: Arial, sans-serif;
}
h1 {
font-size: 28px;
color: #0078d4;
margin-top: 0;
text-align: center;
}
.chart-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.pie-chart {
width: 300px;
height: 300px;
margin: 20px;
}
.device-list {
margin-top: 40px;
}
table {
border-collapse: collapse;
width: 100%;
}
th {
background-color: #0078d4;
color: #fff;
font-weight: bold;
padding: 8px;
text-align: left;
}
td {
border: 1px solid #ddd;
padding: 8px;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
</style>
<h1>Windows 11 Adoption Report</h1>
<div class="chart-container">
<div class="pie-chart">
<canvas id="chart"></canvas>
</div>
</div>
<div class="device-list">
<table>
<thead>
<tr>
<th>Device Name</th>
<th>User</th>
<th>Last Sync DateTime</th>
<th>OSVersion</th>
</tr>
</thead>
<tbody>
"@
foreach ($device in $devices) {
$html += "<tr><td>$($device.DeviceName)</td><td>$($device.EmailAddress)</td><td>$($device.LastSyncDateTime)</td><td>$($device.OSVersion)</td></tr>"
}
$html += @"
</tbody>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
var ctx = document.getElementById('chart').getContext('2d');
var chart = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Windows 11', 'Other Windows'],
datasets: [{
backgroundColor: ['#0078d4', '#f2f2f2'],
data: [$windows11Percentage, $otherWindowsPercentage]
}]
},
options: {
legend: {
display: true,
position: 'bottom',
labels: {
fontColor: '#333',
fontSize: 14,
padding: 16
}
}
}
});
</script>
"@
$html | Out-File -FilePath "Windows11AdoptionReport.html"
$base64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes(".\Windows11AdoptionReport.html"))
#Send Mail
$URLsend = "https://graph.microsoft.com/v1.0/users/$MailSender/sendMail"
$BodyJsonsend = @"
{
"message": {
"subject": "Intune error report",
"body": {
"contentType": "Text",
"content": "Dear Admin, this Mail contains the Windows 11 Adoption report"
},
"toRecipients": [
{
"emailAddress": {
"address": "$MailTo"
}
}
],
"attachments": [
{
"@odata.type": "#microsoft.graph.fileAttachment",
"name": "Windows11AdoptionReport.html",
"contentType": "text/plain",
"contentBytes": "$base64"
}
]
}
}
"@
$response = Invoke-MgRestMethod -Method POST -Uri $URLsend -Body $BodyJsonsend
You have to add the sender and receiver email into this variable:
$MailTo = ""
$MailSender = ""
Nice work Jannikreinhard.
LikeLike
[…] https://jannikreinhard.com/2023/04/30/tracking-windows-11-upgrades-with-azure-automation-and-intune/ […]
LikeLike
Hey Jannik, I have been following the steps outlined in this however I seem to be running into an issue with the access token explaining you must specify access token, any ideas please?
LikeLike
There was something missing in the script. I have fixed this
LikeLike
Hey Jannik, I managed to work around it via a similar way but appreciate this. I also come into the following issue which I didn’t manage to bug System.Management.Automation.MethodInvocationException: Exception calling “ReadAllBytes” with “1” argument(s): “Could not find file ‘C:\Windows\System32\Windows11AdoptionReport.html’.” —> System.IO.FileNotFoundException: Could not find file ‘C:\Windows\System32\Windows11AdoptionReport.html’. It doesn’t seem to Output the file so cant find it.
LikeLike
The script works fine at outputting the file to the windows\system32 folder when ran locally however seems to be having issue in the runbook
LikeLike
Hey Jannik, I’ve got the same problem as Jack with the file not writing out. I also have an issue that my registered application can’t send mail as the user. How did you manage the credentials/permissions for this in your environment?
LikeLike
Do you have an error code
LikeLike
I get a 401 Unauthorised
LikeLike
Hi Kristen, I managed the credentials for the application by following Janniks guide using the below
Install-Module Microsoft.Graph -Scope CurrentUser
Connect-MgGraph -Scopes Application.Read.All, AppRoleAssignment.ReadWrite.All, RoleManagement.ReadWrite.Directory
$managedIdentityId = “Managed Identity Object ID”
$roleName = “DeviceManagementApps.Read.All”
$msgraph = Get-MgServicePrincipal -Filter “AppId eq ‘00000003-0000-0000-c000-000000000000′”
$role = $Msgraph.AppRoles| Where-Object {$_.Value -eq $roleName}
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $managedIdentityId -PrincipalId $managedIdentityId -ResourceId $msgraph.Id -AppRoleId $role.Id
Disconnect-MgGraph
LikeLike