Public/Get-AzVMUtilization.ps1

function Get-AzVMUtilization {
    <#
    .SYNOPSIS
        Pulls CPU and memory platform metrics for a single VM and returns monthly
        VMPerformance-compatible rows.

    .DESCRIPTION
        Pulls 'Percentage CPU' and 'Available Memory Bytes' from Azure Monitor over the
        given window, resolves the SKU's total RAM for the committed-memory proxy, and
        aggregates everything into per-month rows via ConvertTo-VMPerformanceRow.

        Accepts a VM object (as returned by Get-AzVM), or the discrete identity
        parameters when you already have them.

    .PARAMETER VM
        A VM object from Get-AzVM. Its Id, Location, Name and HardwareProfile.VmSize are used.

    .PARAMETER ResourceId
        ARM resource id of the VM (alternative to -VM).

    .PARAMETER VMName
        VM name (alternative to -VM).

    .PARAMETER Location
        VM region (alternative to -VM).

    .PARAMETER SkuName
        VM size, e.g. 'Standard_D4as_v5' (alternative to -VM). Optional; without it the
        committed-memory proxy counter is skipped.

    .PARAMETER StartTime
        UTC window start.

    .PARAMETER EndTime
        UTC window end.

    .PARAMETER TimeGrain
        Aggregation grain, default 00:05:00 (PT5M).

    .OUTPUTS
        [pscustomobject[]] VMPerformance rows for this VM.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Object')]
    [OutputType([pscustomobject[]])]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Object', ValueFromPipeline)]
        [object] $VM,

        [Parameter(Mandatory, ParameterSetName = 'Explicit')] [string] $ResourceId,
        [Parameter(Mandatory, ParameterSetName = 'Explicit')] [string] $VMName,
        [Parameter(Mandatory, ParameterSetName = 'Explicit')] [string] $Location,
        [Parameter(ParameterSetName = 'Explicit')] [string] $SkuName,

        [Parameter(Mandatory)] [datetime] $StartTime,
        [Parameter(Mandatory)] [datetime] $EndTime,
        [timespan] $TimeGrain = ([timespan]'00:05:00')
    )

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Object') {
            $ResourceId = $VM.Id
            $VMName     = $VM.Name
            $Location   = $VM.Location
            $SkuName    = $VM.HardwareProfile.VmSize
        }

        Write-Verbose "Pulling metrics for $VMName ($SkuName, $Location) over $StartTime..$EndTime"

        $cpu = Get-VMMetricSeries -ResourceId $ResourceId -MetricName 'Percentage CPU' `
            -StartTime $StartTime -EndTime $EndTime -TimeGrain $TimeGrain
        $mem = Get-VMMetricSeries -ResourceId $ResourceId -MetricName 'Available Memory Bytes' `
            -StartTime $StartTime -EndTime $EndTime -TimeGrain $TimeGrain

        $totalRamMB = $null
        if ($SkuName) {
            try {
                $totalRamMB = Resolve-VMSkuRam -SkuName $SkuName -Location $Location
            }
            catch {
                Write-Warning "Could not resolve RAM for SKU '$SkuName' in '$Location': $($_.Exception.Message)"
            }
        }
        if ($null -eq $totalRamMB) {
            Write-Warning "No total RAM for $VMName ($SkuName); '% Committed Bytes In Use' proxy will be skipped."
        }

        $convertArgs = @{
            VMName   = $VMName
            CpuPoint = @($cpu)
            MemPoint = @($mem)
        }
        if ($null -ne $totalRamMB) { $convertArgs['TotalRamMB'] = $totalRamMB }

        ConvertTo-VMPerformanceRow @convertArgs
    }
}