Microsoft Office LTSC入りAMIを試してみた #reinvent

2022.12.25

しばたです。

先月末からAWSよりMicorosoft Office入りAMIが提供され、EC2でMicrosoft Office LTSC Professional Plus 2021が使える様になりました。

re:Invent期間中に弊社吉井により試してみた記事が公開されていますが、当時は何らかのエラーが発生し実際に試せなかった様です。

そこでちょっと遅くなりましたが私も試してみることにしました。

はじめに

最初に気を付けるべき点について触れておきます。

Microsoft製品入りAMIについて以前にVisual Studio入りAMIがAWSより提供され利用可能となっています。

今回もVisual Studioの時と同様でOffice LTSCのソフトウェアはSPLA契約の元AWSから提供される形となっており、

  • AWS License Managerを使いOffice利用ユーザーのライセンスを管理
  • AWS License ManagerとAWS Managed Microsoft ADを連携させOfficeを利用可能なユーザーを制限する

といった前提条件は変わりません。
その上でさらにOfficeのライセンスアクティベーションのために追加の制約が発生する形となっています。

前提条件

ここからOffice LTSC入りAMIを利用するための前提条件について解説します。

ネットワーク前提条件

ネットワークの前提条件は以下の通りです。

  • AWS Managed Microsoft AD用に2つのAZに分かれた2つのサブネットが必要
  • Office LTSC入りEC2でAWS Systems Managerを使用するためサービスエンドポイントへのアクセスが必要
    • インターネットへの経路 or VPC Endpoint どちらでも可
  • [NEW!] OfficeライセンスアクティベーションのためにAWSが提供するMicrosoft KMSサーバーへの疎通が必要
    • これはAWS License ManagerのVPC Endpoint (com.amazonaws.[region].activation-license-manager)でのみ提供
    • AWS License ManagerとAWS Managed Microsoft ADの紐づけの際に生成される
    • 事前に「TCP 1688ポートへのインバウンド通信」を許可するセキュリティーグループを用意する必要有り
  • [NEW!] VPC Endpointの名前解決のためにVPC設定の「DNS ホスト名」と「DNS 解決」を有効にする
  • [NEW!] AWS Managed Microsoft AD向けにRoute 53 Resolver Inbound Endpointを用意する

基本的な条件はVisual Studio入りAMIの場合と同様なものの、Office LTSCの場合はライセンスアクティベーションにAWSが提供するMicrosoft KMSサーバーを使うためVPC Endpointが必要になります。
このVPC EndpointはAWS License ManagerとAWS Managed Microsoft ADの紐づけの際に自動生成され、TCP 1688番ポート *1でアクセス可能にしてやる必要があります。

加えてOffice LTSCを利用する場合はRoute 53 Resolver Outbound Endpointが必要になります。
Visual Studio AMIを利用する場合はEC2インスタンスのDNS設定を直接AWS Managed Microsoft ADに向ける様に自動で設定変更されていたのですが、Office LTSCを使う場合は変更されずRoute 53 Resolver(旧名 Amazon Provided DNS)を使う初期設定のままとなります。
このためActive Directory環境に対する名前解決が出来ずにインスタンス初回起動時のドメイン参加処理でエラーとなってしまいます。

インスタンス起動時に自動実行されるSSM Run Commandのスクリプトを確認すると以下の様になっており、「Office LTSCを使う場合はRoute53 resolverを使う」と明記されています。

ドメイン参加スクリプトから一部抜粋

# Set DNS address only for VS. For Office we are taking Route53 resolver approach
if($IsOfficeProductCode -eq 'false'){
    Write-Output 'Setting the DNS address to MAD Domain Controller IP for VS Product...'
    try{
        $dns = Get-DnsClientServerAddress
        Write-Output "Current Value for index " $dns.InterfaceIndex[0] "-" $dns.ServerAddresses
        Set-DnsClientServerAddress -InterfaceIndex $dns.InterfaceIndex[0] -ServerAddresses ($DCIpAddress1, $DCIpAddress2) -ErrorAction Stop
        $dns = Get-DnsClientServerAddress
        Write-Output "Updated Value for index " $dns.InterfaceIndex[0] "-" $dns.ServerAddresses
        Write-Output 'Successfully! updated the DNS address to MAD Domain Controller IP...'
    }
    catch [System.Exception] {
        Write-TerminatingError -Message "Unable to set DNS address: $_"
    }
}

