Create an Azure automation runbook
Here we will create a runbook in the automation account that will retrieve data from Microsoft Graph using the REST API and export the data to the Azure storage account.
Import Modules
We will need a couple of PowerShell modules added to the automation account for the runbook to use.
In the Azure portal in the automation account, go to the Modules gallery pane
Search for and import the following modules:
Az.Accounts
Az.Storage
MSAL.PS (only required if using a Run as account)
Create a Runbook
Click on the Runbooks pane and choose Create a runbook
Enter a name for the runbook, select PowerShell for the runbook type and click Create
Copy the PowerShell code below into the runbook and edit it as described below
If you wish to test the runbook before publishing to make sure it works, use the Test pane
Alternatively, Publish the runbook, then click Start from the runbook menu. This option will give you the full output of the script.
When ready, Publish the runbook
Export-MSGraphManagedDeviceData
This PowerShell script can be used as-is as the source for your runbook or as simply as an example that you can modify to your requirements.
Populate the following parameters at the top of the script:
$ResourceGroup. This is the name of the resource group that hosts your storage account in Azure
$StorageAccount. This is the name of the storage account to which you will export data
$Container. The name of the container to use in the storage account
Managed Identity vs Run as account
The script is configured to run using a managed identity, but code is also included to use a Run as account instead. To use a Run as account, in the Authentication section of the script, simply uncomment the code blocks that start with #Run as account, and comment out the sections that start with #Managed Identity.
What the Runbook does
First we authenticate to MS Graph and obtain an access token to make our REST API calls with. We also authenticate to Azure AD in order to send data to the storage account.
###########################################################################
## Azure automation runbook PowerShell script to export device data from ##
## Microsoft Intune / Endpoint Manager and dump it to Azure Blob storage ##
## where it can be used as a datasource for Power BI. ##
###########################################################################
## Module Requirements ##
# Az.Accounts
# Az.Storage
# MSAL.PS (if using Run as account)
# Set some variables
$ProgressPreference = 'SilentlyContinue'
$ResourceGroup = "<myresourcegroupname>" # Reource group that hosts the storage account
$StorageAccount = "<mystorageaccountname>" # Storage account name
$Container = "intune-powerbi" # Container name
####################
## AUTHENTICATION ##
####################
## Get MS Graph access token
# Managed Identity
$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
$authHeader = @{
'Authorization' = "Bearer $accessToken"
}
# Run as account
# Requires MSAL.PS module
<#
$connectionName = "AzureRunAsConnection"
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
$Cert = Get-Item Cert:\LocalMachine\Root\$($servicePrincipalConnection.CertificateThumbprint)
$MsalToken = Get-MsalToken -ClientID $servicePrincipalConnection.ApplicationId -ClientCertificate $Cert -TenantId $servicePrincipalConnection.TenantId -Scopes 'https://graph.microsoft.com/.default'
$authHeader = @{
'Authorization' = $MsalToken.CreateAuthorizationHeader()
}
#>
## Connect to Azure AD
# Mmanaged Identity
Connect-AzAccount -Identity
# Run as account
#Connect-AzAccount -ServicePrincipal -Tenant $servicePrincipalConnection.TenantId -ApplicationId $servicePrincipalConnection.ApplicationId -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
#########################
## GET DATA FROM GRAPH ##
#########################
$URI = "https://graph.microsoft.com/beta/deviceManagement/manageddevices"
$Response = Invoke-WebRequest -Uri $URI -Method Get -Headers $authHeader -UseBasicParsing
$JsonResponse = $Response.Content | ConvertFrom-Json
$DeviceData = $JsonResponse.value
If ($JsonResponse.'@odata.nextLink')
{
do {
$URI = $JsonResponse.'@odata.nextLink'
$Response = Invoke-WebRequest -Uri $URI -Method Get -Headers $authHeader -UseBasicParsing
$JsonResponse = $Response.Content | ConvertFrom-Json
$DeviceData += $JsonResponse.value
} until ($null -eq $JsonResponse.'@odata.nextLink')
}
#############################################
## ORGANISE THE DATA INTO USEABLE DATASETS ##
#############################################
# Seperate by OS
$WindowsDevices = $DeviceData | where {$_.operatingSystem -eq "Windows"}
$iOSDevices = $DeviceData | where {$_.operatingSystem -eq "iOS"}
$AndroidDevices = $DeviceData | where {$_.operatingSystem -eq "Android"}
$UnknownDevices = $DeviceData | where {$_.operatingSystem -ne "Android" -and $_.operatingSystem -ne "iOS" -and $_.operatingSystem -ne "Windows"}
# Set property exclusion lists. These properties will not be included in the final datasets.
$AndroidExcludedProperties = @(
'activationLockBypassCode',
'remoteAssistanceSessionUrl',
'remoteAssistanceSessionErrorDetails',
'configurationManagerClientEnabledFeatures',
'deviceHealthAttestationState',
'totalStorageSpaceInBytes',
'freeStorageSpaceInBytes',
'requireUserEnrollmentApproval',
'iccid',
'udid',
'roleScopeTagIds',
'windowsActiveMalwareCount',
'windowsRemediatedMalwareCount',
'configurationManagerClientHealthState',
'configurationManagerClientInformation',
'ethernetMacAddress',
'physicalMemoryInBytes',
'processorArchitecture',
'specificationVersion',
'skuFamily',
'skuNumber',
'managementFeatures',
'hardwareInformation',
'deviceActionResults',
'chromeOSDeviceInfo',
'retireAfterDateTime',
'preferMdmOverGroupPolicyAppliedDateTime',
'autopilotEnrolled',
'managedDeviceId',
'managedDeviceODataType',
'managedDeviceReferenceUrl',
'usersLoggedOn',
'partnerReportedThreatState',
'chassisType'
)
$iOSExcludedProperties = @(
'activationLockBypassCode',
'remoteAssistanceSessionUrl',
'remoteAssistanceSessionErrorDetails',
'configurationManagerClientEnabledFeatures',
'deviceHealthAttestationState',
'requireUserEnrollmentApproval',
'iccid',
'udid',
'roleScopeTagIds',
'windowsActiveMalwareCount',
'windowsRemediatedMalwareCount',
'configurationManagerClientHealthState',
'configurationManagerClientInformation',
'ethernetMacAddress',
'physicalMemoryInBytes',
'processorArchitecture',
'specificationVersion',
'skuFamily',
'skuNumber',
'managementFeatures',
'hardwareInformation',
'deviceActionResults',
'chromeOSDeviceInfo',
'retireAfterDateTime',
'preferMdmOverGroupPolicyAppliedDateTime',
'autopilotEnrolled',
'managedDeviceId',
'managedDeviceODataType',
'managedDeviceReferenceUrl',
'usersLoggedOn',
'partnerReportedThreatState',
'chassisType',
'freeStorageSpaceInBytes',
'totalStorageSpaceInBytes'
)
$WindowsExcludedProperties = @(
'activationLockBypassCode'
'chassisType'
'jailBroken'
'remoteAssistanceSessionUrl'
'remoteAssistanceSessionErrorDetails'
'phoneNumber'
'androidSecurityPatchLevel'
'deviceHealthAttestationState'
'subscriberCarrier'
'meid'
'requireUserEnrollmentApproval'
'iccid'
'udid'
'roleScopeTagIds'
'configurationManagerClientInformation'
'ethernetMacAddress'
'physicalMemoryInBytes'
'processorArchitecture'
'specificationVersion'
'managementFeatures'
'hardwareInformation'
'deviceActionResults'
'usersLoggedOn'
'chromeOSDeviceInfo'
'totalStorageSpaceInBytes'
'freeStorageSpaceInBytes'
'configurationManagerClientEnabledFeatures'
'configurationManagerClientHealthState'
'managedDeviceId'
'managedDeviceODataType'
'managedDeviceReferenceUrl'
)
# Remove the unwanted properties and add some new ones
$AndroidDevices = $AndroidDevices | Select-Object -Property * -ExcludeProperty $AndroidExcludedProperties
$iOSDevices = $iOSDevices | Select-Object -Property *,`
@{l="freeStorageSpaceInGB";e={[math]::Round(($_.freeStorageSpaceInBytes / 1GB),2)}},`
@{l="totalStorageSpaceInGB";e={[math]::Round(($_.totalStorageSpaceInBytes / 1GB),2)}} `
-ExcludeProperty $iOSExcludedProperties
$WindowsDevices = $WindowsDevices | Select-Object -Property *,`
@{l="freeStorageSpaceInGB";e={[math]::Round(($_.freeStorageSpaceInBytes / 1GB),2)}},`
@{l="totalStorageSpaceInGB";e={[math]::Round(($_.totalStorageSpaceInBytes / 1GB),2)}}, `
@{l="daysSinceLastSync";e={[math]::Round(((Get-Date) - ($_.lastSyncDateTime | Get-Date -ErrorAction SilentlyContinue)).TotalDays,0)}}, `
@{l="enabledCoMgmtWorkloads_inventory";e={$_.configurationManagerClientEnabledFeatures.inventory}}, `
@{l="enabledCoMgmtWorkloads_modernApps";e={$_.configurationManagerClientEnabledFeatures.modernApps}}, `
@{l="enabledCoMgmtWorkloads_resourceAccess";e={$_.configurationManagerClientEnabledFeatures.resourceAccess}}, `
@{l="enabledCoMgmtWorkloads_deviceConfiguration";e={$_.configurationManagerClientEnabledFeatures.deviceConfiguration}}, `
@{l="enabledCoMgmtWorkloads_compliancePolicy";e={$_.configurationManagerClientEnabledFeatures.compliancePolicy}}, `
@{l="enabledCoMgmtWorkloads_windowsUpdateForBusiness";e={$_.configurationManagerClientEnabledFeatures.windowsUpdateForBusiness}}, `
@{l="enabledCoMgmtWorkloads_endpointProtection";e={$_.configurationManagerClientEnabledFeatures.endpointProtection}}, `
@{l="enabledCoMgmtWorkloads_officeApps";e={$_.configurationManagerClientEnabledFeatures.officeApps}}, `
@{l="MEMCMClient_state";e={$_.configurationManagerClientHealthState.state}}, `
@{l="MEMCMClient_errorCode";e={$_.configurationManagerClientHealthState.errorCode}}, `
@{l="MEMCMClient_lastSyncDateTime";e={$_.configurationManagerClientHealthState.lastSyncDateTime}}, `
@{l="MEMCMClient_daysSinceLastSync";e={[math]::Round(((Get-Date) - ($_.configurationManagerClientHealthState.lastSyncDateTime | Get-Date -ErrorAction SilentlyContinue)).TotalDays,0)}} `
-ExcludeProperty $WindowsExcludedProperties
# Export the data to CSV format
$androiddevices | export-csv -Path $env:temp\AndroidDevices.csv -Force -NoTypeInformation
$iOSDevices | export-csv -Path $env:temp\iOSDevices.csv -Force -NoTypeInformation
$WindowsDevices | export-csv -Path $env:temp\WindowsDevices.csv -Force -NoTypeInformation
###########################################
## UPLOAD DATASETS TO AZURE BLOB STORAGE ##
###########################################
$StorageAccount = Get-AzStorageAccount -Name $StorageAccount -ResourceGroupName $ResourceGroup
"AndroidDevices","iOSDevices","WindowsDevices" | foreach {
Set-AzStorageBlobContent -File "$env:temp\$_.csv" -Container $Container -Blob $_/$_.csv -Context $StorageAccount.Context -Force
}
Schedule the Runbook
Schedule the Runbook to execute at regular intervals to keep the data updated.
In the Azure portal, in the automation account, open the Runbook you created
Click Link to schedule
In the Schedule section, select an existing schedule or create a new one

Last updated
Was this helpful?