Les Cloud PCs, c’est excellent. Ils s’adaptent à de nombreux use cases, bénéficient d’un excellent support de Microsoft et offrent une expérience utilisateur louées de bon nombre de clients.
Vous avez déployé plusieurs Cloud PCs au sein de votre organisation. Cependant, une fois déployés, certains Cloud PCs peuvent ne pas être utilisés aussi fréquemment que prévu, ce qui entraîne des inefficacités et des coûts inutiles.
Dans Intune, cliquez sur un seul Cloud PC pour accéder au Performance Pane contenant les informations du last logon et les heures d’utilisations :

Bien que Intune fournisse des aperçus d’utilisation par appareil, il n’existe aucun moyen natif d’exporter simultanément les données d’utilisation pour plusieurs Cloud PCs.
Passons à Microsoft Graph ! Ou passez l’explication et récupérez directement le script complet sur mon GitHub.
Comment l’API fonctionne
Analysons les rapports de plus près et plongeons dans l’API :

Pour récupérer les détails d’utilisation des Cloud PCs, nous utilisons l’URI Graph suivante : https://graph.microsoft.com/beta/deviceManagement/virtualEndpoint/reports/getRemoteConnectionHistoricalReports
Voyons le corps de l’action POST :

Oooh : {« top »:50, »skip »:0, »filter »: »CloudPcId eq ‘c86b874c-45af-4eac-bc64-35d00ab6d374’ and SignInDateTime gt datetime’2025-02-05T05:57:25.772Z' », »select »:[« SignInDateTime », »SignOutDateTime », »UsageInHour »], »orderBy »:[« SignInDateTime desc »]}
Il y a donc un CloudPCId quelque part utilisé pour filtrer, et je peux combiner le SignInDateTime pour filtrer directement sur X jours dans mon body. Le « X jours » correspond en réalité au timestamp des 7 derniers jours.
Maintenant que nous comprenons comment l’API fonctionne réellement, place au scripting !
Automatisons
Comme toujours, connectez-vous à Graph en utilisant une registered application. Cette fois, vous aurez besoin au minimum des permissions suivantes : CloudPC.Read.All et DeviceManagementManagedDevices.Read.All.
Nous créons un tableau alldevices pour stocker nos Cloud PCs et les récupérer tous via l’URI dédiée. La date ref sera utilisée plus tard pour définir le timestamp.
$allDevices=@()$dateref = (Get-Date).AddDays(-7).ToString("yyyy-MM-ddTHH:mm:ssZ")$nextLink = "https://graph.microsoft.com/beta/deviceManagement/virtualEndpoint/cloudPCs"# Fetch all deviceswhile (![string]::IsNullOrEmpty($nextLink)) { $response = Invoke-MgGraphRequest -Method GET -Uri "$nextLink" $allDevices += $response.value $nextLink = $response.'@odata.nextLink' Write-Host $nextLink}
Nous aurons besoin plus tard d’un fichier de sortie pour stocker les résultats individuels des Cloud PCs et d’un tableau (array) qui contiendra toutes les données des Cloud PC reports :
$outputFilePath = [System.IO.Path]::GetTempFileName()$allReportData = @()
Maintenant, parcourez le tableau (parse) pour chaque Cloud PC et isolez certaines informations qui pourraient être utiles par la suite :
foreach ($device in $allDevices) { $cloudpcid=$device.id $cloudpcname=$device.managedDeviceName $upn=$device.userPrincipalName
Il est temps de construire le body au format json. Vous pouvez modifier $dateref à votre guise pour ajuster le timestamp. La seule info qui m’intéresse est le « UsageInHour », mais vous pouvez également récupérer les informations de Sign-In et Sign-Out directement depuis le champ select.
$uri="https://graph.microsoft.com/beta/deviceManagement/virtualEndpoint/reports/getRemoteConnectionHistoricalReports" $body = @{ filter = "CloudPcId eq '$cloudpcid' and SignInDateTime gt datetime'$dateref'" select = @( "UsageInHour" ) top = 100 skip = 0 } | ConvertTo-Json -Compress -Depth 5
Maintenant que nous avons construit le body et l’uri, nous effectuons l’action POST. Mais pour ce type d’actions POST, vous avez besoin d’un fichier de sortie pour stocker les résultats. Nous créons un fichier temporaire (temp file) et l’utilisons dans l’appel POST :
$tempFilePath = [System.IO.Path]::GetTempFileName()
$response = Invoke-MgGraphRequest -Method post -Uri $uri -Body $body -OutputFilePath $tempFilePath
$responseContent = Get-Content -Path $tempFilePath -Raw | ConvertFrom-Json
Top ! Vous avez dans $responsecontent l’ensemble du usage in hours des 7 derniers jours.
Le problème est que le Usage in hours est calculé à chaque ouverture et fermeture de session, ce qui peut générer énormément d’entrées.
Pour calculer le total, nous faisons la somme de tous les usage in hours. Le souci… c’est que le Usage in hours n’est pas identifié comme une valeur numérique. Nous devons donc d’abord convertir le usage in hours en valeur numérique avant de pouvoir calculer le total.
$sum = 0
foreach ($valueArray in $responseContent.values) {
foreach ($value in $valueArray) {
$sum += [double]$value
}
}
Enfin, je choisis de créer un PSCustomObject contenant toutes les données qui m’intéressent et de stocker chaque objet dans le tableau (array) que nous avons créé précédemment.
$reportdata = [PSCustomObject]@{ CloudPCName = $cloudpcname UPN = $upn UsageInHours = $sum } $allReportData += $reportdata}# Convert the array to JSON for better use in Powerbi or reporting tools$ReportCloudPCUsage = $allReportData | ConvertTo-Json -Depth 5
J’ai apporté quelques améliorations avec le format et la date ref en paramètres. Voici le script complet, mais vous pouvez également le récupérer sur mon GitHub personnel :
param ( [string]$TenantId, [string]$ClientId, [string]$ClientSecret, [int]$Days, [switch]$Json )$global:tenant = $tenantId$global:clientId = $clientId$global:clientSecret = $clientSecret$SecuredPasswordPassword = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $clientId, $SecuredPasswordPasswordConnect-MgGraph -TenantId $tenant -ClientSecretCredential $ClientSecretCredential -NoWelcome$allDevices = @()# Ensure $dateref is an integer$days = [int]$days # Check if dateref is provided, otherwise use default 7 daysif (-not $days) { $daterefFormatted = (Get-Date).AddDays(-7).ToString("yyyy-MM-ddTHH:mm:ssZ")} else { $daterefFormatted = (Get-Date).AddDays(-$days).ToString("yyyy-MM-ddTHH:mm:ssZ")}$nextLink = "https://graph.microsoft.com/beta/deviceManagement/virtualEndpoint/cloudPCs"# Fetch all deviceswhile (![string]::IsNullOrEmpty($nextLink)) { $response = Invoke-MgGraphRequest -Method GET -Uri "$nextLink" $allDevices += $response.value $nextLink = $response.'@odata.nextLink' Write-Host $nextLink}$allReportData = @()foreach ($device in $allDevices) { $cloudpcid = $device.id $cloudpcname = $device.managedDeviceName $upn = $device.userPrincipalName $uri = "https://graph.microsoft.com/beta/deviceManagement/virtualEndpoint/reports/getRemoteConnectionHistoricalReports" $body = @{ filter = "CloudPcId eq '$cloudpcid' and SignInDateTime gt datetime'$daterefFormatted '" select = @( # Use an array for 'select' "UsageInHour" ) top = 100 skip = 0 } | ConvertTo-Json -Compress -Depth 5 #-Compress removes extra whitespace $tempFilePath = [System.IO.Path]::GetTempFileName() $response = Invoke-MgGraphRequest -Method post -Uri $uri -Body $body -OutputFilePath $tempFilePath # Read the content of the temporary file $responseContent = Get-Content -Path $tempFilePath -Raw | ConvertFrom-Json $sum = 0 foreach ($valueArray in $responseContent.values) { foreach ($value in $valueArray) { $sum += [double]$value } } $reportdata = [PSCustomObject]@{ CloudPCName = $cloudpcname UPN = $upn UsageInHours = $sum } $allReportData += $reportdata}# Output results based on the -Table switchif ($Json) { return $allReportData | ConvertTo-Json -Depth 5 } else { return $allReportData | Format-Table -AutoSize}
Il vous suffit d’exécuter le script en spécifiant le nombre de jours que vous souhaitez filtrer, et le tour est joué :

Happy monitoring !