Windows 11 Hardware Readiness Report
Overview
This Power BI report is more or less a mirror of what is available in Endpoint Analytics and is built from the same data. It allows you to see which devices in Intune are ready for Windows 11 and which are not and the reasons why.


The report uses the same process as the other reports in this site, ie an Azure automation account runbook exports the data from Microsoft Graph and sends it in CSV format to an Azure storage account container. The Power BI report then connects to the storage account and uses the CSV file as the source for the report. Scheduling the runbook to run regularly and scheduling a refresh on the Power BI report will keep the data up-to-date.
Configure Azure Resources
If you've already created any of my other reports in this site or used the same process, ie Azure automation runbook > Azure blob storage, you'll already have the Azure resources in place to run this report. Otherwise you'll need to create them using the guides below.
Create / configure an Azure automation accountGrant API permissionsAzure Storage accountFor API permissions for the managed identity of the automation account, the following minimum are needed for this report:
DeviceManagementConfiguration.Read.All
DeviceManagementManagedDevices.Read.All
DeviceManagementManagedDevices.ReadWrite.All
Make sure that the Az.Accounts and Az.Storage modules have been imported into the automation account.
Azure automation runbook
Download the runbook script below and create a runbook in your automation account. Set a recurring schedule for the runbook as needed, eg once a day.
##################################################################
## Azure Automation runbook to export Windows 11 readiness data ##
## from MS Graph (Intune) into Azure blob storage for use with ##
## Power BI reporting ##
##################################################################
##############
# Change log #
##############
# 2021-10-19 | Removed the "managedBy" field from the results to enable easy elimination of duplicate results
# 2021-10-05 | First release
## Module Requirements ##
# Az.Accounts
# Az.Storage
# Variables
$ResourceGroup = "<my-resource-group>" # Reource group that hosts the storage account
$StorageAccount = "<my-storage-account>" # Storage account name
$Container = "windows11readiness" # Container name
$ExportLocation = "$env:TEMP"
$ProgressPreference = 'SilentlyContinue'
$VerbosePreference = 'Continue'
# Graph web request function
Function Invoke-MyGraphGetRequest {
Param ($URL)
try {
$WebRequest = Invoke-WebRequest -Uri $URL -Method GET -Headers $Headers -UseBasicParsing
}
catch {
$WebRequest = $_.Exception.Response
}
Return $WebRequest
}
# Authenticate
$url = $env:IDENTITY_ENDPOINT
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("X-IDENTITY-HEADER", $env:IDENTITY_HEADER)
$headers.Add("Metadata", "True")
$body = @{resource='https://graph.microsoft.com/' }
$accessToken = (Invoke-RestMethod $url -Method 'POST' -Headers $headers -ContentType 'application/x-www-form-urlencoded' -Body $body ).access_token
$script:Headers = @{
'Authorization' = "Bearer $accessToken"
}
$null = Connect-AzAccount -Identity
# Graph URI
$URI = "https://graph.microsoft.com/beta/deviceManagement/userExperienceAnalyticsWorkFromAnywhereMetrics('allDevices')/metricDevices?`$select=id%2cdeviceName%2cosDescription%2cosVersion%2cupgradeEligibility%2cazureAdJoinType%2cupgradeEligibility%2cramCheckFailed%2cstorageCheckFailed%2cprocessorCoreCountCheckFailed%2cprocessorSpeedCheckFailed%2ctpmCheckFailed%2csecureBootCheckFailed%2cprocessorFamilyCheckFailed%2cprocessor64BitCheckFailed%2cosCheckFailed&dtFilter=all"
# Get data from Graph with some error handling
$Response = Invoke-MyGraphGetRequest -URL $URI
if ($Response.StatusCode -ne 200)
{
Write-Warning "Graph request returned $($Response.StatusCode)). Retrying..."
Start-Sleep -Seconds 30
$RetryCount = 0
do {
$Response = Invoke-MyGraphGetRequest -URL $URI
If ($Response.StatusCode -ne 200)
{
Write-Warning "Graph request returned $($Response.StatusCode)). Retrying..."
$RetryCount ++
Start-Sleep -Seconds 30
}
}
Until ($Response.StatusCode -eq 200 -or $RetryCount -ge 10)
If ($RetryCount -ge 10)
{
Write-Error "Gave up waiting for a success response to the Graph request."
throw
}
}
# Loop through the nextLinks until all data is retrieved
$JsonResponse = $Response.Content | ConvertFrom-Json
$DeviceData = $JsonResponse.value
If ($JsonResponse.'@odata.nextLink')
{
do {
$URI = $JsonResponse.'@odata.nextLink'
$Response = Invoke-MyGraphGetRequest -URL $URI
if ($Response.StatusCode -ne 200)
{
Write-Warning "Graph request returned $($Response.StatusCode)). Retrying..."
Start-Sleep -Seconds 60
$RetryCount = 0
do {
$Response = Invoke-MyGraphGetRequest -URL $URI
If ($Response.StatusCode -ne 200)
{
Write-Warning "Graph request returned $($Response.StatusCode)). Retrying..."
$RetryCount ++
Start-Sleep -Seconds 60
}
}
Until ($Response.StatusCode -eq 200 -or $RetryCount -ge 10)
If ($RetryCount -ge 10)
{
Write-Error "Gave up waiting for a success response to the Graph request."
throw
}
}
$JsonResponse = $Response.Content | ConvertFrom-Json
$DeviceData += $JsonResponse.value
} until ($null -eq $JsonResponse.'@odata.nextLink')
}
# Create a unique result set of Windows 10 devices
$AllDevices = $DeviceData | where {$_.osVersion -like "10.0.*"}
$Properties = ($AllDevices[0] | Get-Member -MemberType NoteProperty).Name
$AllDevices = $AllDevices | Sort-Object -Property $Properties -Unique
# output some numbers
Write-Verbose "Total devices: $($AllDevices.count)"
# Export to CSV
$AllDevices | Export-CSV -Path "$ExportLocation\alldevices.csv" -NoTypeInformation -Force
# Upload to blob storage
$StorageAccount = Get-AzStorageAccount -Name $StorageAccount -ResourceGroupName $ResourceGroup
@(
"alldevices.csv"
) | foreach {
try {
$FileName = $_
Write-Verbose "Uploading $FileName to Azure storage container $Container"
$null = Set-AzStorageBlobContent -File "$ExportLocation\$FileName" -Container $Container -Blob $FileName -Context $StorageAccount.Context -Force -ErrorAction Stop
}
catch {
Write-Error -Exception $_ -Message "Failed to upload $FileName to blob storage"
}
}
Parameters
You'll need to set the following parameters in the top of the runbook script:
$ResourceGroup - the name of the resource group containing the storage account you are using
$StorageAccount - the name of the storage account that will contain the exported data
$Container - the name of the container in the storage account
Power BI Template
Once you have executed the runbook and got data in your storage account, download the open the Power BI template below.
PK µZSS±¦ Version ¢ ( 3dÐc0B PK µZSSÏâA^õ b [Content_Types].xml ¢ ( KNÃ0¯byß:°@%©eGÄ~dO·ñCö5gcÁ¸n²C ¥,gü_åæhö!jg+~±.8C+Ò¶«x¢vuÍ7uù:z,Km¬xOäo²Gqí<ÚüÒº`r:áA CqYWB:KhiE§?x]n±
4»?æô\våìnjU3QOÙXÐ
Ù3zóâmöºT¾Ä>ù³§ph¦cº æFh)ôÞçA
åõÄzð~Ð(GÌýß!ÊMþoP¦ i¼ÕVýâcâÄt1õ7PK µZSSãD*