source/private/GetM365ProductIdTable.ps1

function GetM365ProductIdTable {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        ## This is URL path to the the licensing reference table document from GitHub.
        ## The current working URL is the default value.
        ## In case Microsoft moved the document, use this parameter to point to the new URL.
        [parameter()]
        [string]
        $URL = 'https://raw.githubusercontent.com/MicrosoftDocs/entra-docs/main/docs/identity/users/licensing-service-plan-reference.md',

        # Return only the matching SkuId
        [Parameter(ParameterSetName = 'SkuId', Mandatory)]
        [ValidateNotNullOrEmpty()]
        [guid[]]
        $SkuId,

        # Return only the matching SkuPartNumber
        [Parameter(ParameterSetName = 'SkuPartNumber', Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $SkuPartNumber,

        ## Force convert license names to title case.
        [parameter()]
        [switch]
        $TitleCase,

        ## Force to download the online version instead of checking table in the current session
        [parameter()]
        [switch]
        $ForceOnline,

        ## Specifiy the list delimiter for ChildServicePlan and ChildServicePlanName.
        ## Default character delimited is comma ","
        [parameter()]
        [string]
        $ListDelimiterCharacter
    )

    function ShowResult {
        $visible_properties = [string[]]@('SkuName', 'SkuPartNumber', 'SkuId')
        [Management.Automation.PSMemberInfo[]]$default_properties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet', $visible_properties )
        $Global:SkuTable | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $default_properties -Force

        switch ($PSCmdlet.ParameterSetName) {
            # -SkuId <GUID>
            SkuId {
                Write-Verbose "Filtering by SkuId"
                foreach ($id in $SkuId) {
                    $Global:SkuTable | Where-Object { $_.SkuId -eq $id }
                }
            }
            # -SkuPartNumber <SKU PART NUMBER>
            SkuPartNumber {
                Write-Verbose "Filtering by SkuPartNumber"
                foreach ($partNumber in $SkuPartNumber) {
                    $Global:SkuTable | Where-Object { $_.SkuPartNumber -eq $partNumber }
                }
            }
            default {
                Write-Verbose "No filtering. Showing all results."
                $Global:SkuTable
            }
        }
    }

    $ErrorActionPreference = 'STOP'

    if ($ForceOnline) { $Global:SkuTable = @() }

    #https://learn.microsoft.com/en-us/entra/identity/users/licensing-service-plan-reference

    # Check first if the SKU table is already available in the session. This ensures that the script only downloads the online table once per session, unless the -ForceOnline switch is used.
    if ($Global:SkuTable) {
        Write-Verbose "SKU table exists in session."
        return ShowResult
    }

    # Continue if the SKU table is not yet in the session
    Write-Verbose "Downloading SKU table online..."

    ## Parse the Markdown Table from the $URL
    try {
        $raw_Table = Invoke-RestMethod -Uri $URL -ErrorAction Stop
        $raw_Table = $raw_Table -split "`n"
    }
    catch {
        Write-Output "There was an error getting the licensing reference table at [$URL]. Please make sure that the URL is still valid."
        Write-Output $_.Exception.Message
        return $null
    }

    ## Determine the starting row index of the table
    $startLine = ($raw_Table.IndexOf('| Product name | String ID | GUID | Service plans included | Service plans included (friendly names) |') + 1)

    ## Determine the ending index of the table
    $endLine = ($raw_Table.IndexOf('## Service plans that cannot be assigned at the same time') - 1)

    ## Extract the string in between the lines $startLine and $endLine
    $result = for ($i = $startLine; $i -lt $endLine; $i++) {
        if ($raw_Table[$i] -notlike "*---*") {
            $raw_Table[$i].Substring(1, $raw_Table[$i].Length - 1)
        }
    }

    ## Perform a little clean-up
    ## replace "[space] | [space]" with "|"
    ## replace "[space]<br/>[space]" with ","
    ## replace "((" with "("
    ## replace "))" with ")"
    ## #replace ")[space](" with ")("

    $result = $result `
        -replace '\s*\|\s*', '|' `
        -replace '\s*<br/>\s*', ',' `
        -replace '\(\(', '(' `
        -replace '\)\)', ')' `
        -replace '\)\s*\(', ')('

    # Force title case conversion if -TitleCase is not used.
    if (-not $PSBoundParameters.ContainsKey('TitleCase')) {
        $TitleCase = $true
        Write-Verbose "TitleCase name conversion enabled"
    }

    ## Create the result object
    $TextInfo = (Get-Culture).TextInfo

    # Set "," (comma) as the default delimiter character for ChildServicePlan and ChildServicePlanName
    if (-not $ListDelimiterCharacter) { $ListDelimiterCharacter = "," }
    $Global:SkuTable = @($result | ConvertFrom-Csv -Delimiter "|" -Header 'SkuName', 'SkuPartNumber', 'SkuID', 'ChildServicePlan', 'ChildServicePlanName' | ForEach-Object {


            if ($ListDelimiterCharacter -ne ",") {
                $childServicePlan = (([string]$_.ChildServicePlan).Split(",") | Sort-Object) -join $ListDelimiterCharacter
                $childServicePlanName = (([string]$_.ChildServicePlanName).Split(",") | Sort-Object) -join $ListDelimiterCharacter
            }
            else {
                $childServicePlan = $_.ChildServicePlan
                $childServicePlanName = $_.ChildServicePlanName
            }

            [pscustomobject]@{
                SkuName              = if ($TitleCase) { $TextInfo.ToTitleCase($_.SkuName) } else { $_.SkuName }
                SkuPartNumber        = $_.SkuPartNumber
                SkuId                = [guid]$_.SkuId
                ChildServicePlan     = $childServicePlan
                ChildServicePlanName = if ($TitleCase) { $TextInfo.ToTitleCase($childServicePlanName) } else { $childServicePlanName }
            }
        })

    ## return the result
    return ShowResult
}