Broadcom.Community.VCFLicensing.psm1

# Author: William Lam
# Description: PowerShell Module to automate between Broadcom VCF Business Service Console (BSC) and VCF Operations 9.x

# --- Helper Functions for Part Construction ---
Function Get-MultipartFormDataPart {
    param (
        [string] $Name,
        [string] $Value,
        [string] $Boundary
    )
    # The string must be fully formatted before converting to bytes
    $part = ""
    $part += "--$Boundary`r`n"
    $part += "Content-Disposition: form-data; name=`"$Name`"`r`n"
    $part += "`r`n"
    $part += "$Value`r`n"

    # Using ASCII for headers and boundaries is standard/safer than UTF8
    return [System.Text.Encoding]::ASCII.GetBytes($part)
}

Function Get-MultipartFilePart {
    param (
        [string] $Name,
        [string] $FilePath,
        [string] $Boundary
    )

    $fileName = [System.IO.Path]::GetFileName($FilePath)
    $contentBytes = [System.IO.File]::ReadAllBytes($FilePath)
    $contentType = "application/octet-stream"

    $partHeader = ""
    # Add an extra CRLF before the boundary if it's the second part
    $partHeader += "`r`n--$Boundary`r`n"
    $partHeader += "Content-Disposition: form-data; name=`"$Name`"; filename=`"$fileName`"`r`n"
    $partHeader += "Content-Type: $contentType`r`n"
    $partHeader += "`r`n" # Blank line separates headers from content

    # Convert header parts to ASCII bytes
    $headerBytes = [System.Text.Encoding]::ASCII.GetBytes($partHeader)
    $trailerBytes = [System.Text.Encoding]::ASCII.GetBytes("`r`n") # CRLF after file content

    # Combine: Header Bytes + File Content Bytes + Trailer Bytes
    $combined = New-Object System.Byte[] ($headerBytes.Length + $contentBytes.Length + $trailerBytes.Length)
    [Array]::Copy($headerBytes, 0, $combined, 0, $headerBytes.Length)
    [Array]::Copy($contentBytes, 0, $combined, $headerBytes.Length, $contentBytes.Length)
    [Array]::Copy($trailerBytes, 0, $combined, $headerBytes.Length + $contentBytes.Length, $trailerBytes.Length)

    return $combined
}
# --- End Helper Functions ---

