EC2 Image BuilderでWindows Server OSを日本語化するコンポーネントを作ってみた

2023.04.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

しばたです。

EC2 Image BuilderではAWSが提供する管理対象イメージをオリジンにすると「自動バージョンニングオプション」により最新バージョンのOSを使い続けることができ非常に便利です。

だだ、Windows Server OSの場合AWSから提供されるのは英語版のみとなり我々日本人にとっては少し不便です。
そこでWindows Server OSを日本語化するコンポーネントを作ってみました。

本記事では作ったコンポーネントの解説をしていきます。

作ったコンポーネント

以下が作ったコンポーネント定義です。

Windows Server OSはバージョン毎で日本語化の方法が異なり、今回公開しているコードはWindows Server 2019専用となります。
(とはいえ他のバージョンでも近い手順で日本語化可能ですので機会があれば別途作ってみようと思います)

コンポーネント定義 (Ver.1.1)

name: windows-server-2019-setup-japanese-ui
description: Install Japanese Language pack and Update UI configurations.
schemaVersion: 1.0

phases:
  - name: build
    steps:
      - name: InstallLanguagePackStep
        action: ExecutePowerShell
        inputs:
          commands:
            - |
              # Download Language pack ISO.
              $langPachUri = 'https://software-download.microsoft.com/download/pr/17763.1.180914-1434.rs5_release_SERVERLANGPACKDVD_OEM_MULTI.iso' # for Windows Server 2019
              $langPackPath = Join-Path -Path $env:TEMP 'langpack.iso'
              try {
                  $ProgressPreference = 'SilentlyContinue'
                  Write-Output 'Download language pack ISO file...'
                  Invoke-WebRequest -Uri $langPachUri -OutFile $langPackPath
              } finally {
                  $ProgressPreference = 'Continue'
              }

              try {
                  # Mount ISO, and get cab file path
                  Write-Output 'Mount language pack ISO file...'
                  $result = Mount-DiskImage -ImagePath $langPackPath -PassThru
                  if (-not $result.Attached) {
                      Write-Error 'Failed to mount language pack ISO.'
                      return 
                  } 
                  $jpCabPath = "$(($result | Get-Volume).DriveLetter):\x64\langpacks\Microsoft-Windows-Server-Language-Pack_x64_ja-jp.cab"
                  # Install language pack 
                  Write-Output 'Insall Language pack cab file...'
                  Write-Output ('  {0}' -f $jpCabPath)
                  Add-WindowsPackage -Online -PackagePath $jpCabPath -LogPath (Join-Path -Path $env:TEMP 'dism.log')
              } finally {
                  # Unmount ISO
                  Write-Output 'Unmount Language pack ISO file...'
                  Dismount-DiskImage -ImagePath $langPackPath > $null
                  # Remove ISO
                  if (Test-Path -LiteralPath $langPackPath) {
                      Write-Output 'Remove Language pack ISO file...'
                      Remove-Item -LiteralPath $langPackPath
                  }
              }

              # Set UI Language Settings
              Write-Output 'Update language list...'
              Set-WinUserLanguageList -LanguageList ja-JP, en-US -Force

              # Set System local
              Write-Output 'Update system locale...'
              Set-WinSystemLocale -SystemLocale ja-JP
      - name: RebootAfterLanguagePackInstalled
        action: Reboot
        inputs:
          delaySeconds: 10
      - name: UpdateUIConfigurationsStep
        action: ExecutePowerShell
        inputs:
          commands:
            - |
              # Update UI Language Settings
              Write-Output 'Update UI language...'
              Set-WinUILanguageOverride -Language ja-JP
              Write-Output 'Update Input method...'
              Set-WinDefaultInputMethodOverride -InputTip '0411:00000411'

              # Set Location settings
              Write-Output 'Set Location...'
              Set-WinHomeLocation -GeoId 122
              Set-WinCultureFromLanguageListOptOut -OptOut $False

              # Set Timezone
              Write-Output 'Set Timezone...'
              Set-TimeZone -Id 'Tokyo Standard Time'
      - name: UpdateSysprepCconfigsStep
        action: ExecutePowerShell
        inputs:
          commands:
            - |
              $xmlPath = 'C:\ProgramData\Amazon\EC2-Windows\Launch\Sysprep\Unattend.xml'
              if (Test-Path -LiteralPath $xmlPath) {
                  # Backup Unattend.xml 
                  Copy-Item -LiteralPath $xmlPath -Destination "${xmlPath}.orig" -Force
                  # Update Unattend.xml
                  Write-Output 'Update Unattend.xml...'
                  $xmlDoc = [ xml](Get-Content -LiteralPath $xmlPath -Raw)
                  # Update Timezone
                  $xmlDoc.unattend.settings.component | Where-Object { $_.name -eq 'Microsoft-Windows-Shell-Setup' } | ForEach-Object {
                      $_.TimeZone = 'Tokyo Standard Time'
                  }
                  # Update Launguage, Locale
                  $xmlDoc.unattend.settings.component | Where-Object { $_.name -eq 'Microsoft-Windows-International-Core' } | ForEach-Object {
                      $_.InputLocale  = 'ja-JP'
                      $_.SystemLocale = 'ja-JP'
                      $_.UILanguage = 'ja-JP'
                      $_.UserLocale = 'ja-JP'
                  }
                  $xmlDoc.Save($xmlPath)
              }
      - name: ApplyWindowsUpdateStep
        action: UpdateOS