個人的には「AWS Managed Microsoft AD → Route 53 Resolver へのフォーワードをそのまま使う」のでは駄目だったのか?といささか疑問なのですが、AWSのスクリプトがこうなっている以上どうしようもありません...

ここまでを踏まえて、最終的にざっくり下図の様な構成が求められます。

License Manager用VPC EndpointとRoute 53 Resolver Outbound Endpointが必須なことによりなかなか面倒な構成になっています。

比較用にVisual Studio AMIを使う場合のネットワーク構成を出すと下図の通りです。

こちらはライセンスアクティベーションに専用のエンドポイントも不要ですし、Active Directory参加時にEC2インスタンスのDNS設定も自動で変更されるので非常にスッキリしています。

やってみた

ここからは実際に各種設定を行っていきます。
今回は私の検証用AWSアカウントの東京リージョンで試しています。

VPC環境の用意

VPC環境の準備については手順を割愛します。

ざっくり前項で紹介した構成図と同等のものをご用意ください。
今回はEC2インスタンスからインターネットへアクセス可能な環境を作っています。

AWS Managed Microsoft ADの用意

Microsoftライセンス管理のためにAWS Managed Microsoft AD環境が必要です。
エディションは「Standard Edition」「Enterprise Edition」どちらでも構いません。

本記事ではcorp.contoso.comというディレクトリ環境を用意しておきます。

Route 53 Resolver Outbound Endpointの用意

先述の通りOffice LTSC AMIを使う場合はEC2インスタンスのDNS設定が書き換えられないためRoute 53 Resolver Outbound Endpointが必要です。

マネジメントコンソールからざっくり以下の設定で作成しています。

出来上がったものはこんな感じになります。

この設定ではcorp.contoso.comドメインへのDNSクエリをAWS Managed Microsoft ADのDNSサーバー(10.0.21.119,10.0.22.189)にフォーワードする様にしています。
セキュリティーグループはAWS Managed Microsoft ADのDNSサーバーへのアウトバウンド通信を許可しているものであればなんでも構いません。

AWS License Managerの用意

AWS License Managerの設定はVisual Stuio AMIの時とほぼ同じです。

License Managerのコンソール画面にアクセスすると新たに製品「Office Professional Plus」が増えていることがわかります。
先にMarketplace AMIをサブスクライブしている場合はその情報も表示されます。

左ペインの「設定」から「ユーザーベースのサブスクリプション」を選びAWS Managed Microsoft AD環境と紐づけしてやります。

ディレクトリIDを選択し、対象製品で「Office Professional Plus」を選択すると画面下部にVPC Endpointを追加するための設定が表示されます。

エンドポイント(ENI)を増やすVPCとサブネット、エンドポイント用のセキュリティーグループを選択します。

セキュリティーグループは事前に作成しておく必要があり、TCP 1688のインバウンド通信を許可するものを選んでやります。
「設定」ボタンをクリックするとAWS License ManagerとAWS Managed Microsoft ADの紐づけ処理が行われます。

しばらく待てば完了し下図の様になります。
Visual Studioの時と同様にRemote Desktop Service SALも同時に選ばれます。

作成されたVPC Endpointはこんな感じのcom.amazonaws.[region].activation-license-managerに対するものになります。

セキュリティーグループの設定はこんな感じでOKです。

IAM Roleの用意

Visual Studio AMIの時と同様に

  • AmazonSSMManagedInstanceCoreポリシー
  • AWSMarketplaceMeteringFullAccessポリシー

の権限を持つIAM Roleを用意しておきます。
既存のものがあればそれを使う形で問題ありません。

