VMware Cloud Director · PowerCLI · Automation

VMware Cloud Director Capacity & Storage Profile Reporting with PowerCLI

Published · Ravindrakumar Narayanan
VCD PowerCLI Get-OrgVdc Storage Profiles Search-Cloud CSV / Out-GridView Capacity Planning

A single PowerCLI script that walks every Organisation and Org VDC in a VMware Cloud Director instance and exports CPU, RAM and per-storage-profile capacity — to CSV, an interactive Out-GridView window, or both. This post also covers two problems you'll likely hit the moment you try this yourself: the StorageProfiles property silently coming back empty, and numbers rendering with a comma instead of a period depending on regional settings.

Why a per-storage-profile report

Most quick PowerCLI snippets for VMware Cloud Director capacity reporting pull a single blended storage figure per Org VDC. That's fine until a VDC has more than one storage tier — say a flash-backed "Gold" profile and a capacity-backed "Bronze" profile — at which point one number hides exactly the detail you need for capacity planning or a migration sizing exercise. This script instead emits one row per storage profile, so a VDC with two tiers becomes two rows, each with its own allocated/used/limit figures and percentage used.

It also enumerates every Organisation it can see, not just one — useful if you're managing a multi-tenant estate or auditing capacity across several customer Orgs in one pass.

Prerequisites

How the script works, step by step

The script runs in four phases. Each one maps directly to a section of the code, so if you're reading the source alongside this, you'll recognise the boundaries.

Phase 1 — Connect & prepare

The script checks that VMware.VimAutomation.Cloud is installed, imports it, then calls Connect-CIServer against the server you pass in via -VCDServer. If you don't supply -Credential, it prompts interactively — nothing is ever hardcoded.

Before touching any data, it also forces the PowerShell session's number formatting to use a period as the decimal separator for the rest of the run (more on why in step 5), and restores your original setting automatically when the script finishes.

Phase 2 — Resolve storage profile data once, up front

Rather than asking each VDC for its storage profiles one at a time, the script makes a single bulk call to Search-Cloud covering the whole vCD instance, and builds an in-memory lookup table keyed by VDC GUID. This is both faster across a large estate and the foundation of the fallback covered in step 4.

Phase 3 — Enumerate Orgs → VDCs → storage profiles

For every Organisation (Get-Org), the script pulls its Org VDCs (Get-OrgVdc), and for each VDC:

  • Reads CPU and RAM allocated / limit / used figures.
  • Resolves the Network Pool and Provider VDC name from the underlying API object.
  • Builds one output row per storage profile on that VDC, trying three data sources in order until one returns something (the fallback in step 4).

Orgs with no VDCs, and VDCs with no resolvable storage profile, still produce a row — clearly labelled — rather than silently disappearing from the report.

Phase 4 — Export and/or display

All rows are sorted by Org, VDC and storage profile name, then:

  • Written to CSV (semicolon-delimited, UTF-8) unless -NoCsv is passed.
  • Opened in an interactive Out-GridView window if -GridView is passed.

A breakdown of which data source supplied each row is printed to the console before the session disconnects — useful for sanity-checking the report before you send it anywhere.

The StorageProfiles gotcha

The "friendly" Get-OrgVdc object exposes a StorageProfiles property that, on paper, gives you exactly the per-profile breakdown this report needs. In practice, against some vCD versions and account types, that property comes back empty for every single VDC — not because the data doesn't exist, but because the friendly wrapper's link resolution doesn't always populate it. The deprecated VDC-level aggregate properties (StorageUsedGB, etc.) still work in many environments, but they collapse multiple storage tiers into one blended figure and Broadcom has flagged them for removal.

To get a reliable answer regardless of which scenario you're in, the script tries three sources in order and only moves to the next one if the current one comes back empty:

Tier 1 — StorageProfiles property

Fast path. Used directly whenever $vdc.StorageProfiles is actually populated.

↓ if empty
Tier 2 — Search-Cloud query service

Queries vCD's search/query API directly with -QueryType AdminOrgVdcStorageProfile, falling back to the lower-privilege OrgVdcStorageProfile query type if the admin one isn't permitted. Matches results back to each VDC by the GUID embedded in the record's href — not by name, since VDC names aren't guaranteed unique across different Orgs.

↓ if empty
Tier 3 — Deprecated VDC aggregate (last resort)

Falls back to the old blended StorageLimitGB / StorageUsedGB properties so the row still has a number rather than a blank cell.