コードの解説

実際にコンポーネントを試す前にコードの解説を簡単にしておきます。

1. 言語パックのダウンロードとインストール (InstallLanguagePackStep)

このコンポーネントは5つのステップから構成されており、最初にOS毎の言語パックのダウンロードとインストールを行っています。
基本的な手順は弊社林の記事とムッシュのブログを参考にしています。

やっていることはOS毎の言語パックをダウンロードして追加したい言語(ここでは日本語)のCABファイルをインストールしているだけです。
林の記事ではlpksetup.exeコマンドを使ってCABファイルをインストールしていますが、スクリプトで扱うにはAdd-WindowsPackageコマンド(dism.exeと同等)を使う方が取り回しが楽だったのでそこは変えています。

そしてCABファイルインストール後にシステムロケールの変更をしています。

余談

今回はWindows Server 2019の言語パック(ISOファイル)を都度ダウンロードする様にしていますが、言語パックのサイズは約2GBあるためネットワーク転送量を気にするのであれば事前にS3などにCABファイルだけ配置して直接ダウンロードする様にすると良いでしょう。

2. 再起動 (RebootAfterLanguagePackInstalled)

追加した日本語にUIを変更する(具体的にはSet-WinUILanguageOverrideコマンドの実行)には一度再起動する必要があったため、Image BuilderのRebootアクションを使ってOSを再起動します。
併せてこの再起動でシステムロケールの変更も反映させています。

3. 言語設定の変更 (UpdateUIConfigurationsStep)

再起動後はUI言語の変更(Set-WinUILanguageOverride)をはじめ各種設定変更コマンドを実行しており、ざっくり

  • UI言語を日本語に変更
  • 「地域」のを日本に変更
  • タイムゾーンをJSTに変更

をやっています。
利用シナリオによっては他の設定も変更した方が良いかもしれませんが、その場合は適宜コードを改変してください。

このステップでOSの設定変更は完了です。

4. Sysprep設定の変更 (UpdateSysprepCconfigsStep)

Image BuilderでAMIを作成する際はSysprepによる一般化が行われます。

Windows Server AMIのデフォルトではSysprep後の初回起動(特殊化/OOBE)時に言語設定およびタイムゾーンを変更する初期設定が仕込まれているためこの設定も変えてやる必要があります。

