Functions/LicenseManagement/Get-BcLicense.ps1

<#
    .Synopsis
    Returns the key-value pairs from a Business Central license.
 
    .Description
    By default, the function retrieves the license from the ServerInstance, but the license can also be provided as a string or a file path.
    The function then extracts the license information as key-value pairs and returns the information as a PowerShell object.
    The function contains various validations to ensure that the license can be retrieved correctly.
    If the license cannot be retrieved for any reason, a warning message is displayed.
 
    .Example
    Get-BcLicense -ServerInstance BC210
 
    <# Result:
        VOICEAccountNumber : 0000000
        CreatedDate : 1-10-2023 05:23:10
        Licensedto : MyCompany
        ProductVersion : 21
        DevelopmentLicense : True
        Expires : 4-10-2023 00:00:00
    #>

#>

function Get-BcLicense {
    [Alias('Get-LicenseDetails', 'Export-BcLicense')]
    [OutputType([psobject])]
    [CmdletBinding(DefaultParameterSetName="ServerInstance", SupportsShouldProcess)]
    param(
        # The name of the BC ServerInstance from which to export the BC License file.
        [Parameter(
            Mandatory=$true,
            Position = 0,
            ValueFromPipeline=$true, 
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "ServerInstance")]
        [string] $ServerInstance,

        # The computer where the ServerInstance is running. Default is the local system.
        [Parameter(
            Position = 1,
            ValueFromPipeline=$true, 
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "ServerInstance")]
        [string] $Computer = $env:COMPUTERNAME,

        # Specifies the ID of the tenant that the license is stored in.
        # This parameter is required unless the specified service instance is not configured to run multiple tenants.
        [Parameter(
            Position = 2,
            ValueFromPipeline=$true, 
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "ServerInstance")]
        [string] $Tenant = 'default',

        # The path to the BC license. The license details will be read from the specified file.
        [Parameter(
            Mandatory=$true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "LicencePath")]
        [string] $LicensePath,

        # A string array that contains the content of the BC license.
        [Parameter(
            Mandatory=$true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "LicenceString")]
        [Alias('RawLicense')]
        [string[]] $LicenseString,

        # Writes a warning to host if the license is older than the set age, default 60 days.
        [switch] $ValidateLicenseAge,

        # When the ValidateLicenseAge switch is set the license age is validated against RecommendedMaxDaysOld.
        [int] $RecommendedMaxDaysOld = 60

    )

    # Get the license as a string array.
    switch($PSCmdlet.ParameterSetName)
    {
        'LicenceString' {
            [string[]] $licenseRaw = $LicenseString
        }

        'LicencePath' {
            # Validations
            if(!(Get-Item -Path $LicensePath).Extension -in @('.flf', '.bclicense')){
                'Supplied file is not a Business Central license. Expected a file with the extension .flf or .bclicense.'| Write-Warning
                return
            }
            if(!(Test-Path $LicensePath)){
                'Could not find the Business Central license file on path: {0}.' -f $LicensePath | Write-Warning
                return
            }

            # Read file
            [string[]] $LicenseRaw = Get-Content -Path $LicensePath -raw
        }

        'ServerInstance' {
            # Get the ServerInstance object.
            $serverInstanceObj = Get-BCServerInstance $ServerInstance -Computer $Computer
            if(!$serverInstanceObj){
                'Could not find the ServerInstance {0}.' -f $ServerInstance | Write-Warning
                return
            }

            # Validations
            if($serverInstanceObj.state -ne 'running'){
                'Cannot export license from ServerInstance {0} because the service is not started.' -f $ServerInstance | Write-Warning
                return
            }

            # Scriptblock is used to load BC modules into a separate PS session and to add compatibility for Computer param.
            [scriptblock] $scriptBlock = {
                param(
                    [string[]] $BcModulesPath,  
                    [string] $ServerInstance,
                    [string] $Tenant
                )
                $BcModulesPath | Import-Module -Force
                Export-NAVServerLicenseInformation -ServerInstance $ServerInstance -Tenant $Tenant -WhatIf:$false -Confirm:$false
            }

            $additionParams = @{}
            if(-not $serverInstanceObj._isLocalService){
                $additionParams = @{'ComputerName' = $serverInstanceObj.ComputerName}
            }

            # Execute scriptblock to export BC license from ServerInstance
            try{
                [string[]] $licenseRaw = Invoke-Command @additionParams -ScriptBlock $scriptBlock -ErrorAction Stop -ArgumentList @(
                                            $serverInstanceObj.GetBcPsModules(), 
                                            $serverInstanceObj.ServerInstance, 
                                            $Tenant)
            } catch{
                Write-Error $_
            }
        }
    }
    if(!$licenseRaw){
        'Could not export the license file from ServerInstance {0}' -f $ServerInstance | Write-Warning
        return
    }
    
    # Extract key-value pairs from the string array.
    $regex = '(?<key>\w+(?:\s\w+){0,3})\s*:\s(?<value>.*)\r\n'  
    $result = $licenseRaw | Select-String -Pattern $regex -AllMatches
    
    $licenseProperties = @{}

    foreach ($match in $result.Matches){
        # Skip Country Code property
        if($match.Groups['key'].Value -eq 'Country Code'){
            continue
        }
        # Add key-value pair to hashtable
        $licenseProperties += @{
            $match.Groups['key'].Value.Replace(' ', '') = 
            $match.Groups['value'].Value
        }
    }

    # Add additional properties
    $licenseProperties += @{
        'DevelopmentLicense' = $(
            if($licenseProperties.Expires){$true}
            else{$false})
    }

    # Validate if required keys are extracted
    $requiredKeys = @('VOICEAccountNumber', 'CreatedDate', 'Licensedto', 'ProductVersion', 'DevelopmentLicense')
    $requiredKeys | ForEach-Object {
        if(!$licenseProperties.ContainsKey($_)){
            $message = 'Could not extract key {0} from the Business Central license file.' -f $_
            Write-Warning $message
            $return = $true
            break
        }
    }
    if($return){return}

    # Parse the date values into DateTime
    $dateElements = $licenseProperties.CreatedDate.Split(' ')
    $createDate = '{0} {1}' -f $dateElements[0], $dateElements[1]
    $dateFormat = 'M/d/yyyy H:mm:ss'
    $licenseProperties.CreatedDate = [datetime]::ParseExact($createDate, $dateFormat, $null)
    
    if($licenseProperties.DevelopmentLicense){
        $dateFormat = 'M/d/yyyy'
        $licenseProperties.Expires = [datetime]::ParseExact($licenseProperties.Expires, $dateFormat, $null)
    }

    # Write warning when the license is more than 60 days old.
    if($ValidateLicenseAge){
        $elapsedTime = ((Get-Date) - $licenseProperties.CreatedDate)
        if($elapsedTime.Days -gt $RecommendedMaxDaysOld){
            $message = 'The supplied license was created {0} days ago. It is recommended to update the license at least once every {1} days.' -f 
                            $elapsedTime.Days, $RecommendedMaxDaysOld
            Write-Warning $message
        }
    }
    
    # Convert hashtable to PSObject
    $license = New-Object PSObject -Property $licenseProperties | Sort-Object -Property $returnKeys

    # Create a string array with properties that will be visible by default.
    [string[]] $returnKeys = $requiredKeys
    if($licenseProperties.DevelopmentLicense){
        $returnKeys += 'Expires'
    }
    # Add list of properties that will be visible by default. Properties not in the list will be hidden by default.
    [Management.Automation.PSMemberInfo[]] $PSStandardMembers = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',$returnKeys)
    $license | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers

    return $license
}

Export-ModuleMember -Function Get-BcLicense -Alias @('Get-LicenseDetails', 'Export-BcLicense')