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 Automation 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 your 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.
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?
There was something missing in the script. I have fixed this
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 can’t find it.
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
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?
Do you have an error code
I get a 401 Unauthorised
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
My error that I am getting for the output-file is 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 can’t find it.
Hey Jannik, did you manage to find out what may be the cause of the runbook not outputting the html file?
Many Thanks