Get-O365SKUsInfos.psm1

#
## Created by: lucas.cueff[at]lucas-cueff.com
#
## released on 01/2021
#
# v1.0.0 : first public release - beta version
# v1.1.0 : last public release - beta version - fix IE com object (windows pwsh crash with invoke-webrequest)
#
#'(c) 2020-2021 lucas-cueff.com - Distributed under Artistic Licence 2.0 (https://opensource.org/licenses/artistic-license-2.0).'
<#
    .SYNOPSIS
    Get last Microsoft Office 365 SKU / Service plans info (GUID, String ID, Product Name).
 
    .DESCRIPTION
    Get last Microsoft Office 365 SKU / Service plans info (GUID, String ID, Product Name).
    Resolve SKU and Service Plans from GUID, String ID, Name.
    Get all SKUs that include a Service Plan.
    Cache last catalog from Microsoft locally.
     
    .EXAMPLE
    Get-O365SKUCatalog
    Get last Microsoft Office 365 SKU / Service Plans catalog from Microsoft website and return an array of custom psobjects
 
    .EXAMPLE
    Get-O365SKUinfo
    Get SKU info using a GUID, String ID or Product Name
 
    .EXAMPLE
    Get-O365SKUInfoFromPlan
    Get all SKU and related info including a Service Plan (using GUID, String ID or Plan Name)
 
    .EXAMPLE
    Get-O365Planinfo
    Get Service Plan info using a GUID, String ID or Plan Name
#>