Function Invoke-MultipartUpload {
    param(
        [Parameter(Mandatory=$true)]
        [string]$Uri,

        [Parameter(Mandatory=$true)]
        [Hashtable]$Headers,

        [Parameter(Mandatory=$true)]
        [string]$FilePath,

        [Parameter(Mandatory=$false)]
        [string]$NameValue = $null,

        [Parameter(Mandatory=$false)]
        [switch]$SkipCertCheck,

        [Parameter(Mandatory=$false)]
        [boolean]$Troubleshoot=$false
    )

    # FIXED FIELD NAMES
    $FileNameField = "file"
    $NameFieldName = "name"

    # Input validation
    if (-not (Test-Path $FilePath)) {
        Write-Error "File not found: $FilePath"
        return
    }

    # 1. --- Generate Unique Boundary ---
    $Boundary = "----PowerShellBoundary$([Guid]::NewGuid().ToString().Replace('-', ''))"

    # 2. --- Define Content-Type Header ---
    $ContentType = "multipart/form-data; boundary=$Boundary"
    $Headers["Content-Type"] = $ContentType

    # --- DEBUG INFO ---
    if($Troubleshoot) {
        Write-Host "--- DEBUG INFO ---"
        Write-Host "URI: $Uri"
        Write-Host "Generated Boundary: $Boundary"
        Write-Host "Content-Type Header: $ContentType"
        Write-Host "File Path: $FilePath"
        Write-Host "Name Value: $(if ($NameValue) {"$NameValue (Included)"} else {"N/A (Skipped)"})"
        Write-Host "SkipCertificateCheck: $SkipCertCheck"
        Write-Host "------------------"
    }

    # 3. --- Build the full body as a stream of bytes ---
    $bodyStream = New-Object System.IO.MemoryStream

    try {
        # A. Conditional: Add the "name" part (Text Field)
        # (Logic unchanged from previous revision)
        if ($NameValue) {
            $nameBytes = Get-MultipartFormDataPart -Name $NameFieldName -Value $NameValue -Boundary $Boundary
            $bodyStream.Write($nameBytes, 0, $nameBytes.Length)
        }

        # B. Add the "file" part (Binary File)
        # (Logic unchanged from previous revision)
        $filePartBytes = @()
        if ($NameValue) {
            $filePartBytes = Get-MultipartFilePart -Name $FileNameField -FilePath $FilePath -Boundary $Boundary
        } else {
            # Manual assembly for file-only upload (first part)
            $fileName = [System.IO.Path]::GetFileName($FilePath)
            $contentType = "application/octet-stream"

            $partHeader = ""
            $partHeader += "--$Boundary`r`n"
            $partHeader += "Content-Disposition: form-data; name=`"$FileNameField`"; filename=`"$fileName`"`r`n"
            $partHeader += "Content-Type: $contentType`r`n"
            $partHeader += "`r`n" # Blank line separates headers from content

            $headerBytes = [System.Text.Encoding]::ASCII.GetBytes($partHeader)
            $contentBytes = [System.IO.File]::ReadAllBytes($FilePath)
            $trailerBytes = [System.Text.Encoding]::ASCII.GetBytes("`r`n") # CRLF after file content

            $combined = New-Object System.Byte[] ($headerBytes.Length + $contentBytes.Length + $trailerBytes.Length)
            [Array]::Copy($headerBytes, 0, $combined, 0, $headerBytes.Length)
            [Array]::Copy($contentBytes, 0, $combined, $headerBytes.Length, $contentBytes.Length)
            [Array]::Copy($trailerBytes, 0, $combined, $headerBytes.Length + $contentBytes.Length, $trailerBytes.Length)

            $filePartBytes = $combined
        }

        $bodyStream.Write($filePartBytes, 0, $filePartBytes.Length)

        # C. Add the closing boundary
        $closing = "--$Boundary--`r`n"
        $closingBytes = [System.Text.Encoding]::ASCII.GetBytes($closing)
        $bodyStream.Write($closingBytes, 0, $closingBytes.Length)

        # Reset stream position to the beginning for reading by Invoke-WebRequest
        $bodyStream.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null

        # 4. --- Execute the Request ---
        # (Splatting logic unchanged)
        $IWRParams = @{
            Uri = $Uri
            Method = 'Post'
            Headers = $Headers
            Body = $bodyStream
        }
        if ($SkipCertCheck) {
            $IWRParams.Add('SkipCertificateCheck', $true)
        }

        $Response = Invoke-WebRequest @IWRParams

        Write-Host "Upload successful. HTTP Status Code: $($Response.StatusCode)"
        return $Response
    }
    # ... (Catch and Finally blocks remain the same)
    catch {
        Write-Error "Upload failed: $($_.Exception.Message)"
        # Capture and display the error body for 500 errors
        if ($_.Exception.Response -is [System.Net.HttpWebResponse]) {
            $ErrorResponse = $_.Exception.Response
            $StreamReader = New-Object System.IO.StreamReader($ErrorResponse.GetResponseStream())
            $ErrorBody = $StreamReader.ReadToEnd()
            Write-Error "Internal Server Error (500) Body:"
            Write-Error $ErrorBody
        }
        return $_
    }
    finally {
        if ($bodyStream) {
            $bodyStream.Dispose()
        }
    }
}

