Overview#
The Windows 365 Cloud PC Monitors feature is currently in Public Preview and already provides a broad range of valuable insights into your Cloud Endpoints. This includes sign-in time trends, Cloud PC health trends, average round-trip times, connection data by Windows App version and a much more. However, at the time of writing, the platform does not provide reporting for Cloud PC disk space trends, particularly around free and used disk space. Luckily, we can still get this information using the Microsoft Graph REST API.
Microsoft Graph managedDevices#
Because every Cloud PC is also an Intune-enrolled Windows device, the
Microsoft Intune device-inventory pipeline carries OS disk values for
each one. Graph exposes these via the managedDevice resource — specifically
two read-only Int64 properties inherited by windowsManagedDevice:
| Property | Type | Meaning |
|---|---|---|
totalStorageSpaceInBytes | Int64 | Total OS volume capacity |
freeStorageSpaceInBytes | Int64 | Free space on the OS volume |
Both come straight off the device at Intune check-in time and apply to the
system drive (typically C:). They are documented on the shared
managedDevice resource type
and are inherited by
windowsManagedDevice.
The cloudPC resource itself does not carry these properties — you must
join across.
Filtering managedDevices to the Cloud PC fleet#
Cloud PCs are not flagged with a dedicated managedDeviceOwnerType value;
they appear as ordinary company-owned Windows devices. Currently, we can distriguish Cloud PCs using the following methods:
Managed Device
modelproperty containsCloud PC: Every W365 SKU’s model string starts with “Cloud PC " (e.g.Cloud PC Enterprise 2vCPU/4GB/64GB). This is the simplest one-shot filter and is what the Intune admin center itself uses.The Microsoft Graph
deviceManagement/managedDevicesendpoint returns all managed devices and properties for total storage (totalStorageSpaceInBytes) and free space (freeStorageSpaceInBytes). It also include ourmodelproperty that we can filter against.In my example, using the Microsoft Graph PowerShell SDK, I can run,
Get-MgDeviceManagementManagedDevice -All -Property * | Where-Object { $_.Model -like 'Cloud PC*'} | Select-Object Id, Total*, Free*, Modelin my tenant which displays my current Cloud PC’s and all the properties I will need:Id TotalStorageSpaceInBytes FreeStorageSpaceInBytes Model -- ------------------------ ----------------------- ----- 3c787e74-e2d2-4fce-9cd1-650b5e06a4ee 136844410880 95460261888 Cloud PC Frontline 4vCPU/16GB/12… 22bb1414-aa07-4d5d-8d5d-a7af104d1dae 136844410880 99693363200 Cloud PC Frontline Shared 4vCPU/… cc802f52-f11e-43df-8393-565439c1a7c6 274283364352 222822400000 Cloud PC Enterprise 8vCPU/32GB/2…Match Cloud PCs from
/deviceManagement/virtualEndpoint/cloudPCstoGet-MgDeviceManagementManagedDevice: Another way to get Cloud PCs is to use the/deviceManagement/virtualEndpoint/cloudPCsGraph endpoint which will give us a list of Cloud PCs like below:
{
"id": "95194d88-cec5-4b65-af62-26dbd1814364",
"displayName": "W365-Frontline-Dedicated - Bradley Wyatt",
"imageDisplayName": "Windows 11 Enterprise + Microsoft 365 Apps 25H2",
"provisioningPolicyId": "27d60840-6888-44b9-aefd-e5f8f1a92add",
"provisioningPolicyName": "W365-Frontline-Dedicated",
"onPremisesConnectionName": "",
"servicePlanId": "dd3801e2-4aa1-4b16-a44b-243e55497584",
"servicePlanName": "Cloud PC Frontline 4vCPU/16GB/128GB",
"userPrincipalName": "brad@windowsfromanywhere.com",
"lastModifiedDateTime": "2026-05-15T00:01:02Z",
"managedDeviceId": "3c787e74-e2d2-4fce-9cd1-650b5e06a4ee",
"managedDeviceName": "CFD-brad-HNFI4",
"aadDeviceId": "db5913da-e353-49d7-9570-0f2d7f13b51a",
"gracePeriodEndDateTime": null,
"provisioningType": "shared"
}But you may have noticed that this endpoint does not include any storage information, therefore you will need to iterate through each item and match the managedDeviceId value to Id from the deviceManagement/managedDevices endpoint.
PowerShell example using Microsoft.Graph SDK#
The following PowerShell script lists every Cloud PC in the tenant with its free / total storage in GB and a percent-free figure. It uses the Microsoft Graph PowerShell SDK and the v1.0 endpoint.
# Requires: Install-Module Microsoft.Graph -Scope CurrentUser
Connect-MgGraph -Scopes 'DeviceManagementManagedDevices.Read.All'
# Pull every managedDevice whose model starts with "Cloud PC"
$cpcs = Get-MgDeviceManagementManagedDevice -All -Property * | Where-Object { $_.Model -like 'Cloud PC*' }
$cpcs | ForEach-Object {
$totalGB = [math]::Round($_.TotalStorageSpaceInBytes / 1GB, 2)
$freeGB = [math]::Round($_.FreeStorageSpaceInBytes / 1GB, 2)
$pctFree = if ($totalGB -gt 0) { [math]::Round(($freeGB / $totalGB) * 100, 1) } else { 0 }
[pscustomobject]@{
DeviceName = $_.DeviceName
UPN = $_.UserPrincipalName
Model = $_.Model
TotalGB = $totalGB
FreeGB = $freeGB
PctFree = $pctFree
LastSyncUtc = $_.LastSyncDateTime
}
} | Sort-Object PctFree | Format-Table -AutoSizeResults would look like the following:
DeviceName UPN Model TotalGB FreeGB PctFree LastSyncUtc
---------- --- ----- ------- ------ ------- -----------
CFD-brad-HNFI4 brad@windowsfromanywhere.com Cloud PC Frontline 4vCPU/16GB/128GB 127.45 88.90 69.80 5/14/2026 7:58:34 PM
CFS-Q4L0XGCNSTR Cloud PC Frontline Shared 4vCPU/16GB/128GB 127.45 92.85 72.90 5/15/2026 4:42:12 AM
CPC-brad-VO992 brad@windowsfromanywhere.com Cloud PC Enterprise 8vCPU/32GB/256GB 255.45 207.52 81.20 5/14/2026 8:01:37 PMFlex Shared with User experience sync enabled devices will not yield accurate results since the user profiles are wiped and then loaded on at login.
The lastSyncDateTime column is critical: a Cloud PC that hasn’t checked in
for a week will show stale storage values. Filter or flag rows whose last sync
is older than the customer’s reporting tolerance.
Required delegated or application permission:
DeviceManagementManagedDevices.Read.All.
How fresh is the data?#
| Path | Freshness | Notes |
|---|---|---|
Graph managedDevices | Last Intune check-in (~8 hr default for Windows) | Force a Sync to refresh; honour lastSyncDateTime |
Per-user disk projection on Windows 365 Frontline / Flex Shared Cloud PCs#
The Graph managedDevices approach above answers “how full is this Cloud PC right now?” It does not answer “how big is user X’s footprint when they sign in to a shared Cloud PC?” On Windows 365 Flex Shared (the SKU formerly known as Frontline shared), the OS volume is a moving target: per windows 365 flex snapshot based reset the disk reverts to a known-good snapshot at sign-out, so any free-space reading is bound to whichever user is currently signed in and is wiped before the next user starts. Per-user visibility on a shared Cloud PC requires a different pipeline.
Conceptual model — what a “per-user” disk reading means here#
A user’s effective on-disk consumption when they sign in to a Flex Shared Cloud PC is the sum of three things, none of which freeStorageSpaceInBytes measures on its own:
- The base OS image footprint — fixed per provisioning policy. Whatever the gallery image plus admin-installed apps consume on a freshly snapshot-reset OS volume. This is the floor for every user on every Cloud PC in the policy. A 128 GB OS disk that boots to the snapshot at 52 GB used has 76 GB headroom before the user logs in.
- The per-user persistence footprint — what [[cloud-pc-flex-user-experience-sync|User Experience Sync]] (UES) re-attaches at sign-in, drawn from Microsoft-managed pooled storage. This is the only longitudinal per-user value the platform exposes natively. When attached, its contents are projected into
C:\Users\<upn>and count against the OS volume. - Session-time growth — temp files, browser cache, OneDrive hydration of Files On-Demand placeholders, app caches, downloads. This is volatile and disappears on the next snapshot reset, but determines the peak disk pressure during a session.
Anything redirected off-box — KFM-redirected Desktop / Documents / Pictures pointing at the user’s OneDrive, Edge sync data, Outlook OST stored in User Experience Sync (UES) (or excluded entirely) — does not consume CPC OS-volume capacity at sign-in, but it does drive hydration traffic and may briefly land on disk if the user opens those files. UES explicitly excludes AppData\Local\Packages\*\AC, SystemAppData, LocalCache, TempState, the identity broker caches, and other non-roamable data, so the persistence container is a settings + user files footprint, not a full profile clone.
[[cloud-pc-flex-user-experience-sync|UES]] is not [[fslogix-profile-containers|FSLogix]]. The W365 Flex Shared profile model is Microsoft-managed pooled storage attached by the platform; there is no SMB share, no %username%_%sid%.vhdx to enumerate, and no FSLogix container to mount. Direct migration from FSLogix to UES is explicitly not supported. FSLogix remains the [[azure-virtual-desktop|AVD]] multi-session pattern — relevant for parity conversations and for customers who run BYOI configurations, but not for stock W365 Flex Shared. The “enumerate VHDX files in the profile share” approach customers ask about is an AVD/FSLogix recipe; on W365 the equivalent surface is the UES Graph endpoints below.
Approach 1 — UES per-user storage via Microsoft Graph (canonical)#
The Intune admin center exposes UES per-user storage at Devices → Provision Cloud PCs → Provisioning policies → (policy) → User Storage tab. The same data is available programmatically through two beta Graph endpoints under the policy assignment:
| Endpoint | Returns |
|---|---|
…/cloudPCUserSettingsPersistence/retrieveUserSettingsPersistenceProfileUsage(configurationId='{id}') | Pool aggregates: totalAllocatedStorageInGB, usedStorageInGB, remainingAvailableStorageInGB. |
…/cloudPCUserSettingsPersistence/retrieveUserSettingsPersistenceProfiles(configurationId='{id}') | Per-user profiles: userPrincipalName, profileSizeInGB, lastProfileAttachedDateTime, status (connected / notConnected / deleting). |
profileSizeInGB is the maximum allocated size for the user (the tier picked at policy creation: 4 / 8 / 16 / 32 / 64 GB), not the in-use bytes. The platform does not currently surface a per-user used figure on the persistence profile object — the only “used” value is the policy-pool aggregate. That is a real gap for capacity planning at the user level; the documented workaround is to inspect C:\Users\<upn> size from inside an active session (Approach 4 below) and treat the persistence-tier value as the ceiling.
Both endpoints require CloudPC.Read.All and live under /beta. The gracePeriodEndDateTime on cloudPCUserSettingsPersistenceDetail is the cut-off after which the platform auto-evicts the oldest-last-attached profile when the pool is over its limit — useful as a “who’s about to be auto-cleaned?” signal.
# Requires: Install-Module Microsoft.Graph.Beta -Scope CurrentUser
Connect-MgGraph -Scopes 'CloudPC.Read.All'
$policyId = '<provisioning-policy-id>'
$assignmentId = '<assignment-id>'
$configId = '<persistence-configuration-id>' # from cloudPCUserSettingsPersistenceDetail
$profilesUri = "https://graph.microsoft.com/beta/deviceManagement/virtualEndpoint/" +
"provisioningPolicies/$policyId/assignments/$assignmentId/" +
"cloudPCUserSettingsPersistence/retrieveUserSettingsPersistenceProfiles" +
"(configurationId='$configId')"
$profiles = (Invoke-MgGraphRequest -Method GET -Uri $profilesUri).value
$profiles | Select-Object userPrincipalName,
profileSizeInGB,
status,
@{n='LastAttached';e={$_.lastProfileAttachedDateTime}} |
Sort-Object LastAttached -Descending |
Export-Csv .\flex-shared-ues-profiles.csv -NoTypeInformationPair this with the alert rule Windows 365 Flex Cloud PC User Experience Sync Storage Limits under Tenant administration → Cloud PC Alerts so admins are notified before the pool hits its tolerance window.
Approach 2 — OneDrive quota per user via Graph#
Because Flex Shared deployments overwhelmingly pair UES with OneDrive Known Folder Move (UES does not retain Packages\*\LocalCache-style caches and the customer guidance is to push Desktop / Documents / Pictures to OneDrive), a meaningful chunk of “user data” lives off-CPC. Two Graph paths surface it per user:
# Per-user, real-time
Get-MgUserDrive -UserId user@contoso.com |
Select-Object @{n='UPN';e={'user@contoso.com'}},
@{n='UsedGB';e={[math]::Round($_.Quota.Used/1GB,2)}},
@{n='TotalGB';e={[math]::Round($_.Quota.Total/1GB,2)}}# Tenant-wide CSV (D7 / D30 / D90 / D180 periods)
GET https://graph.microsoft.com/v1.0/reports/getOneDriveUsageAccountDetail(period='D30')The CSV returns Owner Principal Name, Storage Used (Byte), Storage Allocated (Byte), File Count, and Last Activity Date — joinable on UPN against the UES profile list to produce a per-user “where their data actually lives” view.
Approach 3 — Sign-in correlation via cloudPcAuditEvent + connectivity history#
To attribute a particular OS-disk free-space reading to a specific user session, you need a timeline of who was signed in to which Cloud PC when. Two surfaces:
cloudPcAuditEvent(Graph v1.0) at/deviceManagement/virtualEndpoint/auditEvents— records administrative and lifecycle activities (provision, reprovision, restore, snapshot reset). Useful for marking the boundary at which the OS disk was reset to baseline.- [[cloud-pc-connectivity-history-report|User connectivity history report]] — Cloud PC user start/finish connection activities, surfaced in the Intune admin centre under Devices → Cloud PC performance. This is the closest the platform comes to a “who was signed in to this CPC at time T?” feed for Flex Shared.
Joining the connectivity history (user, start, end, Cloud PC ID) to Graph managedDevices.lastSyncDateTime plus the storage values lets you attribute that snapshot of freeStorageSpaceInBytes to that user’s session — provided the Intune check-in landed during their connected window.
Approach 4 — In-session telemetry (KQL)#
For customers who ship Cloud PC Perf counters to Log Analytics via the Azure Monitor Agent, DeviceLogonEvents (Defender for Endpoint advanced hunting) joined with the LogicalDisk free-space sample at the same minute attributes the disk reading to the signed-in user:
let logons =
DeviceLogonEvents
| where ActionType == "LogonSuccess" and LogonType in ("Interactive","RemoteInteractive")
| project Computer = DeviceName, AccountUpn, LogonTime = Timestamp;
let disk =
Perf
| where ObjectName == "LogicalDisk" and CounterName == "Free Megabytes" and InstanceName == "C:"
| project Computer, FreeMB = CounterValue, TimeGenerated;
disk
| join kind=inner (logons) on Computer
| where TimeGenerated between (LogonTime .. LogonTime + 8h)
| summarize MinFreeMB = min(FreeMB), MaxFreeMB = max(FreeMB) by Computer, AccountUpn, LogonTime
| order by LogonTime descThis is the only path that produces an honest “user X drove the disk down to N MB free during their session.” It requires AMA + a DCR shipping LogicalDisk(*)\Free Megabytes and Defender for Endpoint onboarding — see [[microsoft-defender-for-endpoint-on-cloud-pc|MDE on Cloud PC]] for the prereqs.
Approach 5 — In-session inspection from a remediation script#
This is the [[intune-proactive-remediations|Intune Remediations]] path — paired
detection + remediation PowerShell scripts shipped through the Intune Management
Extension and harvested back via the report or Graph deviceRunStates.
A scheduled remediation script that runs in the user context on the active Cloud PC can size C:\Users\<upn> directly and write the result to the Remediations report. This is the most direct measurement of the user’s actual on-disk footprint during a session, but it requires (a) the user to be signed in when the script runs and (b) acceptance that the result is wiped at the next snapshot reset.
# Detection script — emits per-user used GB for the currently signed-in user
$upn = (Get-CimInstance Win32_ComputerSystem).UserName
$path = "C:\Users\$($env:USERNAME)"
$used = (Get-ChildItem $path -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object Length -Sum).Sum
"$upn,$([math]::Round($used/1GB,2))"
exit 0Combined per-user projection model#
To answer Bradley’s framing question — “if user X connects to this shared CPC, what does their disk footprint look like?” — combine the three independent signals into a projected on-disk consumption value:
ProjectedUsedGB(user) ≈ BaseImageGB(policy)
+ UES.profileSizeInGB(user) # ceiling, not in-use
+ ExpectedSessionGrowthGB(user) # rolling avg from Approach 4
+ OneDriveHydrationGB(user, files-on-demand cache)
ProjectedFreeGB(user) = TotalStorageSpaceInBytes(policy) / 1GB - ProjectedUsedGB(user)In script form, joining Approach 1 and Approach 2 with a fixed base-image figure for the policy:
# Pseudocode — assumes $profiles from Approach 1 and a $baseImageGB constant per policy
$baseImageGB = 52 # measured once on a freshly snapshot-reset Cloud PC for this policy
$projection = foreach ($p in $profiles) {
$od = Get-MgUserDrive -UserId $p.userPrincipalName -ErrorAction SilentlyContinue
[pscustomobject]@{
UPN = $p.userPrincipalName
UESCeilingGB = $p.profileSizeInGB
OneDriveUsedGB = if ($od) { [math]::Round($od.Quota.Used/1GB,2) } else { 0 }
LastAttached = $p.lastProfileAttachedDateTime
ProjectedMaxOnDiskGB = $baseImageGB + $p.profileSizeInGB
}
}
$projection | Sort-Object ProjectedMaxOnDiskGB -Descending | Format-TableThe result is a ceiling projection — what the OS volume could show in-use if the user fills their UES allotment and pulls a chunk of their OneDrive into the local Files On-Demand cache. Combine with Approach 4’s rolling average for a realistic working figure.
Limitations specific to the Frontline / Flex Shared model#
profileSizeInGBis allocation, not usage. The Graph endpoint exposes the tier, not bytes consumed inside the container. For “how full is user X’s UES storage?” the only first-party answer today is the in-sessionGet-ChildItem C:\Users\<upn>measurement; the User Storage tab in Intune likewise shows tier, not in-use.- Pool-level visibility is good, per-user used-bytes is not.
usedStorageInGBat the pool level is accurate (it backs the alert rule), but it does not decompose to per-user. - No cross-policy aggregation. UES pools are scoped per policy assignment. A user on two policies (rare but possible) has two independent containers, neither of which sees the other.
- Concurrent users on different CPCs in the same pool. Flex Shared is one-user-per-CPC, but a pool of N CPCs can have N users active simultaneously, each with their own UES profile attached on a different VM. Per-user projections must be computed individually; the policy-pool aggregate does not tell you which CPC any given user landed on.
- Snapshot-reset breaks longitudinal disk trends. A
freeStorageSpaceInBytesseries on a Flex Shared CPC sawtooths around the snapshot-reset boundary, not the user-session boundary. UsecloudPcAuditEventfor reset events to segment the series. - OneDrive Files On-Demand can hydrate unpredictably. A user who opens a 30 GB folder pulls placeholders into local cache; the same user on the next session may show a clean cache. KFM size from Approach 2 is the cloud size, not the currently hydrated on the CPC size.
- UES excludes the most volatile profile elements. The
Packages\*\AC/LocalCache/TempStateexclusions mean Edge / Teams / Outlook search index growth is a session-time concern but not a UES growth concern. That’s good for storage planning and bad for predicting peak in-session disk pressure. - Beta endpoints.
cloudPCUserSettingsPersistence*lives under/betaand is subject to change. Pin a snapshot of the documented response shape in your tooling and re-verify quarterly. - Sov-cloud availability. The persistence endpoints are documented as global service only — not available in US Gov L4 / L5 (DOD) / China-21Vianet at time of writing. Customers in those clouds need a different reporting plan (likely Approach 4 + Approach 5 only).
Limitations and gotchas#
No native W365 disk report. Setting customer expectations matters — there is no Cloud PC-specific tile to point at. Lead with Graph.
freeStorageSpaceInBytesdefaults to 0. Per the Graph spec, the default value is 0 if the device has not reported. Treat any zero you see as “unknown,” not “full.” Filter rows wherelastSyncDateTimeis null or where both total and free are zero.OS volume only. Both Graph properties measure the system drive. Additional data disks (rare on Cloud PC, more common on AVD-style configurations — see [[avd-os-disk-types]] for context) are not represented.
Sync latency. A user filling their Cloud PC in the last hour will not show the new value until the next Intune check-in. Force a Sync via Intune or a Graph
syncDeviceaction when investigating an active incident.Requires Intune enrollment. Cloud PCs that fail to enroll in Intune (rare, usually a provisioning policy + identity issue — see [[cloud-pc-provisioning-errors]]) emit no managedDevice record and therefore no storage values.
Flex Shared resets the disk. [[windows-365-flex|Windows 365 Flex]] in [[windows-365-flex-shared-mode|shared mode]] runs the [[windows-365-flex-snapshot-based-reset|snapshot-based reset]] on user sign-out, so free-space on a shared-mode Cloud PC reflects only the active session’s footprint, not cumulative user storage. Reporting against shared Flex Cloud PCs answers “is this session’s disk healthy right now?”, not “is this user storing too much?”
Resize doesn’t move data. A [[cloud-pc-resize|resize]] that grows the SKU also grows the OS disk. A Cloud PC that was full at 64 GB will report ample free space immediately after resize to 128 GB; trend dashboards spanning the resize need to normalize against
totalStorageSpaceInBytes, not absolute free GB.Frontline → Flex naming drift. Some Intune surfaces still label shared-mode Cloud PCs as “Frontline shared.” See [[cloud-pc-frontline-to-flex-rebrand]].
Privacy.
freeStorageSpaceInBytesis treated as device telemetry, not user-content metadata, but reporting it alongsideuserPrincipalNamedoes associate a personal usage pattern with an identified user. Apply the customer’s normal endpoint-monitoring data-handling rules.
Sources#
- managedDevice resource type — Microsoft Graph v1.0
- windowsManagedDevice resource type — Microsoft Graph v1.0
- Cloud PC utilization report — Microsoft Learn
- Cloud PC Resource performance report — Microsoft Learn
- DeviceInfo table — Microsoft Defender XDR advanced hunting
- Use remediations in Microsoft Intune — Microsoft Learn
- Install the Microsoft Graph PowerShell SDK — Microsoft Learn
- User Experience Sync for Flex Shared — Microsoft Learn
- cloudPCUserSettingsPersistenceDetail (beta) — Microsoft Graph
- cloudPCUserSettingsPersistenceProfile (beta) — Microsoft Graph
- cloudPCUserSettingsPersistenceUsageResult (beta) — Microsoft Graph
- retrieveUserSettingsPersistenceProfiles (beta) — Microsoft Graph
- retrieveUserSettingsPersistenceProfileUsage (beta) — Microsoft Graph
- Snapshot-based reset for Flex shared Cloud PCs — Microsoft Learn
- reportRoot: getOneDriveUsageAccountDetail — Microsoft Graph v1.0
- cloudPcAuditEvent resource type — Microsoft Graph v1.0
- Get Windows 365 audit logs — Microsoft Learn