EC2の起動

ここまで準備ができたらEC2を作成していきます。

はじめにMarketplaceからOffice LTSC Professional Plusのイメージをサブスクライブしておいてください。

サブスクライブ後、このイメージからインスタンスを作成していきます。

その他項目については環境に応じてよしなに設定してください。
IAMインスタンスプロファイルの設定は必須ですので忘れないようにしてください。

(今回はVisual Studio AMIを試した時のIAM Roleを流用)

インスタンスを作成ししばらく待てば利用可能になります。
Visual Studio AMIのときと同様に環境周りに不備がある場合は起動したインスタンスが直ちに終了されます。

Office LTSC AMIの場合はインスタンス初回起動時に以下のSSM Run Commandが実施されます。

  • AWS Managed Microsoft ADに参加させるAWS-RunPowerShellドキュメント
    • Visual Studio AMIの時とは異なりインスタンスのDNS設定は変更しない
    • Active Directory参加後に一度再起動される
  • Officeのライセンスアクティベーションを行うAWS-RunPowerShellドキュメント
    • ドメイン参加完了後に行われる
    • アクティベーション先(Microsoft KMS Server)の指定をVPC EndpointのIPにしている

Officeライセンスのアクティベーションも行っているのが独自のポイントとなります。

もちろんインスタンスを起動しただけでは誰もRDP接続出来ない様に制限されています。

ユーザーとインスタンスの紐づけ

ここでAWS License Managerに戻りOfficeを利用するユーザー設定(サブスクライブ)を行います。

この手順はVisual Studio AMIの場合と全く同じです。
今回は対象インスタンスから「ユーザーをサブスクライブして関連付け」ます。

関連付けの完了まで少し待ちます。
エラー無く完了すればOKです。

Officeの利用は当然「サーバー管理用途」外なのでRemote Desktop SALのサブスクライブも同時に行われます。

RDP接続

これで無事対象インスタンスにRDP接続できる様になります。

今回はOffice LTSC Proffesional Plus 2021 (Ver.2108)がインストール済みでした。
OS(Windows Server 2022)とOfficeはともに英語版です。

またslmgr.vbsを使いOfficeのライセンス認証状況を調べると以下の様にVPC Endpoint(10.0.21.102)向けて行われていることも確認できました。

# 各種ライセンス認証状況を確認
PS C:\> cscript C:\Windows\System32\slmgr.vbs /dlv All

# ※今回に関わる分のみ抜粋
Name: Office 21, Office21ProPlus2021VL_KMS_Client_AE edition
Description: Office 21, VOLUME_KMSCLIENT channel
Activation ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Application ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Extended PID: xxxxx-xxxxx-xxx-xxxxxx-xx-xxxx-xxxxx.xxxx-xxxxxxx
Product Key Channel: Volume:GVLK
Installation ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Partial Product Key: 6F7TH
License Status: Licensed
Volume activation expiration: 259180 minute(s) (180 day(s))
Remaining App rearm count: 3
Remaining SKU rearm count: 3
Trusted time: 12/25/2022 4:23:47 AM
Configured Activation Type: All

Most recent activation information:
Key Management Service client information
    Client Machine ID (CMID): xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    Registered KMS machine name: 10.0.21.102:1688
    KMS machine IP address: 10.0.21.102
    KMS machine extended PID: xxxxx-xxxxx-xxx-xxxxxx-xx-xxxx-xxxx.xxxx-xxxxxxx
    Activation interval: 720 minutes
    Renewal interval: 20160 minutes
    KMS host caching is enabled

Active Directory側情報

Active Directory側には例によってAWS管理の「License Manager」OUが作成され専用のユーザーやグループが作成されます。

各EC2インスタンスに専用のOUとグループポリシー(マシンポリシー)がアタッチされRDP接続を制限するのもVisual Studio AMIの時と同様です。

費用感

本日(2022年12月)時点の費用感については以下の通りです。