Function Connect-VcfBsc {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Connect to the VCF Business Service Console
        .DESCRIPTION
            This cmdlet creates $global:bscConnection object containing valid access token
        .PARAMETER ClientId
            The OAuth Client ID generated from VCF Business Service Console UI
        .PARAMETER SecretId
            The OAuth Secret ID generated from VCF Business Service Console UI

        .EXAMPLE
            Connect-VcfBsc -ClientID $ClientId -SecretId $SecretId
    #>

    Param (
        [Parameter(Mandatory=$true)][String]$ClientId,
        [Parameter(Mandatory=$true)][String]$SecretId,
        [Switch]$Troubleshoot
    )

    $body = @{
        "grant_type" = "client_credentials"
        "client_id" = $ClientId
        "client_secret" = $SecretId
    }

    $uri = "https://eapi.broadcom.com/vcf/generateToken"
    $method = "POST"

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
        Write-Host -ForegroundColor cyan "[DEBUG]`n$($body | Out-String)`n"
    }

    try {
        $requests = Invoke-WebRequest -Uri $uri -Method $method -Headers @{"Content-Type" = "application/x-www-form-urlencoded"} -Body $body
    } catch {
        Write-Error "Error in requesting Access Token"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }

    if($requests.StatusCode -eq 200) {
        $accessToken=($requests.Content | ConvertFrom-Json).access_token

        $headers = @{
            "Authorization" = "Bearer $accessToken"
        }

        $global:bscConnection = new-object PSObject -Property @{
            'headers' = $headers
        }

        $global:bscConnection | Out-Null
    } else {
        Write-Host "Something went wrong with auth"
    }
}

Function Register-VcfOperations {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Register VCF Operations instance given a registration file
        .DESCRIPTION
            This cmdlet register VCF Operations instance given a registration file
        .PARAMETER TenantId
            The BSC Tenant ID (retrieved through VCF BSC UI)
        .PARAMETER RegistrationFile
            The filename of the exported registration file from VCF Operations
        .PARAMETER Name
            The human friendly label to use for the registered VCF Operations in VCF BSC

        .EXAMPLE
            $VCF_OPERATIONS_REGISTRATION_LABEL="vcf01.vcf.lab"
            $VCF_OPERATIONS_REGISTRATION_FILE="Registration-vcf01.vcf.lab-2025-12-16T15_03_43Z.data"

            Register-VcfOperations -TenantId $VCF_BSC_TENANT_ID -RegistrationFile $VCF_OPERATIONS_REGISTRATION_FILE -Name $VCF_OPERATIONS_REGISTRATION_LABEL
    #>

    Param (
        [Parameter(Mandatory=$true)][String]$TenantId,
        [Parameter(Mandatory=$true)][String]$RegistrationFile,
        [Parameter(Mandatory=$true)][String]$Name,
        [Switch]$Troubleshoot
    )

    $headers = @{
        "Authorization" = $global:bscConnection.Headers.Authorization
    }

    $uri = "https://eapi.broadcom.com/vcf/license-mgmt/api/v1/tenants/${TenantId}/appliance-registration/upload"

    Write-Host "Uploading VCF Operations (${VCF_OPERATIONS_REGISTRATION_LABEL}) Registration File to Broadcom Business Service Console ..."
    try {
        if($Troubleshoot) {
            $requests = Invoke-MultipartUpload -Uri $uri -Headers $headers -FilePath $RegistrationFile -NameValue $Name -Troubleshoot $true
        } else {
            $requests = Invoke-MultipartUpload -Uri $uri -Headers $headers -FilePath $RegistrationFile -NameValue $Name
        }
    } catch {
        Write-Error "Error in registering VCF Operations"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }

    if($requests.StatusCode -eq 200) {
        ($requests.Content | ConvertFrom-Json)
    }
}

