Modules/businessdev.ALbuild.Core/Public/Test-ALbuildLicense.ps1

function Test-ALbuildLicense {
    <#
    .SYNOPSIS
        Verifies an ALbuild commercial license against the licensing service.
 
    .DESCRIPTION
        Free-tier functionality performs no license check. Licensed features call this to verify
        a tenant's license via the 365 business development licensing service. The result is
        cached per tenant for the session. Network/service failures do NOT throw - they return an
        object with IsValid = $false and a Reason - so that license enforcement is an explicit
        decision of the caller (see Assert-ALbuildLicensed), never an accidental side effect.
 
    .PARAMETER TenantId
        The Azure DevOps organization / collection id. Defaults to $env:System_CollectionId.
 
    .PARAMETER TenantName
        The Azure DevOps organization / collection URI. Defaults to $env:System_CollectionUri.
 
    .PARAMETER Refresh
        Bypass the per-tenant session cache and re-query the service.
 
    .EXAMPLE
        Test-ALbuildLicense -TenantId $env:System_CollectionId
 
    .OUTPUTS
        PSCustomObject with IsValid, Status, IsTrial, ExpiresOn, TenantId, TenantName, Reason.
    #>

    [CmdletBinding()]
    param(
        [string] $TenantId = $env:System_CollectionId,
        [string] $TenantName = $env:System_CollectionUri,
        [switch] $Refresh
    )

    if ([string]::IsNullOrWhiteSpace($TenantId)) {
        return [PSCustomObject]@{
            IsValid = $false; Status = 'NoTenant'; IsTrial = $false; ExpiresOn = $null
            TenantId = $TenantId; TenantName = $TenantName
            Reason = 'No tenant id available (set -TenantId or run inside Azure DevOps).'
        }
    }

    $normalizedName = if ($TenantName) {
        $TenantName -replace '^https?://', '' -replace '/+$', ''
    } else { '' }

    if (-not $script:ALbuildLicenseCache) { $script:ALbuildLicenseCache = @{} }
    if (-not $Refresh -and $script:ALbuildLicenseCache.ContainsKey($TenantId)) {
        return $script:ALbuildLicenseCache[$TenantId]
    }

    $baseUrl = (Get-ALbuildConfig -Name 'LicensingBaseUrl').TrimEnd('/')
    $appId   = Get-ALbuildConfig -Name 'LicenseAppId'
    $url     = "$baseUrl/v1/apps/$appId/features/$appId/tenant/$TenantId/verifyLicense"

    try {
        $response = Invoke-RestMethod -Uri $url -Method Get -TimeoutSec 30 -ErrorAction Stop
        $license  = $response.license
        $status   = [string]$response.status
        $isOk     = $status -and ($status.ToLowerInvariant() -eq 'ok')

        $isTrial  = $isOk -and ($null -ne $license) -and [string]::IsNullOrEmpty([string]$license.licenseKey)
        $expires  = $null
        if ($isOk -and $license -and $license.PSObject.Properties.Name -contains 'trialPeriodEndingDate' -and $license.trialPeriodEndingDate) {
            [datetime] $parsed = [datetime]::MinValue
            if ([datetime]::TryParse([string]$license.trialPeriodEndingDate, [ref] $parsed)) { $expires = $parsed }
        }

        $result = [PSCustomObject]@{
            IsValid    = [bool]$isOk
            Status     = if ($status) { $status } else { 'Unknown' }
            IsTrial    = [bool]$isTrial
            ExpiresOn  = $expires
            TenantId   = $TenantId
            TenantName = $normalizedName
            Reason     = if ($isOk) { '' } else { "Licensing service returned status '$status'." }
        }
    }
    catch {
        $result = [PSCustomObject]@{
            IsValid = $false; Status = 'Error'; IsTrial = $false; ExpiresOn = $null
            TenantId = $TenantId; TenantName = $normalizedName
            Reason = "Could not reach the licensing service: $($_.Exception.Message)"
        }
    }

    $script:ALbuildLicenseCache[$TenantId] = $result
    return $result
}