製品 価格(税抜) 備考
Office LTSC Proffesional Plus 21.43 USD/unit
Remote Desktop Services SAL 10 USD/unit

Office LTSCとRDS SALの月額課金となり日割りはありません。

これだけだと「まあこんなものか。」という金額ですが、Office入りAMIを使う場合はVPC EndpointとRoute 53 Resolver Outbound Endpointが必須となります。
このため最低でも以下の追加費用が発生します。

サービス 価格(東京リージョン) 備考
VPC Endpoint (License Manager) : ENI費用 0.014 USD/hour (月額約 10.08 USD) 1 ENI当たりの単価
VPC Endpoint (License Manager) : 通信費用 通信量に応じて ライセンス認証だけなのでほぼ0になるはず
Route 53 Resolver Outbound Endpoint : ENI費用 0.125 USD/hour (月額約 90 USD) 1 ENI当たりの単価
Route 53 Resolver Outbound Endpoint : クエリ費用 クエリ数に応じて フォーワードする分だけなので少額で済むはず

この中でRoute 53 Resolver Outbound Endpointが特に厳しく、通常2AZに多重化するため最低でも月 180 USD(90 x 2) の費用を見込んでおく必要があります。
環境によってはこれ以外の費用が掛かることもあるでしょうし「Office入りAMIを使う際はネットワーク周りの費用が重い」というのを押さえておくと良いでしょう。

補足

最後に何点か補足をば。

補足1 : Officeの日本語化

Officeの日本語化は以下のMicrosoftサポートの手順で行えます。

(言語パックをダウンロードするのに要インターネットアクセス)

OSも併せて日本語化した結果はこんな感じです。

補足2 : ローカル管理者の設定

Visual Studio AMIの場合と同様にSSM Sessionは普通に使えますので、ローカル管理者を設定したい場合はSSM Sessionから以下のコマンドを実行してください。

# SSM SessionからRDP接続を許可したユーザーを Administrators グループに参加させてやる
Add-LocalGroupMember -Group Administrators -Member nobunaga@corp.contoso.com

補足3 : RDS SAL用のRD License Server

Visual Studio AMIの場合と同様でRD License Server関連の設定は一切変更されておらずデフォルトのままでした。
Office入りAMIにおいても多人数で1台のEC2を共用するユースケースには対応できませんのでご注意ください。

補足4 : 自動ドメイン参加スクリプト

Visual Studio AMIが公開されたときのものとだいたい同じですが-IsOfficeProductCodeパラメーターが増えています。

展開して表示
<#
 .SYNOPSIS
 Domain joins an instance to the customers MAD

 .DESCRIPTION
 This script will domain join an instance in the MAD under the given OU, this script will run on customers instance using SSM

 It will perform the follow actions:
 1. Validation : Check if instance is part of domain already and under the correct OU, if not proceed
 2. Install AD tools on the instance
 3. Set the DNS Address to a MAD Domain Controllers IP
 4. Domain join the instance to the computer object already created using One time machine pascode
 5. Exit with status 3010 which tells ssm to restart the computer and rerun the script for validation on 1

As this script is running on the cutomers instance we will not be placing it on the instance rather will be running it by directly passing the script in a SSM RunPowershell document.

#>

#==================================================
# Utility Functions 
#==================================================
Function Write-TerminatingError {
    <#
    .SYNOPSIS
    write the error to the console and exit the program.
    #>
    Param (
        [String]$Message
    )
    Write-Error $Message
    exit 1
}

Function Install-ADTools {
    $attempts=3
    $sleepInSeconds=1
    do
    {
        try
        {
            Write-Output 'Installing AD tools on the instance...'
            Install-WindowsFeature -Name 'RSAT-AD-Tools' -ErrorAction Stop
            break;
        }
        catch [System.Exception]
        {
            Write-Output "Unable to install AD tools on the machine, exception : $_"
            if($attempts -eq 1)
            {
                 Write-TerminatingError -Message "Unable to install AD tools on the machine, failing error :  $_"
            }
        }
        $attempts--
        if ($attempts -gt 0) { sleep $sleepInSeconds }
    } while ($attempts -gt 0)
}