Function Get-VcfBscCLicense {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Returns the list of licenses from VCF Business Service Console
        .DESCRIPTION
            This cmdlet returns the list of licenses from VCF Business Service Console
        .PARAMETER TenantId
            The BSC Tenant ID (retrieved through VCF BSC UI)
        .PARAMETER Name
            The name of the license label to filter results

        .EXAMPLE
            Get-VcfBscCLicense -TenantId $VCF_BSC_TENANT_ID

            Get-VcfBscCLicense -TenantId $VCF_BSC_TENANT_ID -Name $VCF_LICENSE_NAME
    #>

    Param (
        [Parameter(Mandatory=$true)][String]$TenantId,
        [Parameter(Mandatory=$false)][String]$Name,
        [Switch]$Troubleshoot
    )

    $headers = @{
        "Authorization" = $global:bscConnection.Headers.Authorization
        "Content-Type"="application/json"
        "Accept"="application/json"
    }

    $uri = "https://eapi.broadcom.com/vcf/license-mgmt/api/v1/tenants/${TenantId}/allocations/search"
    $method = "POST"

    <# TODO Look into server side filtering
    $payload = [ordered]@{
        "filters" = @(
            [ordered]@{
                "key" = "NAME"
                "operator" = "EQUALS"
                "value" = $Name
            }
        )
    }

    $body = $payload | ConvertTo-Json
    #>


    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
        Write-Host -ForegroundColor cyan "[DEBUG]`n$($body | Out-String)`n"
    }

    try {
        $results = Invoke-WebRequest -Method $method -Uri $uri -Headers $headers

    } catch {
        Write-Error "Error in retrieving BSC License"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }

    if($results.StatusCode -eq 200) {
        $licenses = ($results.Content | ConvertFrom-Json).results

        if ($PSBoundParameters.ContainsKey("Name")){
            $licenses = $licenses | where {$_.name -eq $Name}
        }

        return $licenses | select id, name, quantity, status, product
    }
}

Function Set-VcfBscCLicense {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Associate or Disassociate license to registered VCF Operations instance in VCF Business Service Console
        .DESCRIPTION
            This cmdlet associates or disassociates license to registered VCF Operations instance in VCF Business Service Console
        .PARAMETER TenantId
            The BSC Tenant ID (retrieved through VCF BSC UI)
        .PARAMETER VcfOperationsId
            The Id of the registered VCF Operations (returned from Register-VcfOperations)
        .PARAMETER LicenseIds
            List of license IDs (returned from Get-VcfBscCLicense)
        .PARAMETER Operation
            Associate or Disassociate

        .EXAMPLE
            $VCF_BSC_OPERATIONS_REGISTRATION_ID="f8d3966c-9a82-3cf6-e797-2392b311ed23"
            $VCF_BSC_LICENSE_IDS=@("efaa38b7-08ac-452f-929b-d30f6d37fba5","fc711690-f8d3-4209-a06e-529c39979251")

            Set-VcfBscCLicense -TenantId $VCF_BSC_TENANT_ID -VcfOperationsId $VCF_BSC_OPERATIONS_REGISTRATION_ID -LicenseIds $VCF_BSC_LICENSE_IDS -Operation Associate

    #>

    Param (
        [Parameter(Mandatory=$true)][String]$TenantId,
        [Parameter(Mandatory=$true)][String]$VcfOperationsId,
        [Parameter(Mandatory=$true)][String[]]$LicenseIds,
        [Parameter(Mandatory=$true)][ValidateSet("Associate","Dissociate")]$Operation,
        [Switch]$Troubleshoot
    )

    $headers = @{
        "Authorization" = $global:bscConnection.Headers.Authorization
        "Content-Type"="application/json"
        "Accept"="application/json"
    }

    $uri = "https://eapi.broadcom.com/vcf/license-mgmt/api/v1/tenants/${TenantId}/licenses"
    $method = "POST"

    if($Operation -eq "Associate") {
        $payload = [ordered]@{
            operation = "ASSOCIATE"
            license_associate_request = @{
                vcf_ops_id = $VcfOperationsId
                ids = @($LicenseIds)
            }
        }
    } else {
        $payload = [ordered]@{
            operation = "DISSOCIATE"
            license_dissociate_request = @{
                vcf_ops_id = $VcfOperationsId
                ids = @($LicenseIds)
            }
        }
    }

    $body = $payload | ConvertTo-Json

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
        Write-Host -ForegroundColor cyan "[DEBUG]`n$($body | Out-String)`n"
    }

    Write-Host "$Operation license(s) to VCF Operations Id: $VcfOperationsId ...`n"
    try {
        $results = Invoke-WebRequest -Method $method -Uri $uri -Headers $headers -Body $body
    } catch {
        Write-Error "Error in $operation BSC License"
        Write-Error "`n($_.Exception.Message)`n"
        $results
        break
    }

    if($results.StatusCode -eq 200) {
        return ($results.Content | ConvertFrom-Json).results | select id, name, quantity, status, product
    }
}

