Public/Publish-VMPerformanceData.ps1

function Publish-VMPerformanceData {
    <#
    .SYNOPSIS
        Uploads a VMPerformance CSV (and its sidecar manifest) to Azure Blob Storage under
        {customer}/{date}/ in a single container.

    .DESCRIPTION
        Uploads to blob path '{Customer}/{Date}/{fileName}' using account-key auth. The
        sidecar '<name>.manifest.json' is uploaded alongside when present. Existing blobs
        are overwritten (idempotent re-run). Designed to run from Azure Cloud Shell.

    .PARAMETER Path
        Local CSV file to upload.

    .PARAMETER Customer
        Real customer name; first blob path segment (e.g. 'WorkSafe BC').

    .PARAMETER StorageAccountName
        Target storage account, e.g. 'projectmgmtfinopsnav'.

    .PARAMETER ContainerName
        Blob container (default 'vmperformance'); created if missing.

    .PARAMETER StorageAccountKey
        Account key. If omitted, -StorageAccountResourceGroup is used to fetch it.

    .PARAMETER StorageAccountResourceGroup
        Resource group of the storage account, used to fetch the key when none is supplied.

    .PARAMETER Date
        Run date 'yyyy-MM-dd' for the blob path. Defaults to the date parsed from the file
        name (VMPerformance-YYYY-MM-DD), else today (UTC).

    .OUTPUTS
        [pscustomobject] with BlobUri and ManifestUri (ManifestUri is $null when absent).
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)] [ValidateScript({ Test-Path $_ -PathType Leaf })] [string] $Path,
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Customer,
        [Parameter(Mandatory)] [string] $StorageAccountName,
        [string] $ContainerName = 'vmperformance',
        [string] $StorageAccountKey,
        [string] $StorageAccountResourceGroup,
        [ValidatePattern('^\d{4}-\d{2}-\d{2}$')] [string] $Date
    )

    if (-not $Date) {
        $leaf = [System.IO.Path]::GetFileNameWithoutExtension($Path)
        if ($leaf -match '(\d{4}-\d{2}-\d{2})') {
            $Date = $Matches[1]
        }
        else {
            $Date = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd')
        }
    }

    $ctx = New-VMMetricsStorageContext -StorageAccountName $StorageAccountName `
        -ContainerName $ContainerName -StorageAccountKey $StorageAccountKey `
        -ResourceGroupName $StorageAccountResourceGroup

    $csvName  = Split-Path -Leaf $Path
    $csvBlob  = Get-VMMetricsBlobName -Customer $Customer -Date $Date -FileName $csvName

    $blobUri = $null
    $manifestUri = $null

    if ($PSCmdlet.ShouldProcess("$ContainerName/$csvBlob", "Upload CSV")) {
        $b = Set-AzStorageBlobContent -File $Path -Container $ContainerName -Blob $csvBlob `
            -Context $ctx -Force -ErrorAction Stop
        $blobUri = $b.ICloudBlob.Uri.AbsoluteUri
        Write-Verbose "Uploaded CSV -> $blobUri"
    }

    # Sidecar manifest, if present next to the CSV (same base name + .manifest.json).
    $manifestLocal = Join-Path (Split-Path -Parent $Path) ((Split-Path -LeafBase $Path) + '.manifest.json')
    if (Test-Path $manifestLocal -PathType Leaf) {
        $manifestName = Split-Path -Leaf $manifestLocal
        $manifestBlob = Get-VMMetricsBlobName -Customer $Customer -Date $Date -FileName $manifestName
        if ($PSCmdlet.ShouldProcess("$ContainerName/$manifestBlob", "Upload manifest")) {
            $m = Set-AzStorageBlobContent -File $manifestLocal -Container $ContainerName -Blob $manifestBlob `
                -Context $ctx -Force -ErrorAction Stop
            $manifestUri = $m.ICloudBlob.Uri.AbsoluteUri
            Write-Verbose "Uploaded manifest -> $manifestUri"
        }
    }

    [pscustomobject]@{
        BlobUri     = $blobUri
        ManifestUri = $manifestUri
    }
}