SHIMSOFT-MS365info.psm1

function Get-MS365Info {
    <#
    .SYNOPSIS
    「ライセンスのための製品名とサービス プラン 識別子」サイトの情報をもとに、
    Microsoft 365 サービスの一覧情報を取得します。
    https://learn.microsoft.com/ja-jp/entra/identity/users/licensing-service-plan-reference
 
 
    .DESCRIPTION
    「ライセンスのための製品名とサービス プラン 識別子」サイトの情報をもとに、
    Microsoft 365 サービスの一覧情報を取得します。
    https://learn.microsoft.com/ja-jp/entra/identity/users/licensing-service-plan-reference
 
    サイトから取得した情報はキャッシュされ、基本的にはローカルにダウンロード済み情報から
    応答を返します。明示的に ClearCache オプションを指定するか、キャッシュ情報が古いと判断された場合に
    再ダウンロードされます。
 
    .EXAMPLE
    Get-MS365info
 
    ----------
    Microsoft 365 関連プランが網羅されます。
 
 
    .EXAMPLE
    Get-MS365info -Language ko-kr -ClearCache
 
    ----------
    現在保持しているキャッシュをリフレッシュし、製品名を韓国語ローカライズします。
 
 
    .EXAMPLE
    Get-MS365info -ClearCache -noLocalize
 
    ----------
    現在保持しているキャッシュをリフレッシュしますが、製品名のローカライズを行わずリフレッシュを行います。
 
 
 
    .EXAMPLE
    Get-MS365info -Type Product
 
    ----------
    Microsoft 365 関連プランのうち、スイート・単体プランのものが列挙されます。
 
    .EXAMPLE
    Get-MS365info -Type ServicePlan
 
    ----------
    Microsoft 365 関連プランのうち、Service Plan のものが列挙されます。
 
    .EXAMPLE
    Get-MS365info -Type Suite
 
    ----------
    Microsoft 365 関連プランのうち、複数の Service Plan を含むものをスイートとして列挙します。
 
 
    .PARAMETER Language
    既定値 $PSCulture
    参照するサイトの言語を指定します。例 "ja-jp" や "en-us"
 
    .PARAMETER ClearCache
    キャッシュファイルをクリアし、サービス一覧をリフレッシュします。
 
    .PARAMETER noLocalize
    キャッシュファイルをリフレッシュする再に指定言語でのローカライズをスキップし、時間短縮します。
    ローカライズ説明として元データの CSV に含まれるものに合わせられます。
 
    .PARAMETER Quiet
    キャッシュファイルを更新・再構築する際に進捗情報表示を最小限にします。
 
    .PARAMETER Silent
    キャッシュファイルを更新・再構築する際に進捗情報表示を非表示にします。
 
    .LINK
 
    .NOTES
    ja.exchange-rates.org の Web サイト利用は無許可利用です。
 
    .INPUTS
    なし。パイプラインからの入力は受け付けません。
 
    .OUTPUTS
    換算結果は文字列です。
    ListCurrencyID オプション指定時の結果は通貨オブジェクトの配列です。(Obj.Country / Unit / Code)
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification="Progress information")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="Parameter used.")]

    Param(
    [Parameter(Mandatory=$false,Position=1)]
        [ValidateSet("af-za","am-et","ar-sa","as-in","az-latn-az","be-by","bg-bg","bn-in","bs-latn-ba","ca-es","ca-es-valencia","chr-cher-us","cs-cz","cy-gb","da-dk","de-de","el-gr","en-gb","en-us","es-es","es-mx","et-ee","eu-es","fa-ir","fi-fi","fil-ph","fr-ca","fr-fr","ga-ie","gd-gb","gl-es","gu-in","he-il","hi-in","hr-hr","hu-hu","hy-am","id-id","is-is","it-it","ja-jp","ka-ge","kk-kz","km-kh","kn-in","kok-in","ko-kr","lb-lu","lo-la","lt-lt","lv-lv","mi-nz","mk-mk","ml-in","mr-in","ms-my","mt-mt","nb-no","ne-np","nl-nl","nn-no","or-in","pa-in","pl-pl","pt-br","pt-pt","quz-pe","ro-ro","ru-ru","sk-sk","sl-si","sq-al","sr-cyrl-ba","sr-cyrl-rs","sr-latn-rs","sv-se","ta-in","te-in","th-th","tr-tr","tt-ru","ug-cn","uk-ua","ur-pk","uz-latn-uz","vi-vn","zh-cn","zh-tw")]
    [String]$Language=($PSCulture.toLower()),

    [Parameter(Mandatory=$false,Position=2)]
    [Switch]$noLocalize,

    [Parameter(Mandatory=$false,Position=3)]
        [ValidateSet("Product","ServicePlan","Suite","All")]
    [String]$Type="All",

    [Parameter(Mandatory=$false,Position=4)]
    [Switch]$ClearCache,

    [Parameter(Mandatory=$false,Position=5)]
    [Switch]$Quiet,

    [Parameter(Mandatory=$false,Position=6)]
    [Switch]$Silent

    )

    $CSVURL = "https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv"
    $DescUrl_1 = "https://learn.microsoft.com/"
    $DescURL_2 = "/entra/identity/users/licensing-service-plan-reference"
    $DescURL = ("{0}{1}{2}" -f $DescURL_1,$Language,$DescURL_2)

    $CacheFile =("MS365info_{0}.csv" -f $Language)


    $CacheIsActive = $False
    if (Test-Path -Path "$PSScriptRoot\$CacheFile") {
        if ((Get-Date -Date (Get-ChildItem "$PSScriptRoot\$CacheFile").LastWriteTime -Format "yyyyMM") -eq (Get-Date -Format "yyyyMM")) {
            $PlanList = Import-Csv -Encoding Default -Path "$PSScriptRoot\$CacheFile"
            $CacheIsActive = $True
        }
    }

    if ($ClearCache) { $CacheIsActive = $False }

    if ($CacheIsActive -eq $False) {

        if ($False -eq $Silent) {
            Write-Host ("Refreshing cache file{0}." -f (" with no Localize"*[int][bool]::Parse($noLocalize)))
        }

        $wc = New-Object System.Net.WebClient
        $url = $CSVURL

        $st = $wc.OpenRead($url)
        $enc = [System.Text.Encoding]::GetEncoding("UTF-8")
        $sr = New-Object System.IO.StreamReader($st, $enc)
        $html = $sr.ReadToEnd()
        $sr.Close()


        # CSV file Get
        $SVCList = ConvertFrom-CSV $html

        $PlanList = @()

        if ($False -eq $noLocalize) {

            $url = $DescURL

            $st = $wc.OpenRead($url)
            $enc = [System.Text.Encoding]::GetEncoding("UTF-8")
            $sr = New-Object System.IO.StreamReader($st, $enc)
            $html = $sr.ReadToEnd()
            $sr.Close()

            $htmlLine = $html.Split("`n")

            #データ参照範囲削減
            $Idx = $htmlLine.indexOf("<table>")
            $SubIdx = $htmlLine.indexOf("</table>")
            $htmlLine = $htmlLine[($idx)..($subIdx)]


            $ProductList = ($SVCList.String_ID | Sort-Object -Unique).Trim()
            $ServiceList = ($SVCList.Service_Plan_Name | Sort-Object -Unique | Where-Object {$_ -notin $ProductList }).Trim()

            $LocalizeName = @()

            $i = 1
            $NumOf = $Productlist.count

            if ($False -eq $Silent) {
                Write-Host ("ProductList {0} Localizing..." -f $Language)
            }

            # ProductList 分の LocalaizeName を生成

            foreach ($PSL in $ProductList) {

                $i++

                if (($False -eq $Silent) -and ($False -eq $Quiet)) {
                    if (($i % 100) -eq 0) { Write-Host ("{0} / {1} `t" -f $i,$NumOf) }
                }

                $Work1 = @($htmlLine | Where-Object {$_ -like ("*>{0}<*" -f ($PSL.Trim() -replace (" ",""))) })
                $Idx = $htmlLine.indexOf($Work1[0])
                if (-1 -ne $Idx) {
                    $Work1 = $htmlLine[($idx-1)] -Replace ("<[/]*td>","")
                    if ("" -eq $Work1) { $Work1 ="//not found//" }
                } else {
                    $Work1 ="//not found//"
                }

                $LocalizeName += @{ ($PSL.Trim() -replace (" ","")) = ($Work1.Trim() -Replace ("&amp;","&")) }
            }

            if (($False -eq $Silent) -and ($False -eq $Quiet)) {
                Write-Host ("{0} / {1} `t" -f ($i-1),$NumOf)
            }

            # ServiceList 分の LocalaizeName を生成

            $i = 1
            $NumOf = $Servicelist.count

            if ($False -eq $Silent) {
                Write-Host ("ServicetList {0} Localizing..." -f $Language)
            }

            foreach ($PSL in $ServiceList) {

                $i++

                if (($False -eq $Silent) -and ($False -eq $Quiet)) {
                    if (($i % 100) -eq 0) { Write-Host ("{0} / {1} `t" -f $i,$NumOf) }
                }

                $Work1 = @($htmlLine | Where-Object {$_ -like ("*>{0} (*" -f ($PSL.Trim() -replace (" ","")))})
                $Idx = $htmlLine.indexOf($Work1[0])

                if (-1 -ne $Idx) {
                    $Work2 = @($Work1[0] -Split ("<br>"))
                    $Work3 = $Work2 | Where-Object {$_ -like ("*{0} *" -f ($PSL.Trim() -replace (" ","")))}
                    $SubIdx = $Work2.indexof($Work3)

                    $Work1 = @($htmlLine[($idx+1)] -Split ("<br>"))

                    if ("</tr>" -eq $Work1) {
                        # Original Data Missing
                        $Work1 = @($htmlLine[($idx-1)] -Split ("<br>"))
                    }

                    $Work2 = ($Work1[$subIdx] -replace ("\([a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+\)","") ) -Replace ("<[/]*td>","")

                    if ("" -eq $Work2) { $Work2 ="//not found//"}

                } else {
                    $Work2 ="//not found//"
                }

                $LocalizeName += @{ ($PSL.Trim() -replace (" ","")) = ($Work2.Trim() -Replace ("&amp;","&")) }
            }

            if (($False -eq $Silent) -and ($False -eq $Quiet)) {
                Write-Host ("{0} / {1} `t" -f ($i-1),$NumOf)
            }

        }

        # LocalaizeName を割り当て

        $i = 1
        $NumOf = $SVClist.count

        if ($False -eq $Silent) {
            Write-Host "PlanList Refining..."
        }

        foreach ($SVC in $SVCList) {
            $i++

            if (($False -eq $Silent) -and ($False -eq $Quiet)) {
                if (($i % 100) -eq 0) { Write-Host ("{0} / {1} `t" -f $i,$NumOf) }
            }

            $Mem = New-Object PSObject
            $Mem | Add-Member -MemberType NoteProperty -Name "Product_Display_Name" -Value $SVC.Product_Display_Name
            $Mem | Add-Member -MemberType NoteProperty -Name "String_ID" -Value ($SVC.String_ID.Trim() -Replace (" ",""))
            $Mem | Add-Member -MemberType NoteProperty -Name "GUID" -Value $SVC.GUID
            $Mem | Add-Member -MemberType NoteProperty -Name "Service_Plan_Name" -Value ($SVC.Service_Plan_Name.Trim() -Replace (" ",""))
            $Mem | Add-Member -MemberType NoteProperty -Name "Service_Plan_Id" -Value $SVC.Service_Plan_Id
            $Mem | Add-Member -MemberType NoteProperty -Name "Service_Plans_Included_Friendly_Names" -Value $SVC.Service_Plans_Included_Friendly_Names

            if ($False -eq $noLocalize) {
                if ($LocalizeName.($Mem.String_ID) -eq "//not found//") {
                    $Mem | Add-Member -MemberType NoteProperty -Name "Product_Display_Name_Localize" -Value $SVC.Product_Display_Name
                    $Mem | Add-Member -MemberType NoteProperty -Name "isLocalizeProduct" -Value $False

                } else {
                    $Mem | Add-Member -MemberType NoteProperty -Name "Product_Display_Name_Localize" -Value $LocalizeName.($Mem.String_ID)
                    $Mem | Add-Member -MemberType NoteProperty -Name "isLocalizeProduct" -Value $True
                }

                if ($LocalizeName.($Mem.Service_Plan_Name) -eq "//not found//") {
                    $Mem | Add-Member -MemberType NoteProperty -Name "Service_Plans_Included_Friendly_Names_Localize" -Value $SVC.Service_Plans_Included_Friendly_Names
                    $Mem | Add-Member -MemberType NoteProperty -Name "isLocalizeServicePlan" -Value $False
                } else {
                    $Mem | Add-Member -MemberType NoteProperty -Name "Service_Plans_Included_Friendly_Names_Localize" -Value $LocalizeName.($Mem.Service_Plan_Name)
                    $Mem | Add-Member -MemberType NoteProperty -Name "isLocalizeServicePlan" -Value $True
                }
            } else {

                $Mem | Add-Member -MemberType NoteProperty -Name "Product_Display_Name_Localize" -Value $SVC.Product_Display_Name
                $Mem | Add-Member -MemberType NoteProperty -Name "Service_Plans_Included_Friendly_Names_Localize" -Value $SVC.Service_Plans_Included_Friendly_Names
                $Mem | Add-Member -MemberType NoteProperty -Name "isLocalizeServicePlan" -Value $False
                $Mem | Add-Member -MemberType NoteProperty -Name "isLocalizeProduct" -Value $False
            }

            $PlanList += $Mem
        }

        # Saving Cache File

        if (($False -eq $Silent) -and ($False -eq $Quiet)) {
            Write-Host ("{0} / {1} `t" -f ($i-1),$NumOf)
        }

        if ($False -eq $Silent) {
            Write-Host ("Updated cache file.")
        }

        $PlanList | Export-Csv -Encoding Default -Path "$PSScriptRoot\$CacheFile" -NoTypeInformation
    }


    Switch ($Type) {
        "All" {    $Result = $PlanList }

        "Product" {
            # Product 関連のみのリストにする。
            $Result = $PlanList | Select-Object -Property Product_Display_Name, Product_Display_Name_Localize, String_ID, GUID, isLocalizeProduct | Sort-Object -Property String_ID -Unique | Sort-Object -Property Product_Display_Name_Localize

        }

        "ServicePlan" {
            # ServicePlan 関連のみのリストにする。
            $Result = $PlanList | Select-Object -Property Service_Plans_Included_Friendly_Names, Service_Plans_Included_Friendly_Names_Localize, Service_Plan_Name, Service_Plan_ID, isLocalizeServicePlan | Sort-Object -Property Service_Plan_Name -Unique | Sort-Object -Property Service_PLans_Included_Friendly_Names_Localize
        }

        "Suite" {
            # Service_Plan_Name が2 個以上含まれる String_ID

            $SuiteList = $PlanList | Group-Object -Property String_ID | Where-Object {$_.Count -gt 1}
            $Result = $SuiteList | Select-Object -Property @{Name="String_ID";Expression={$_.Name}},
                        @{Name="DisplayName";Expression={$_.Group[0].Product_Display_Name}},
                        @{Name="DisplayNameLocalize";Expression={$_.Group[0].Product_Display_Name_Localize}},
                        @{Name="GUID";Expression={$_.Group[0].GUID}},
                        @{Name="isLocalizeProduct";Expression={$_.Group[0].isLocalizeProduct}},
                        @{Name="NumOfPlans";Expression={$_.Count}},
                        @{Name="Plans";Expression={$_.Group | Select-Object -Property @{Name="ServicePlan_DisplayName";Expression={ $_.Service_Plans_Included_Friendly_Names_Localize}}, Service_Plan_Name, Service_Plan_ID, isLocalizeServicePlan | Sort-Object -Property Service_PLans_Included_Friendly_Names_Localize}}


        }

    }

    Write-Output $Result
}