Function Download-VcfBscLicense  {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Download generated license from VCF Business Service Console
        .DESCRIPTION
            This cmdlet downloads generated license from VCF Business Service Console
        .PARAMETER TenantId
            The BSC Tenant ID (retrieved through VCF BSC UI)
        .PARAMETER VcfOperationsId
            The registered VCF Operations Id
        .PARAMETER LicenseFile
            The filename where the license file will be saved

        .EXAMPLE
            $VCF_LICENSE_FILE="vcf01.vcf.lab.lic"

            Download-VcfBscLicense -TenantId $VCF_BSC_TENANT_ID -VcfOperationsId $VCF_BSC_OPERATIONS_REGISTRATION_ID -LicenseFile $VCF_LICENSE_FILE
    #>

    Param (
        [Parameter(Mandatory=$true)][String]$TenantId,
        [Parameter(Mandatory=$true)][String]$VcfOperationsId,
        [Parameter(Mandatory=$true)][String]$LicenseFile,
        [Switch]$Troubleshoot
    )

    $headers = @{
        "Authorization" = $global:bscConnection.Headers.Authorization
        "Content-Type"="application/json"
        "Accept"="application/json"
    }

    $uri = "https://eapi.broadcom.com/vcf/license-mgmt/api/v1/tenants/${TenantId}/vcf-ops/${VcfOperationsId}/licenses/download"
    $method = "GET"

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
    }

    try {
        Write-Host "Downloading license file $LicenseFile ...`n"
        $results = Invoke-WebRequest -Method $method -Uri $uri -Headers $headers -OutFile $LicenseFile

    } catch {
        Write-Error "Error in downloading BSC License"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }
}

Function Connect-VcfOperations {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Connect to VCF Operations
        .DESCRIPTION
            This cmdlet creates $global:vcfOpsConnection object containing valid access token
        .PARAMETER Fqdn
            IP Address/Hostname of VCF Operations
        .PARAMETER User
            The username to login to VCF Operations
        .PARAMETER Password
            The password to login to VCF Operations

        .EXAMPLE
            $VCF_OPERATIONS_HOSTNAME="vcf01.vcf.lab"
            $VCF_OPERATIONS_USERNAME="admin"
            $VCF_OPERATIONS_PASSWORD=''

            Connect-VcfOperations -Fqdn $VCF_OPERATIONS_HOSTNAME -User $VCF_OPERATIONS_USERNAME -Password $VCF_OPERATIONS_PASSWORD
    #>

    Param (
        [Parameter(Mandatory=$true)][String]$Fqdn,
        [Parameter(Mandatory=$true)][String]$User,
        [Parameter(Mandatory=$true)][String]$Password,
        [Switch]$Troubleshoot
    )

    $payload = @{
        username = $User
        password = $Password
        authSource = "local"
    }

    $body = $payload | ConvertTo-Json

    $uri = "https://${Fqdn}/suite-api/api/auth/token/acquire"
    $method = "POST"

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
        Write-Host -ForegroundColor cyan "[DEBUG]`n$($body | Out-String)`n"
    }

    try {
        $requests = Invoke-WebRequest -Uri $uri -Method $method -Headers @{"Content-Type" = "application/json";"Accept" = "application/json"} -Body $body -SkipCertificateCheck
    } catch {
        Write-Error "Error in requesting Access Token"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }

    if($requests.StatusCode -eq 200) {
        $accessToken=($requests.Content | ConvertFrom-Json).token

        $headers = @{
            "Authorization" = "OpsToken $accessToken"
            "Content-Type" = "application/json"
            "Accept" = "application/json"
            "X-Ops-API-use-unsupported" = "true"
        }

        $global:vcfOpsConnection = new-object PSObject -Property @{
            'Server' = $Fqdn
            'headers' = $headers
        }

        $global:vcfOpsConnection | Out-Null
    } else {
        Write-Host "Something went wrong with auth"
    }
}