function Convert-HTMLTableToArray {
    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true)]
            $HTMLObject,
        [Parameter(Mandatory = $true)]
            [int]$TableNumber
    )
    process {
        $tables = @($HTMLObject.getElementsByTagName("TABLE"))
        $table = $tables[$TableNumber]
        $titles = @()
        $rows = @($table.Rows)
        foreach($row in $rows) {
            $cells = @($row.Cells)
            if($cells[0].tagName -eq "TH"){
                $titles = @($cells | foreach-object { ("" + $_.InnerText).Trim() })
                continue
            }
            if (!($titles)) {
                $titles = @(1..($cells.Count + 2) | foreach-object { "P$_" })
            }
            $resultObject = [Ordered] @{}
            for($counter = 0; $counter -lt $cells.Count; $counter++) {
                $title = $titles[$counter]
                if(!($title)) { continue }
                $resultObject[$title] = ("" + $cells[$counter].InnerText).Trim()
            }
            [PSCustomObject]$resultObject
        }
    }
}
function Get-O365SKUCatalog {
<#
    .SYNOPSIS
    Get last Microsoft Office 365 SKU / Service Plans information from Microsoft Website
 
    .DESCRIPTION
    Get last Microsoft Office 365 SKU / Service Plans information from Microsoft Website
    Downloaded from https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference
     
    .PARAMETER ServicePlansInfoAsStrings
    -ServicePlansInfoAsStrings [switch]
    add a new property 'Service plans included as strings' so the object could be easily exported to an external file like a CSV file.
 
    .PARAMETER AsGlobalVariable
    -AsGlobalVariable [switch]
    save objets into global variable O365SKUsInfos
         
    .OUTPUTS
       TypeName : pscustomobject
         
    .EXAMPLE
    Get-O365SKUCatalog
    Get all MS O365 SKUs / Service Plans info
     
    .EXAMPLE
    Get-O365SKUCatalog -AsGlobalVariable
    Get all MS O365 SKUs / Service Plans info and save psobjets to $global:O365SKUsInfos
#>

    [cmdletbinding()]
    param(
        [parameter(Mandatory=$false)]
            [switch]$ServicePlansInfoAsStrings,
        [parameter(Mandatory=$false)]
            [switch]$AsGlobalVariable
    )
    process {
        #$script:URICatalog = "https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference"
        $script:URICatalog = "https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/active-directory/enterprise-users/licensing-service-plan-reference.md"
        $script:tempfile = [System.IO.Path]::GetTempFileName()
        write-verbose "Microsoft O365 SKU catalog URL : $($script:URICatalog)"
        write-verbose "Temporary html file : $($script:tempfile)"
        try {
            $request = Invoke-WebRequest -Uri $script:URICatalog -OutFile $script:tempfile -UseBasicParsing
        } catch {
            throw "Microsoft O365 SKU online catalog $($script:URICatalog) is not available. Please check your network / internet connexion."
        }
        if (!(test-path $script:tempfile)) {
            throw "not able to dowload licensing-service-plan-reference HTML content to $($script:tempfile)"
        } else {
            write-verbose "Temporary html file $($script:tempfile) created successfully"
        }
        $htmlcontent = get-content -Raw -Path $script:tempfile
        try {
            $htmlobj = New-Object -ComObject "HTMLFile"
        } catch {
            throw "not able to create HTMLFile com object"
        }
        try {
            if ($host.Version.Major -gt 5) {
                $encodedhtmlcontent = [System.Text.Encoding]::Unicode.GetBytes($htmlcontent)
                $htmlobj.write($encodedhtmlcontent)
            } else {
                $htmlobj.IHTMLDocument2_write($htmlcontent)
            }
        }
        catch {
            throw "not able to create Com HTML object from temporary file $($script:tempfile)"
        }
        if ($htmlobj) {
            $skuinfo = Convert-HTMLTableToArray -HTMLObject $htmlobj -TableNumber 1
        }
        $skuinfo
        foreach ($sku in $skuinfo) {
            if ($sku.'Service plans included') {
                $tmpserviceplan = $sku.'Service plans included'.split("`n")
                $tmpserviceplanname = $sku.'Service plans included (friendly names)'.split("`n")
                $resultserviceplan = @()
                for ($i=0;$i -le ($tmpserviceplan.count -1);$i++) {
                    $tmpstringid = ($tmpserviceplan[$i]).substring(0,$tmpserviceplan[$i].length - 39)
                    $tmpguid = ($tmpserviceplan[$i]).substring($tmpstringid.length,$tmpserviceplan[$i].length - $tmpstringid.length)
                    $tmpplanname = ($tmpserviceplanname[$i]).substring(0,$tmpserviceplanname[$i].length - 39)
                    $resultserviceplan += [PSCustomObject]@{
                        'String ID' = $tmpstringid.replace(" ","")
                        'GUID' = ((($tmpguid.replace("(","")).replace(")","")).replace(" ","")).replace("`r","")
                        'Plan Name' = if (($tmpplanname.substring($tmpplanname.length - 1,1)) -eq " ") {
                            $tmpplanname.substring(0, $tmpplanname.length - 1)
                        } else {
                            $tmpplanname
                        }
                    }
                }
                if ($ServicePlansInfoAsStrings.IsPresent) {
                    $sku | Add-Member -NotePropertyName "Service plans included as strings" -NotePropertyValue $sku.'Service plans included'
                }
                $sku.'Service plans included' = $resultserviceplan
                $sku.PSObject.Properties.Remove('Service plans included (friendly names)')
            }
        }
        if ($AsGlobalVariable.IsPresent) {
            $global:O365SKUsInfos = $skuinfo
            write-verbose -message "Global Variable O365SKUsInfos set with SKUs Infos"
        }
        return $skuinfo
    } 
}
function Get-O365SKUinfo {
<#
    .SYNOPSIS
    Get last Microsoft Office 365 SKU information from Microsoft Website.
 
    .DESCRIPTION
    Get last Microsoft Office 365 SKU information from Microsoft Website.
    Downloaded from https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference
    You can search the SKU info based on its GUID, String ID or Product Name
     
    .PARAMETER GUID
    -GUID [GUID]
    search a SKU using its GUID
 
    .PARAMETER StringID
    -StringID [string]
    search a SKU using its StringID
 
    .PARAMETER ProductName
    -ProductName [string]
    search a SKU using its Product Name
         
    .OUTPUTS
       TypeName : pscustomobject
         
    .EXAMPLE
    Get-O365SKUinfo -GUID 8f0c5670-4e56-4892-b06d-91c085d7004f
    Get SKU info based on GUID 8f0c5670-4e56-4892-b06d-91c085d7004f
     
    .EXAMPLE
    Get-O365SKUinfo -ProductName "Microsoft 365 F1"
    Get SKU info of "Microsoft 365 F1"
#>

    [cmdletbinding()]
    param(
        [parameter(Mandatory=$false)]
            [guid]$GUID,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$StringID,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$ProductName
    )
    process {
        if (!($global:O365SKUsInfos)) {
            Get-O365SKUCatalog -AsGlobalVariable | out-null
        }
        if ($GUID) {
            $global:O365SKUsInfos | Where-Object {$_.GUID -eq $GUID}
        } elseif ($StringID) {
            $global:O365SKUsInfos | Where-Object {$_.'String ID' -eq $StringID}
        } elseif ($ProductName) {
            $global:O365SKUsInfos | Where-Object {$_.'Product Name' -eq $ProductName}
        } else {
            throw "please use GUID or StringID or ProductName parameters"
        }
    }
}
function Get-O365SKUInfoFromPlan {
<#
    .SYNOPSIS
    Get last Microsoft Office 365 SKU information that included a specific Service Plan.
 
    .DESCRIPTION
    Get last Microsoft Office 365 SKU information that included a specific Service Plan.
    Downloaded from https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference
    You can search the Service Plan info based on its GUID, String ID or Plan Name
     
    .PARAMETER GUID
    -GUID [GUID]
    search a SP using its GUID
 
    .PARAMETER StringID
    -StringID [string]
    search a SP using its StringID
 
    .PARAMETER PlanName
    -PlanName [string]
    search a SP using its Plan Name
         
    .OUTPUTS
       TypeName : pscustomobject
         
    .EXAMPLE
    Get-O365SKUInfoFromPlan -GUID 41781fb2-bc02-4b7c-bd55-b576c07bb09d
    Get all SKU including Service Plan GUID 41781fb2-bc02-4b7c-bd55-b576c07bb09d
     
    .EXAMPLE
    Get-O365SKUInfoFromPlan -PlanName "AZURE ACTIVE DIRECTORY PREMIUM P1"
    Get all SKU including Service Plan "AZURE ACTIVE DIRECTORY PREMIUM P1"
#>

    [cmdletbinding()]
    param(
        [parameter(Mandatory=$false)]
            [guid]$GUID,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$StringID,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$PlanName
    )
    process {
        if (!($global:O365SKUsInfos)) {
            Get-O365SKUCatalog -AsGlobalVariable | out-null
        }
        if ($GUID) {
            $global:O365SKUsInfos | Where-Object {$_.'Service plans included'.GUID -contains $GUID}
        } elseif ($StringID) {
            $global:O365SKUsInfos | Where-Object {$_.'Service plans included'.'String ID' -contains $StringID}
        } elseif ($PlanName) {
            $global:O365SKUsInfos | Where-Object {$_.'Service plans included'.'Plan Name' -contains $PlanName}
        } else {
            throw "please use GUID or StringID or PlanName parameters"
        }
    }
}
function Get-O365Planinfo {
<#
    .SYNOPSIS
    Get last Microsoft Office 365 Service Plan information from Microsoft Website.
 
    .DESCRIPTION
    Get last Microsoft Office 365 Service Plan information from Microsoft Website.
    Downloaded from https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference
    You can search the Service Plan info based on its GUID, String ID or Plan Name
     
    .PARAMETER GUID
    -GUID [GUID]
    search a SP using its GUID
 
    .PARAMETER StringID
    -StringID [string]
    search a SP using its StringID
 
    .PARAMETER PlanName
    -PlanName [string]
    search a SP using its Plan Name
         
    .OUTPUTS
       TypeName : pscustomobject
         
    .EXAMPLE
    Get-O365Planinfo -GUID 41781fb2-bc02-4b7c-bd55-b576c07bb09d
    Get Service Plan info based on GUID 41781fb2-bc02-4b7c-bd55-b576c07bb09d
     
    .EXAMPLE
    Get-O365Planinfo -PlanName "AZURE ACTIVE DIRECTORY PREMIUM P1"
    Get Service Plan info of "AZURE ACTIVE DIRECTORY PREMIUM P1"
#>

    [cmdletbinding()]
    param(
        [parameter(Mandatory=$false)]
            [guid]$GUID,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$StringID,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$PlanName
    )
    process {
        if (!($global:O365SKUsInfos)) {
            Get-O365SKUCatalog -AsGlobalVariable | out-null
        }
        if ($GUID) {
            ($global:O365SKUsInfos | Where-Object {$_.'Service plans included'.GUID -contains $GUID})[0].'Service plans included' | Where-Object {$_.GUID -eq $GUID}
        } elseif ($StringID) {
            ($global:O365SKUsInfos | Where-Object {$_.'Service plans included'.'String ID' -contains $StringID})[0].'Service plans included' | Where-Object {$_.'String ID' -eq $StringID}
        } elseif ($PlanName) {
            ($global:O365SKUsInfos | Where-Object {$_.'Service plans included'.'Plan Name' -contains $PlanName})[0].'Service plans included' | Where-Object {$_.'Plan Name' -eq $PlanName}
        } else {
            throw "please use GUID or StringID or PlanName parameters"
        }
    }
}

Export-ModuleMember -Function Get-O365SKUCatalog, Get-O365SKUinfo, Get-O365SKUInfoFromPlan, Get-O365Planinfo