Sysprepの初期設定はUnattend.xmlファイルに記載されているため、PowerShellで変更が必要な各タグの値を更新しています。
一応念のために変更前のバックアップを取得しています。

5. Windows Updateの実施 (ApplyWindowsUpdateStep)

言語パックをインストールした後はWindows Updateをした方が良いのでAWS組み込みアクションUpdateOSを追加しています。

この作業により言語パックファイル作成時点以降の更新に対する言語リソースの適用などを行います。

試してみる

ここからは実際にこのコンポーネントを試していきます。
今回は私の検証AWSアカウントの東京リージョンを使用しました。

1. コンポーネントの作成

まずはマネジメントコンソールから新規にコンポーネントを作成します。

タイプは「ビルド」でwindows-server-2019-setup-japanese-uiという名前にしています。 コンポーネントバージョンは何でも構わないのでとりあえず1.0.0としています。
そして、前述の通りWindows Server 2019専用なので互換性のあるOSバージョンはWindows Serer 2019のみとしてます。

コンポーネント定義は前掲のコードをそのまま転記します。

その他設定は必要があれば行ってください。

エラー無く作成できればOKです。

2. レシピの作成

あとは通常の流れ通りレシピを作りパイプラインを実行していく形となります。

今回はmy-japanese-windows-imageという名前のレシピを作ってwindows-server-2019-setup-japanese-uiコンポーネントを適用する設定にします。

その他の設定は基本デフォルトのままです。

3. パイプラインの作成

このレシピから新しくパイプラインを作成します。
パイプラインの名前はbuild-my-japanese-windows-imageにしています。

インフラストラクチャ設定およびディストリビューション設定は新規に作成するか環境に応じたものにしてください。
今回のコンポーネントを利用するのに必要な前提条件は

  • ビルド時に要インターネットアクセス (ISOファイルのダウンロードに必要)

だけです。

4. パイプラインの実行

これで準備ができたので、最後にパイプラインを実行して作成されるイメージがちゃんと日本語化されているか確認していきます。

実行結果はこんな感じで今回はami-049fe00f6b49c7995というAMIが作成されました。
コンポーネントの適用時間はだいたい12分くらいでほとんどがAdd-WindowsPackageの処理時間です。

このAMIから確認用のEC2インスタンスを起動してやると、いい感じに日本語化されていることが確認できました。

代替手段 : カスタムAMI + カスケードパイプライン

今回は英語OSを日本語化するアプローチを採りましたが、通常のEC2においてはAWS公式で日本語版Windows Server AMIが提供されておりImage Builderにおいても「カスタムAMI」という形で利用可能です。

ただ、Image BuilderでカスタムAMIをオリジンにした場合「自動バージョンニングオプション」が使えません。
このためAWS公式AMIの新しいバージョンが提供される都度Image Builderのレシピのバージョンを更新して新しいAMIをオリジンにする必要があります。

レシピのバージョン戦略次第ではあるもののこれはちょっと不便です。

この不便さを解消する手段としてカスケードパイプラインを構築する方法があります。
以下の様に

  1. AWS公式日本語AMIをオリジンとするベースイメージを作るパイプライン
    • これは都度レシピのバージョンを更新する必要あり
  2. 上のパイプラインで作成されたイメージをオリジンとするパイプライン
    • これは「自動バージョンニングオプション」が使える

という2つのパイプライン構成にしてやれば、2.のパイプラインでは自動バージョニングオプションが使えるため「あたかも常に最新の日本語Windows Server AMIを使える」かの様に扱えます。

とはいえ1.のパイプラインの更新は利用者が自分で行う必要があり、作業を自動化するには自分で仕組みを作り込む必要があります。

要件に応じて「コンポーネントで都度日本語化する」か「カスケードパイプラインを構築する」かを決めると良いでしょう。

最後に

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

本記事の内容が皆さんの役に立てば幸いです。

追記

Windows Server 2022に対応した新しいバージョンを作りました。