Function Download-VcfOperationsRegistrationFile {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Download registration file from VCF Operations
        .DESCRIPTION
            This cmdlet downloads registration file from VCF Operations

        .EXAMPLE
            Download-VcfOperationsRegistrationFile
    #>

    Param (
        [Switch]$Troubleshoot
    )

    $uri = "https://$($global:vcfOpsConnection.server)/suite-api/internal/extension/vcf-license-cloud-integration/registration/offline/request"
    $method = "POST"

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
    }

    try {
        $requests = Invoke-WebRequest -Method $method -Uri $uri -Headers $global:vcfOpsConnection.headers -SkipCertificateCheck
    } catch {
        Write-Error "Error in downloading registration file"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }

    if($requests.StatusCode -eq 200) {
        $result = ($requests.Content | ConvertFrom-Json)

        $RegistrationFile = $result.fileName
        $RegistrationData = $result.jwsEncodedData

        Write-Host "Successfully downloaded registration file $RegistrationFile`n"
        $RegistrationData | Out-File -FilePath $RegistrationFile
    }
}

Function Import-VcfOperationsLicenseFile {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Import generated license file from VCF Business Service Console into VCF Operations
        .DESCRIPTION
            This cmdlet imports generated license file from VCF Business Service Console into VCF Operations
        .PARAMETER LicenseFile
            The name of the license file to import

        .EXAMPLE
            $VCF_LICENSE_FILE="vcf01.vcf.lab.lic"

            Import-VcfOperationsLicenseFile -LicenseFile $VCF_LICENSE_FILE
    #>

    Param (
        [Parameter(Mandatory=$true)][String]$LicenseFile,
        [Switch]$Troubleshoot
    )

    $uri = "https://$($global:vcfOpsConnection.server)/suite-api/internal/extension/vcf-license-cloud-integration/registration/offline/response"
    $method = "POST"

    $headers = @{
        "Authorization" = $global:vcfOpsConnection.headers.authorization
        "X-Ops-API-use-unsupported" = "true"
        "Accept" = "application/json, text/plain, */*"
    }

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
    }

    try {
        Write-Host "Importing License File ($LicenseFile) to VCF Operations ...`n"
        if($Troubleshoot) {
            $results = Invoke-MultipartUpload -Uri $uri -Headers $headers -FilePath $LicenseFile -SkipCertCheck -Troubleshoot $true
        } else {
            $results = Invoke-MultipartUpload -Uri $uri -Headers $headers -FilePath $LicenseFile -SkipCertCheck
        }
    } catch {
        Write-Error "Error in importing license file"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }
}

Function Download-VcfOperationsUsageFile {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Download usage file from VCF Operations
        .DESCRIPTION
            This cmdlet downloads usage file from VCF Operations

        .EXAMPLE
            Download-VcfOperationsUsageFile
    #>

    Param (
        [Switch]$Troubleshoot
    )

    # Get the current date/time as a DateTimeOffset object
    $NowOffset = [DateTimeOffset]::Now

    # STARTDATE: Tomorrow's date in Unix milliseconds
    $StartTimeMilliseconds = $NowOffset.AddDays(1).ToUnixTimeMilliseconds()

    # ENDDATE: One month from now in Unix milliseconds
    $EndTimeMilliseconds = $NowOffset.AddMonths(1).ToUnixTimeMilliseconds()

    $uri = "https://$($global:vcfOpsConnection.server)/suite-api/internal/extension/vcf-license-cloud-integration/usage/offline/report?startDate=${StartTimeMilliseconds}&endDate=${EndTimeMilliseconds}"
    $method = "POST"

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
    }

    try {
        Write-Host "Downloading Usage File ..."
        $requests = Invoke-WebRequest -Method $method -Uri $uri -Headers $global:vcfOpsConnection.headers -SkipCertificateCheck
    } catch {
        Write-Error "Error in downloading usage file"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }

    if($requests.StatusCode -eq 200) {
        $result = ($requests.Content | ConvertFrom-Json)

        $UsageFile = $result.fileName.Replace(" ", "")
        $UsageData = $result.gzipJwsEncodedData

        Write-Host "Successfully downloaded usage file $UsageFile`n"
        $ContentBytes = [System.Convert]::FromBase64String($UsageData)
        [System.IO.File]::WriteAllBytes($UsageFile, $ContentBytes)
    }
}