#==================================================
# Main
#==================================================
Function Domain-Join-Instance{
    <# The script accepts the below paramters
        DCIpAddress1,DCIpAddress2 : Domain Controller IPs for DCs of the directory, Domain: customers domain, OneTimeMachinePasscode : Password for the Computer Object for this instance
    #>
    Param(
        [string]$DCIpAddress1,
        [string]$DCIpAddress2,
        [string]$Domain,
        [string]$OneTimeMachinePasscode,
        [string]$ROUserPass,
        [string]$ROUsername,
        [string]$Region,
        [string]$InstanceId,
        [string]$IsOfficeProductCode

    )

    $LMReservedOUName = 'LicenseManager'
    $sleepInSeconds=1

    try{
        $CurrentDomain = Get-CimInstance -ClassName 'Win32_ComputerSystem' -ErrorAction Stop | Select-Object -ExpandProperty 'Domain'
    }
    catch [System.Exception] {
        Write-TerminatingError -Message "Unable to Computer details to check current domain: $_"
    }

    # We validate if gpo update is working, if not we need to perform the domain join.
    # when we are recovering a broken instance, it's possible that the local domain name is right but the computer object has been replaced.
    $GPUpdateResult = gpupdate /force
    $GPUpdateResult = Out-String -InputObject $GPUpdateResult
    $GPErrorMsg = "Computer policy could not be updated successfully."

    if(($CurrentDomain -eq $Domain) -and (-not ($GPUpdateResult -match $GPErrorMsg))){
        Write-Output "Instance is part of domain :  $CurrentDomain"

        $username = "$Domain\$ROUsername"
        $password = $ROUserPass | ConvertTo-SecureString -asPlainText -Force
        $credential = New-Object System.Management.Automation.PSCredential($username,$password)

        #Getting OU Path of computer object
        $attempts=3
        do
        {
            try
            {
                Write-Output 'Getting AD Computer details...'
                $ADComputer = Get-ADComputer -Identity $ENV:ComputerName -Credential $credential -ErrorAction Stop
                break;
            }
            catch [System.Exception]
            {
                Write-Output "Unable to get AD Computer details..., exception : $_"
                if($attempts -eq 1)
                {
                     Write-TerminatingError -Message "Unable to fetch details for the computer object: $_"
                }
            }
            $attempts--
            if ($attempts -gt 0) { sleep $sleepInSeconds }
        } while ($attempts -gt 0)

        $DistinguishedName = $ADComputer | Select-Object -ExpandProperty DistinguishedName
        # Example for DistinguishedName : CN=EC2AMAZ-PCQS7MN,OU=i-05f83f681c65dfd6c-OU,OU=IAD,OU=LicenseManager,OU=AWS Reserved,DC=example,DC=com

        $OUPathArr = $DistinguishedName -split ',',2
        $CurrentOUPath = $OUPathArr[1]

        #Building the OU Path from customers domain and reserved ou path
        $attempts=3
        do
        {
            try
            {
                Write-Output 'Getting AD Domain details...'
                $ADDomain = Get-ADDomain -credential $credential -ErrorAction Stop
                break;
            }
            catch [System.Exception]
            {
                Write-Output "Unable to get AD Domain details......, exception : $_"
                if($attempts -eq 1)
                {
                     Write-TerminatingError -Message "Unable to fetch AD domain details: $_"
                }
            }
            $attempts--
            if ($attempts -gt 0) { sleep $sleepInSeconds }
        } while ($attempts -gt 0)

        $DomainFQDN = $ADDomain.DNSRoot
        $BasePath = $ADDomain.DistinguishedName
        $ReservedOUPath = "OU=$LMReservedOUName,OU=AWS Reserved"
        $ExpectedOUPath = "OU=$InstanceId-OU,OU=$Region,$ReservedOUPath,$BasePath"

        #SUCCESS : If computer is part of OU - exit 0
        if($CurrentOUPath -eq $ExpectedOUPath){
            Write-Output "Success... Instance was domain joined under the correct OU, at path : $ExpectedOUPath"
            exit 0
        } else{
            Write-TerminatingError -Message "Failure... Instance is joined under OU path : $CurrentOUPath `nInstead of expected OU path : $ExpectedOUPath"
        }
    } else{
        Write-Output "Instance is not part of domain so proceeding to join it to the domain: $Domain"
    }

    # in case of recovery, we also need to remove it from any domain explicitly even if it's already in the target domain.
    Write-Output "Removing the computer from the current domain if it's in any, failure to do so will not terminate the script."
    $hostname = hostname
    netdom remove $hostname /domain: /force

    #Install AD Tools
    Install-ADTools

    Write-Output 'Office instance launched: $IsOfficeProductCode'

    # Set DNS address only for VS. For Office we are taking Route53 resolver approach
    if($IsOfficeProductCode -eq 'false'){
        Write-Output 'Setting the DNS address to MAD Domain Controller IP for VS Product...'
        try{
            $dns = Get-DnsClientServerAddress
            Write-Output "Current Value for index " $dns.InterfaceIndex[0] "-" $dns.ServerAddresses
            Set-DnsClientServerAddress -InterfaceIndex $dns.InterfaceIndex[0] -ServerAddresses ($DCIpAddress1, $DCIpAddress2) -ErrorAction Stop
            $dns = Get-DnsClientServerAddress
            Write-Output "Updated Value for index " $dns.InterfaceIndex[0] "-" $dns.ServerAddresses
            Write-Output 'Successfully! updated the DNS address to MAD Domain Controller IP...'
        }
        catch [System.Exception] {
            Write-TerminatingError -Message "Unable to set DNS address: $_"
        }
    }

    #Domain join this instance to the AD and connect to the Computer Object created already using OneTimeMachinePasscode
    $OneTimeMachinePasscodeSecure = $OneTimeMachinePasscode | ConvertTo-SecureString -AsPlainText -Force
    Write-Output 'Domain joining the instance...'
    try{
        $JoinCred = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList ([PSCustomObject]@{
                UserName = $Null
                Password = ($OneTimeMachinePasscodeSecure)[0]
            })
        Add-Computer -Domain $Domain -Options UnsecuredJoin, PasswordPass -Credential $JoinCred -ErrorAction Stop
        Write-Output 'Successfully! domain joined the instance'
    }
    catch [System.Exception] {
        Write-TerminatingError -Message "Unable to domain join the instance: $_"
    }

    Write-Output 'Restarting the instance to reflect changes...'
    #exit 3010 sends a request to ssm to restart instance and rerun the script again : https://docs.aws.amazon.com/systems-manager/latest/userguide/send-commands-reboot.html
    exit 3010
}