Why this matters for trust in the report Every row carries a StorageDataSource column showing exactly which tier produced it. If you ever see "VDC Aggregate (legacy)" rows, that's your signal the account or vCD version couldn't supply per-profile detail for that VDC — worth a closer look rather than assuming it's wrong.

The comma-vs-period decimal bug

[math]::Round() always returns the mathematically correct number. How that number gets displayed — in Out-GridView, on the console, or written into the CSV — depends on the current culture/regional settings of the machine running the script, not on the script's logic. On a system whose region uses a comma as the decimal separator (common across continental Europe), 12.34 renders as 12,34. The value was never wrong; only its text rendering was locale-dependent.

Fixing this needed two settings, not one. Setting only the calling thread's culture fixes the CSV and console output, but Out-GridView opens its window on a separate UI thread that doesn't inherit that override — it falls back to the system's regional settings unless the process-wide default is also set. The script sets both ([Threading.Thread]::CurrentThread.CurrentCulture and [CultureInfo]::DefaultThreadCurrentCulture) to InvariantCulture for the duration of the run, and restores your original settings afterwards in a finally block — so it never leaks into the rest of your session.

PowerShell — the fix, in full
$origCulture           = [Threading.Thread]::CurrentThread.CurrentCulture
$origUICulture          = [Threading.Thread]::CurrentThread.CurrentUICulture
$origDefaultCulture     = [CultureInfo]::DefaultThreadCurrentCulture
$origDefaultUICulture   = [CultureInfo]::DefaultThreadCurrentUICulture

try {
    [Threading.Thread]::CurrentThread.CurrentCulture     = [CultureInfo]::InvariantCulture
    [Threading.Thread]::CurrentThread.CurrentUICulture   = [CultureInfo]::InvariantCulture
    [CultureInfo]::DefaultThreadCurrentCulture           = [CultureInfo]::InvariantCulture
    [CultureInfo]::DefaultThreadCurrentUICulture         = [CultureInfo]::InvariantCulture

    # ... rest of the script ...
}
finally {
    [Threading.Thread]::CurrentThread.CurrentCulture     = $origCulture
    [Threading.Thread]::CurrentThread.CurrentUICulture   = $origUICulture
    [CultureInfo]::DefaultThreadCurrentCulture           = $origDefaultCulture
    [CultureInfo]::DefaultThreadCurrentUICulture         = $origDefaultUICulture
}
If you're sharing the CSV with a European-locale Excel Forcing period decimals makes the CSV portable, but a Dutch- or German-locale Excel may then treat the numeric columns as text rather than auto-recognising them as numbers, since it still expects a comma. If that happens for your audience, either re-import with Excel's "Text to Columns" using a period as the decimal setting, or ask for a locale-specific export — worth confirming with whoever receives the report.

Usage & parameters

Server names and credentials below are placeholders — swap in your own vCD cell's address.

ParameterRequiredDescription
-VCDServerYesFQDN or IP of the vCD cell, e.g. vcloud.contoso.com.
-CredentialNoA PSCredential. Omit it to be prompted interactively.
-OutputPathNoCSV destination. Defaults to a timestamped file in the current folder.
-GridViewNoSwitch. Also opens the results in an interactive, sortable/filterable grid window.
-NoCsvNoSwitch. Skips writing the CSV — handy with -GridView for a quick on-screen check.
PowerShell — basic run, CSV only
.\Export-vCDOrgCapacityReport.ps1 -VCDServer vcloud.contoso.com
PowerShell — supply credentials, interactive grid only
$cred = Get-Credential admin@automatewithravi.com
.\Export-vCDOrgCapacityReport.ps1 -VCDServer vcloud.contoso.com -Credential $cred -GridView -NoCsv

Sample output

An illustrative excerpt — Org, VDC and customer names below are all generic placeholders, not real environments:

OrgNameVDCNameAllocationModel RAM Allocated (GB)StorageProfileName Storage Limit (GB)Storage Used (GB)StorageDataSource
Contoso-ProdContoso-Prod-VDC01AllocationPool 512.00Gold-NVMe2000.00843.50 Search-Cloud (AdminOrgVdcStorageProfile)
Contoso-ProdContoso-Prod-VDC01AllocationPool 512.00Bronze-SAS5000.001240.25 Search-Cloud (AdminOrgVdcStorageProfile)
Fabrikam-DRFabrikam-DR-VDC01PayAsYouGo 256.00(aggregate)1500.00410.00 VDC Aggregate (legacy)

Troubleshooting

Get the full script

Export-vCDOrgCapacityReport.ps1 on GitHub

View on GitHub
← Back to all blogs