Function Import-VcfOperationsUsageFile  {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Import generated usage file from VCF Operations into VCF Business Service Console
        .DESCRIPTION
            This cmdlet imports generated usage file from VCF Operations into VCF Business Service Console
        .PARAMETER TenantId
            The BSC Tenant ID (retrieved through VCF BSC UI)
        .PARAMETER UsageFile
            The name of the usage file to import

        .EXAMPLE
            $VCF_OPERATIONS_USAGE_FILE="Usage-vcf01.vcf.lab-2025-12-16T21_09_14Z.gzip"

            Import-VcfOperationsUsageFile -TenantId $VCF_BSC_TENANT_ID -UsageFile $VCF_OPERATIONS_USAGE_FILE
    #>

    Param (
        [Parameter(Mandatory=$true)][String]$TenantId,
        [Parameter(Mandatory=$true)][String]$UsageFile,
        [Switch]$Troubleshoot
    )

    $uri = "https://eapi.broadcom.com/vcf/license-usage/api/v1/tenants/${TenantId}/license-usage/upload"
    $method = "POST"

    $headers = @{
        "Authorization" = $global:bscConnection.Headers.Authorization
    }

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
    }

    try {
        Write-Host "Importing Usage File ($UsageFile) to VCF Operations ...`n"
        if($Troubleshoot) {
            $results = Invoke-MultipartUpload -Uri $uri -Headers $headers -FilePath $UsageFile -Troubleshoot $true
        } else {
            $results = Invoke-MultipartUpload -Uri $uri -Headers $headers -FilePath $UsageFile
        }
    } catch {
        Write-Error "Error in importing usage file"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }
}

Function Get-VcfOperationsEntitlements {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            List license entitlements that have been imported into VCF Operations
        .DESCRIPTION
            This cmdlet lists license entitlements that have been imported into VCF Operations

        .EXAMPLE
            Get-VcfOperationsEntitlements
    #>

    Param (
        [Switch]$Troubleshoot
    )

    $uri = "https://$($global:vcfOpsConnection.server)/suite-api/internal/extension/vcf-entitlement/entitlements/query"
    $method = "POST"

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
    }

    try {
        $requests = Invoke-WebRequest -Uri $uri -Method $method -Headers $global:vcfOpsConnection.headers -SkipCertificateCheck
    } catch {
        Write-Error "Error in retrieving license entitlements"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }

    if($requests.StatusCode -eq 200) {
        $entitlements = (($requests.Content | ConvertFrom-Json).entitlementWithVcentersDetails).entitlementInfo

        $results = @()
        foreach($entitlement in $entitlements) {
            $tmp = [pscustomobject] [ordered]@{
                Name = $entitlement.name
                Id = $entitlement.id
                Product = $entitlement.productDisplayName
                Type = $entitlement.type
                UsedCapacity = $entitlement.usage
                AllocatedCapacity = $entitlement.capacity
            }
            $results+=$tmp
        }
    }
    return $results | Sort-Object -Property Name | ft
}