#Function call that runs the script, it will be replaced in code with param and values while calling the script
Domain-Join-Instance -DCIpAddress1 '10.0.21.119' -DCIpAddress2 '10.0.22.189' -Domain 'corp.contoso.com' -OneTimeMachinePasscode 'xxxxxxxxxx (中略) xxxxxxxxxx' -ROUsername 'RO-LM-NRT' -ROUserPass 'xxxxxxxxxx (中略) xxxxxxxxxx' -Region 'NRT' -InstanceId 'i-0701a1b3e35bfedf2' -IsOfficeProductCode 'true'

補足5 : Officeライセンスアクティベーションスクリプト

ライセンスアクティベーションスクリプトはこんな感じでした。

展開して表示
#==================================================
# Utility Functions
#==================================================
Function Write-TerminatingError {
    <#
    .SYNOPSIS
    write the error to the console and exit the program.
    #>
    Param (
        [String]$Message
    )
    throw $Message
}

Function Find-First-Match {
    <#
    .SYNOPSIS
    Return the entry in the array
    #>
    Param (
        [String[]]$StringArray,
        [String]$RegEx
    )
    foreach ($element in $StringArray){
      if ($element -match $RegEx){
        return $element
      }
    }
    return $null
}

#==================================================
# Main
#==================================================
Function Office-Activation-Instance{
    Param(
            [string]$Region,
            [string]$Stage,
            [string]$Endpoint
        )
        Write-Output "Office Activation instance script launched for $Region, $Stage and $Endpoint"
        # Entering into the Office16 folder. Terminate if the required folder doesn't exist.
        $Path = 'c:\Program Files\Microsoft Office\Office16'
        if (-not (Test-Path -LiteralPath $Path)) {
            Write-TerminatingError -Message "Failure... Path $Path does not exist."
        }
        Set-Location -Path $Path

	    # This is required since AD does not uses default Amazon DNS
	    $ServerIPAddress = (Resolve-DnsName "$Endpoint" -Server 169.254.169.253).IPAddress | Select -First 1
        if (-not (Test-Path -LiteralPath ".\ospp.vbs")) {
                    Write-TerminatingError -Message "Failure... the script ospp.vbs does not exist."
        }
        Write-Output "Running the activation script with the license manager : $ServerIPAddress"
        cscript ospp.vbs /sethst:$ServerIPAddress

        for ($i=0; $i -lt 3; $i++){
            cscript ospp.vbs /act
            try{
                        $result = cscript ospp.vbs /dstatus
            } catch [System.Exception] {
                         Write-TerminatingError -Message "Unexpected system failure while getting the activation status."
            }
            # Check if the result array contains the key for license status.
            $checkKey = Find-First-Match -StringArray $result  -RegEx "LICENSE STATUS"
            # Check if the value of the key license status is LICENSED:
            $checkKeyValue = Find-First-Match -StringArray $result  -RegEx "LICENSE STATUS:  ---LICENSED---"
            if (($checkKey -eq $null) -or ($checkKeyValue -eq $null)) {
                Start-Sleep -Seconds 30
                continue;
            }
            Write-Output "Office is licensed........$result"
            return;
        }
        $errorDescription = Find-First-Match -StringArray $result  -RegEx "ERROR DESCRIPTION"
        $errorDescriptionTrimmed = $errorDescription -replace "ERROR DESCRIPTION: ",""
        Write-TerminatingError -Message "Office is not licensed because $errorDescriptionTrimmed"
}