function Get-MS365IncludedPlans {
    <#
    .SYNOPSIS
    指定した Microsoft 365 関連スイート製品に含まれるプランの一覧を表示します。
 
 
    .DESCRIPTION
    指定した Microsoft 365 関連スイート製品に含まれるプランの一覧を表示します。
    内部的に Get-MS365Info -type All が呼び出されます。
 
    .EXAMPLE
    Get-MS365IncludedPlans -String_ID "ENTERPRISEPREMIUM"
 
    ----------
    ENTRPRISEPREMIUM = Office 365 E5 に含まれるプランの一覧が表示されます。
 
    .EXAMPLE
    Get-MS365IncludedPlans -ServicePlanName "EXCHANGE_S_STANDARD"
 
    ----------
    EXCHANGE_S_STANDARD = Exchange Online Plan 1 を含むプランの一覧が表示されます。
 
 
    .EXAMPLE
    Get-MS365IncludedPlans -String_ID "ENTERPRISEPREMIUM" -Language ko-kr -ClearCache
 
    ----------
    現在保持しているキャッシュをリフレッシュし、製品名を韓国語ローカライズします。
 
 
    .EXAMPLE
    Get-MS365IncludedPlans -String_ID "ENTERPRISEPREMIUM" -Language ko-kr -ClearCache -noLocalize
 
    ----------
    現在保持しているキャッシュをリフレッシュしますが、製品名のローカライズを行わずリフレッシュを行います。
 
 
    .PARAMETER String_ID
    参照するスイート製品の String_ID を指定します。例 "ENTERPRISEPREMIUM" や "SPE_E5"
 
 
 
    .PARAMETER ServicePlanName
    参照するサービスプランの Service_Plan_Name を指定します。例 "ENTERPRISEPREMIUM" や "SPE_E5"
 
 
    .PARAMETER Language
    既定値 $PSCulture
    参照するサイトの言語を指定します。例 "ja-jp" や "en-us"
 
    .PARAMETER ClearCache
    キャッシュファイルをクリアし、サービス一覧をリフレッシュします。
 
    .PARAMETER noLocalize
    キャッシュファイルをリフレッシュする再に指定言語でのローカライズをスキップし、時間短縮します。
    ローカライズ説明として元データの CSV に含まれるものに合わせられます。
 
    .PARAMETER Quiet
    キャッシュファイルを更新・再構築する際に進捗情報表示を最小限にします。
 
    .PARAMETER Silent
    キャッシュファイルを更新・再構築する際に進捗情報表示を非表示にします。
 
    .LINK
 
    .NOTES
    ja.exchange-rates.org の Web サイト利用は無許可利用です。
 
    .INPUTS
    なし。パイプラインからの入力は受け付けません。
 
    .OUTPUTS
    換算結果は文字列です。
    ListCurrencyID オプション指定時の結果は通貨オブジェクトの配列です。(Obj.Country / Unit / Code)
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification="Progress information")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="Parameter used.")]

    Param(
    [Parameter(Mandatory=$false,Position=1)]
    [String]$String_ID="ENTERPRISEPREMIUM",

    [Parameter(Mandatory=$false,Position=1)]
    [String]$ServicePlanName,


    [Parameter(Mandatory=$false,Position=2)]
        [ValidateSet("af-za","am-et","ar-sa","as-in","az-latn-az","be-by","bg-bg","bn-in","bs-latn-ba","ca-es","ca-es-valencia","chr-cher-us","cs-cz","cy-gb","da-dk","de-de","el-gr","en-gb","en-us","es-es","es-mx","et-ee","eu-es","fa-ir","fi-fi","fil-ph","fr-ca","fr-fr","ga-ie","gd-gb","gl-es","gu-in","he-il","hi-in","hr-hr","hu-hu","hy-am","id-id","is-is","it-it","ja-jp","ka-ge","kk-kz","km-kh","kn-in","kok-in","ko-kr","lb-lu","lo-la","lt-lt","lv-lv","mi-nz","mk-mk","ml-in","mr-in","ms-my","mt-mt","nb-no","ne-np","nl-nl","nn-no","or-in","pa-in","pl-pl","pt-br","pt-pt","quz-pe","ro-ro","ru-ru","sk-sk","sl-si","sq-al","sr-cyrl-ba","sr-cyrl-rs","sr-latn-rs","sv-se","ta-in","te-in","th-th","tr-tr","tt-ru","ug-cn","uk-ua","ur-pk","uz-latn-uz","vi-vn","zh-cn","zh-tw")]
    [String]$Language=($PSCulture.toLower()),

    [Parameter(Mandatory=$false,Position=3)]
    [Switch]$noLocalize,

    [Parameter(Mandatory=$false,Position=4)]
    [Switch]$ClearCache,

    [Parameter(Mandatory=$false,Position=5)]
    [Switch]$Quiet,

    [Parameter(Mandatory=$false,Position=6)]
    [Switch]$Silent

    )

    if ("" -eq $ServicePlanName) {
        $Product = Get-MS365info -Type Product -Language $Language -ClearCache:$ClearCache -noLocalize:$noLocalize -Quiet:$Quiet -Silent:$Silent

        if (-1 -eq $Product.String_ID.IndexOf($String_ID)) {
            $Result = -1
            if ($False -eq $Silent) {
                Write-Error("指定された String_ID '{0}' は見つかりませんでした。" -f $String_ID)
            }
        } else {
            $PlanList = Get-MS365info -Type All -Language $Language -ClearCache:$ClearCache -noLocalize:$noLocalize -Quiet:$Quiet -Silent:$Silent | Where-Object {$_.String_ID -eq $String_ID}
            $Result = $PlanList | Select-Object -Property Service_Plans_Included_Friendly_Names, Service_PLans_Included_Friendly_Names_Localize, Service_Plan_Name, Service_Plan_ID, isLocalizeServicePlan | Sort-Object -Property Service_Plan_Name -Unique | Sort-Object -Property Service_PLans_Included_Friendly_Names_Localize

        }
    } else {
        $ServicePlans = Get-MS365info -Type ServicePlan -Language $Language -ClearCache:$ClearCache -noLocalize:$noLocalize -Quiet:$Quiet -Silent:$Silent

        if (-1 -eq $ServicePlans.Service_Plan_Name.IndexOf($ServicePlanName)) {
            $Result = -1
            if ($False -eq $Silent) {
                Write-Error("指定された ServicePlanName '{0}' は見つかりませんでした。" -f $ServicePlanName)
            }
        } else {
            $PlanList = Get-MS365info -Type All -Language $Language -ClearCache:$ClearCache -noLocalize:$noLocalize -Quiet:$Quiet -Silent:$Silent | Where-Object {$_.Service_Plan_Name -eq $ServicePlanName}
            $Result = $PlanList | Select-Object -Property Product_Display_Name, Product_Display_Name_Localize, String_ID, GUID, isLocalizeProduct | Sort-Object -Property String_ID -Unique | Sort-Object -Property Product_Display_Name_Localize

        }
    }

    Write-Output $Result

}