Function Get-VcfOperationsVcenters {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            List vCenter Servers managed by VCF Operations
        .DESCRIPTION
            This cmdlet lists vCenter Servers managed by VCF Operations

        .EXAMPLE
            Get-VcfOperationsVcenters
    #>

    Param (
        [Switch]$Troubleshoot
    )

    $uri = "https://$($global:vcfOpsConnection.server)/suite-api/internal/extension/vcf-entitlement/vcenter-systems/query?page=0&pageSize=10"
    $method = "POST"

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
    }

    try {
        $requests = Invoke-WebRequest -Uri $uri -Method $method -Headers $global:vcfOpsConnection.headers -SkipCertificateCheck
    } catch {
        Write-Error "Error in retrieving license entitlements"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }

    if($requests.StatusCode -eq 200) {
        $vcenters = ($requests.Content | ConvertFrom-Json).vcenterSystems

        $results = @()
        foreach($vcenter in $vcenters) {
            $tmp = [pscustomobject] [ordered]@{
                "vCenter" = $vcenter.vcenterInfo.host
                "Id" = $vcenter.vcenterInfo.id
                "ManagedByVCFInstance" = $vcenter.vcenterInfo.vcfAdapterName
                "PrimaryLicenseName" = $vcenter.entitlementName
                "PrimaryLicenseProduct" = $vcenter.entitlementProduct
                "PrimaryLicenseUsedCapacity" = $vcenter.licensedUsage
                "AddOnLicenseName" = $vcenter.addOns
                "FullyLicensed" = $vcenter.unlicensedUsage -eq 0 ? $true : $false
            }
            $results+=$tmp
        }
    }
    return $results | Sort-Object -Property Name
}

Function Set-VcfOperationsLicenseAssignment {
    <#
        .NOTES
        ===========================================================================
        Created by: William Lam
        Organization: Broadcom
        Blog: http://www.williamlam.com
        Twitter: @lamw
        ===========================================================================
        .SYNOPSIS
            Assign license entitlement(s) to a vCenter Server managed by VCF Operations
        .DESCRIPTION
            This cmdlet assigns license entitlement(s) to a vCenter Server managed by VCF Operations
        .PARAMETER VcenterId
            vCenter Server ID (returned from Get-VcfOperationsVcenters)
        .PARAMETER VcfLicenseIds

        .EXAMPLE
            $VCENTER_ID="df688ce4-66d6-4adf-b33b-8926bad4be48"
            $VCF_LICENSE_IDS=("efaa38b7-08ac-452f-929b-d30f6d37fba5","fc711690-f8d3-4209-a06e-529c39979251")

            Set-VcfOperationsLicenseAssignment -VcenterId $VCENTER_ID -VcfLicenseIds $VCF_LICENSE_IDS
    #>

    Param (
        [Parameter(Mandatory=$true)][String]$VcenterId,
        [Parameter(Mandatory=$true)][String[]]$VcfLicenseIds,
        [Switch]$Troubleshoot
    )

    $uri = "https://$($global:vcfOpsConnection.server)/suite-api/internal/extension/vcf-entitlement/assign"
    $method = "POST"

    $vc = Get-VcfOperationsVcenters | where {$_.id -eq $VcenterId}

    $payload = [ordered]@{
        vcenter = [ordered]@{
            id = $VcenterId
            adapterName = $vc.vCenter
            host = $vc.vCenter
        }
        allocationIds = $VcfLicenseIds
    }

    $body = ConvertTo-Json -InputObject @($payload)

    if($Troubleshoot) {
        Write-Host -ForegroundColor cyan "`n[DEBUG] - $method`n$uri`n"
        Write-Host -ForegroundColor cyan "[DEBUG]`n$($body | Out-String)`n"
    }

    try {
        $requests = Invoke-WebRequest -Uri $uri -Method $method -Headers $global:vcfOpsConnection.headers -Body $body -SkipCertificateCheck
    } catch {
        Write-Error "Error in assigning license entitlements"
        Write-Error "`n($_.Exception.Message)`n"
        break
    }

    if($requests.StatusCode -eq 200) {
        ($requests.Content | ConvertFrom-Json)

    }
}