#Function call that runs the script, it will be replaced in code with param and values while calling the script

Function Main {
    Param(
            [string]$Region,
            [string]$Stage,
            [string]$Endpoint
        )

    Office-Activation-Instance -Region $Region -Stage $Stage -Endpoint $Endpoint
}

Main -Region 'ap-northeast-1' -Stage 'prod' -Endpoint 'vpce-xxxxxxxxxxxxxxxxx-sw05a846.activation-license-manager.ap-northeast-1.vpce.amazonaws.com'

Office Software Protection Platform スクリプト (ospp.vbs)を使って手動でアクティベーションを行っています。
アクティベーション先に-Endpoint引数で指定したVPC Endpointを使っているのがわかりますね。

追記 : Route 53 Resolver Outbound Endpointを使わない方法

公式にサポートされる手順か未確認ではあるのですが、Route 53 Resolver Outbound Endpointを使わない構成も試したので参考にしてください。

終わりに

長くなりましたが以上となります。

Office入りAMIが提供されたことは非常にありがたいのですが、Microsoftライセンスを遵守する都合Visual Studioの時よりもさらにややこしい事態になっています。

Microsoftライセンスと日々格闘し苦しめられている私から見ても「確かに死ぬほどややこしいがMicrosoftライセンスを遵守するにはこうするしかない」ですし、AWSの苦労がしのばれます。
ライセンス自体の詳細については別記事で解説する予定ですが本当にこうするしかないのです...

構成の複雑さはMicrosoftがライセンスを改定しない限りどうしようもないため利用者は受け入れるしかありません。
ここまでしてOfficeを使いたいか、また、使うべきかを慎重に検討してください。

脚注

  1. Microsoft KMSサーバーが使うポート