function Compare-MS365SuitePlans {
    <#
    .SYNOPSIS
    指定した Microsoft 365 関連スイート製品に含まれるプランを比較用に一覧を表示します。
 
 
    .DESCRIPTION
    指定した Microsoft 365 関連スイート製品に含まれるプランを比較用に一覧を表示します。
    内部的に Get-MS365IncludedPlans -String_ID <<String_ID>> が呼び出されます。
 
    .EXAMPLE
    Compare-MS365SuitePlans -Suites @("STANDARDPACK","ENTERPRISEPACK","ENTERPRISEPREMIUM")
 
    STANDARDPACK = Office 365 E1
    ENTERPRISEPACK = Office 365 E3
    ENTRPRISEPREMIUM = Office 365 E5
    に含まれるプランの比較一覧が表示されます。
 
 
 
    .EXAMPLE
    Compare-MS365SuitePlans -Suites @("STANDARDPACK","ENTERPRISEPACK","ENTERPRISEPREMIUM") -Language ko-kr -ClearCache
 
    ----------
    現在保持しているキャッシュをリフレッシュし、製品名を韓国語ローカライズします。
 
 
    .EXAMPLE
    $Suites = @("STANDARDPACK","ENTERPRISEPACK","ENTERPRISEPREMIUM","SPE_E3","SPE_E5")
    $Matrix = Compare-MS365SuitePlans -Suites $Suites
    $Matrix | Where-Object {$_.DisplayName -ne "GUID"} | Select-Object -Property (@("DisplayNameLocalize")+$Suites) | FT
 
    ----------
    比較したいプランを配列変数にいれて比較し、表示します。
 
 
    .EXAMPLE
    $Suites = (Get-MS365Info -Type Suite | Where-Object {$_.DisplayName -like "*Teams*"}).String_ID
    $Matrix = Compare-MS365SuitePlans -Suites $Suites
    $Matrix | Export-Csv -Path TeamsPlansMatrix.csv -Encoding Default -NoTypeinfo
 
    ----------
    Get-MS365Info コマンド結果を名前でフィルターして Teams 関連プランを取得し、比較マトリックスを取得。
    それを CSV で保存します。
 
 
    .PARAMETER Suites
 
    参照するスイート製品の String_ID 群を指定します。例 @("ENTERPRISEPREMIUM","SPE_E5")
 
 
    .PARAMETER Language
    既定値 $PSCulture
    参照するサイトの言語を指定します。例 "ja-jp" や "en-us"
 
    .PARAMETER Quiet
    キャッシュファイルを更新・再構築する際に進捗情報表示を最小限にします。
 
    .PARAMETER Silent
    キャッシュファイルを更新・再構築する際に進捗情報表示を非表示にします。
 
    .LINK
 
    .NOTES
    ja.exchange-rates.org の Web サイト利用は無許可利用です。
 
    .INPUTS
    なし。パイプラインからの入力は受け付けません。
 
    .OUTPUTS
    換算結果は文字列です。
    ListCurrencyID オプション指定時の結果は通貨オブジェクトの配列です。(Obj.Country / Unit / Code)
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification="Progress information")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="Parameter used.")]

    Param(
    [Parameter(Mandatory=$true,Position=1)]
    [Array]$Suites,

    [Parameter(Mandatory=$false,Position=2)]
        [ValidateSet("af-za","am-et","ar-sa","as-in","az-latn-az","be-by","bg-bg","bn-in","bs-latn-ba","ca-es","ca-es-valencia","chr-cher-us","cs-cz","cy-gb","da-dk","de-de","el-gr","en-gb","en-us","es-es","es-mx","et-ee","eu-es","fa-ir","fi-fi","fil-ph","fr-ca","fr-fr","ga-ie","gd-gb","gl-es","gu-in","he-il","hi-in","hr-hr","hu-hu","hy-am","id-id","is-is","it-it","ja-jp","ka-ge","kk-kz","km-kh","kn-in","kok-in","ko-kr","lb-lu","lo-la","lt-lt","lv-lv","mi-nz","mk-mk","ml-in","mr-in","ms-my","mt-mt","nb-no","ne-np","nl-nl","nn-no","or-in","pa-in","pl-pl","pt-br","pt-pt","quz-pe","ro-ro","ru-ru","sk-sk","sl-si","sq-al","sr-cyrl-ba","sr-cyrl-rs","sr-latn-rs","sv-se","ta-in","te-in","th-th","tr-tr","tt-ru","ug-cn","uk-ua","ur-pk","uz-latn-uz","vi-vn","zh-cn","zh-tw")]
    [String]$Language=($PSCulture.toLower()),


    [Parameter(Mandatory=$false,Position=3)]
    [Switch]$Quiet,

    [Parameter(Mandatory=$false,Position=4)]
    [Switch]$Silent

    )

    $AllPlanInfo = @()
    $PlanNames = @()

    foreach ($S in $Suites) {
        $ServicePlans = Get-MS365IncludedPlans -String_ID $S -Language $Language -Silent
        if (-1 -eq $ServicePlans) {
            if ($False -eq $Silent) {
                Write-Host ("String_ID: {0} is not found and skipping." -f $S)
            }
        } else {
            $AllPlanInfo += @{ $S = $ServicePlans.Service_Plan_Name }
            $PlanNames += $ServicePLans
        }
    }


    $PlanNames = $PlanNames | Sort-Object -Property Service_Plan_Name -Unique | Sort-Object -Property Service_Plans_Included_Friendly_Names_Localize

    $HeaderInfo = Get-MS365Info -Type Product -Language $Language | Where-Object {$_.String_ID -in $Suites}

    $Result = @()

    $Mem1 = New-Object PSObject
    $Mem1 | Add-Member -MemberType NoteProperty -Name "ServicePlanName" -Value "//"
    $Mem1 | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value "Suite Name"
    $Mem1 | Add-Member -MemberType NoteProperty -Name "DisplayNameLocalize" -Value "Suite Name"
    $Mem1 | Add-Member -MemberType NoteProperty -Name "ServicePlanID" -Value "Suite Name"

    $Mem2 = New-Object PSObject
    $Mem2 | Add-Member -MemberType NoteProperty -Name "ServicePlanName" -Value "//"
    $Mem2 | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value "Localize"
    $Mem2 | Add-Member -MemberType NoteProperty -Name "DisplayNameLocalize" -Value "Localize"
    $Mem2 | Add-Member -MemberType NoteProperty -Name "ServicePlanID" -Value "Localize"

    $Mem3 = New-Object PSObject
    $Mem3 | Add-Member -MemberType NoteProperty -Name "ServicePlanName" -Value "//"
    $Mem3 | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value "GUID"
    $Mem3 | Add-Member -MemberType NoteProperty -Name "DisplayNameLocalize" -Value "GUID"
    $Mem3 | Add-Member -MemberType NoteProperty -Name "ServicePlanID" -Value "GUID"


    foreach ($S in $Suites) {
        $Idx = $HeaderInfo.String_ID.IndexOf($S)
        if (-1 -ne $Idx) {
            $Mem1 | Add-Member -MemberType NoteProperty -Name $S -Value $HeaderInfo[$idx].Product_Display_Name
            $Mem2 | Add-Member -MemberType NoteProperty -Name $S -Value $HeaderInfo[$idx].Product_Display_Name_Localize
            $Mem3 | Add-Member -MemberType NoteProperty -Name $S -Value $HeaderInfo[$idx].GUID
        }
    }

    $Result += $Mem1
    $Result += $Mem2
    $Result += $Mem3


    foreach ($P in $PlanNames) {
        $Mem1 = New-Object PSObject
        $Mem1 | Add-Member -MemberType NoteProperty -Name "ServicePlanName" -Value $P.Service_Plan_Name
        $Mem1 | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $P.Service_Plans_Included_Friendly_Names
        $Mem1 | Add-Member -MemberType NoteProperty -Name "DisplayNameLocalize" -Value $P.Service_Plans_Included_Friendly_Names_Localize
        $Mem1 | Add-Member -MemberType NoteProperty -Name "ServicePlanID" -Value $P.Service_Plan_ID

        foreach ($S in $Suites) {
            $Idx = $HeaderInfo.String_ID.IndexOf($S)
            if (-1 -ne $Idx) {
                $Idx = $AllPlanInfo.($S).IndexOf($P.Service_Plan_Name)
                if (-1 -ne $Idx) {
                    $Mem1 | Add-Member -MemberType NoteProperty -Name $S -Value "o"
                } else {
                    $Mem1 | Add-Member -MemberType NoteProperty -Name $S -Value ""
                }
            }
        }
        $Result += $Mem1
    }

    Write-Output $Result

}


Export-ModuleMember -Function Get-MS365Info, Get-MS365IncludedPlans, Compare-MS365SuitePlans