Azure MarketPlace のイメージから直接デプロイした VM (AVD) を 全自動で日本語化する

# はじめに
現在は、Azure Virtual Desktop (AVD) で、ホストプールと仮想マシンを作成する際に、どれだけシンプルな画面からデプロイさせられるか？ を研究しています。
その際には、いくつかのハードルがあったのですが、そこで得られたノウハウを記事にしていこうと思っています。

今回は、Azure Market Place で提供されている マルチセッションの OSイメージを選択して VM をデプロイした際に、全自動で イメージが日本語化され、展開直後から ユーザーが利用できるようにする方法を共有したいと思います。

なお、AVD用のマルチセッションの OSイメージは 英語版ですが、プレインストールされている Office 365 のアプリも英語版です。
OS の日本語化に加えて、Office の日本語化も合わせて行う必要があって、苦労した点です。

本件を確立させる前の研究段階で、Azure VM をデプロイ時に全自動で日本語化 に取り組んだ際の記事も掲載しています。併せて参照いただけると幸いです。

https://qiita.com/carol0226/items/c1b47e953f98c0646e91

::: note info
基本的には、上記の記事をベースとして、以下の３点を追加で対応させた進化版が 本記事の内容となっています。
- Office365アプリの日本語化を追加
- 処理中のログ出力に対応させた
- AVDのデプロイ時に処理が可能なことを検証済み（50回以上のデプロイ実績あり）
:::

---

# 当スクリプトを作成することにした経緯
AVD のような VDI の構築・運用・管理には ゴールデンイメージの作成と管理がつきものです。
このゴールデンイメージを作り始めると 数多くのバージョンが生み出されて 管理が煩雑になります。
従来から、ゴールデンイメージを使わない前提での VDIサービスの提供を考えられないか・・・と思っていたので、このたび 本テーマに取り込みました。

---

# この仕組みのメリット
- コマンドが羅列されただけのスクリプトで構成（条件分岐や繰り返し処理が無く、全体像を理解しやすい）
- スクリプト内にログオンアカウントの記載が不要
- Windows10/11 共通で使用できる
- GUI操作で日本語化した時の結果と比べて 遜色ない仕上がり
- Market Place で提供されている AVD用マルチセッションイメージから自由に選んでデプロイできる
- デプロイ時の進捗を 共有フォルダにログとして出力します。

---

# 前提事項
この記事では、オンプレミスのドメインコントローラー に参加する方式を前提としています。

以下の記事を参照して、No.1 ～ No.7 までを行い、AVD がデプロイされている必要があります。。

https://qiita.com/carol0226/items/7318fdd7aff780f5177c

No.8 が本記事です。
No.9 以降の実施で より 高度な AVD を構成できるような構成になっています。

---

# VM イメージの要件
- OSは、Windows 10 20H2以降と、Windows 11 のみ対応（その他の OS では不可）
- 処理時間は、D2sV3 の VM で、合計 約 30 分～ 60 分程度 掛かります。
（内訳）
　OS言語パック導入で 約 10 分
　Office日本語化で 約 10 分
　Windows Updateで 10 分～ 40 分（元のイメージと更新量によって変動）
- スクリプトは、セッションホストのVMデプロイ時に カスタム構成スクリプト を使って実行する

---

# カスタム構成スクリプトの内容
以下のスクリプトを ストレージアカウント上に BLOB として保存しておき、AVDデプロイ時に カスタム構成スクリプトURL を指定することで実行できます。
スクリプトの配置方法は 後述しています。

````
# ログのパス設定
$FILENAME = $env:Computername
$log = "\\[共有フォルダのパス]\DeployLog\" + $FILENAME + ".log"

# 開始
echo "Delpoy Start" >> $log

# タイムゾーン
echo "#1 Time Zone" >> $log
Set-TimeZone -Id "Tokyo Standard Time" >> $log
date >> $log
Get-TimeZone >> $log

# 言語パックインストール
echo "#2 OS LanguagePack Install" >> $log
date >> $log
Install-Language ja-jp >> $log

# システムロケール
echo "#3 System Locale" >> $log
date >> $log
Set-WinSystemLocale -SystemLocale ja-JP >> $log
Get-WinSystemLocale  >> $log

# 地域設定
echo "#4 Locale Setting" >> $log
date >> $log
Set-Culture -CultureInfo ja-JP >> $log
Get-Culture >> $log

# システム優先言語
echo "#5 System Preferred UI Language" >> $log
date >> $log
Set-SystemPreferredUILanguage ja-JP >> $log
Get-SystemPreferredUILanguage >> $log

# ロケーション
echo "#6 Location" >> $log
date >> $log
Set-WinHomeLocation -GeoId 0x7a >> $log
Get-WinHomeLocation >> $log

# 言語リスト
echo "#7 Language List" >> $log
date >> $log
$mylang = New-WinUserLanguageList -Language "ja-JP"
Set-WinUserLanguageList $mylang -Force >> $log
Get-WinUserLanguageList >> $log

# ようこそ画面をコピー
echo "#8 Welcome Display Copy" >> $log
date >> $log
Install-Language ja-jp -CopyTosettings >> $log

# Office言語パック（日本語）インストール
echo "#9 Office Language Pack (ja-jp) Install" >> $log
date >> $log
\\[共有フォルダのパス]\ODT\setup.exe /configure \\[共有フォルダのパス]\ODT\OfficeLanguagePackOnlySilent.xml

# Windows Update ※AutoRebootが掛かると 後続バッチが動作しないので必ず最後の処理にすること
echo "#10 Windows Update" >> $log
date >> $log
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force >> $log
Install-Module PSWindowsUpdate -Force >> $log
Get-WindowsUpdate -AcceptAll -Install -AutoReboot >> $log

# 終了時刻の記録
echo "Deploy Finished" >> $log
date >> $log
````

---

# 注意事項
そこそこシンプルなスクリプトだと思いますが、各コマンドの順序やパラメーターに重要な意味があります。改変すると期待通りに動作しない恐れがあります。<br>完成までに相当な検証工数を掛けてバランス調整していますので、基本的には 上記の内容で そのままご使用ください。<br>Windows Update と 再起動の部分を省いて利用すると 約20分で完了しますが、この場合は 初回ログオン時に完全に日本語化されているように見えますが、OS再起動後に RDP ができなくなります。本事象を Microsoftサポートに問い合わせたところ、Windows Update の実行が必須であるとのアドバイスをうけ、Windows Update の最適な処理を研究して追加し さらに検証と調整を重ねて完成に至っています。

【MicrosoftサポートBlog：言語パックインストール時のご留意点】
https://jpwinsup.github.io/blog/2022/06/22/UserInterfaceAndApps/LanguageSupport_IME/lp-consideration/

---

# スクリプトなどの仕込み作業（初回のみ）

## カスタム構成スクリプトの仕込み
0. 前章に記載していた カスタム構成スクリプト をテキストエディタへコピー＆ペーストして、ファイル (拡張子は .ps1) として 任意のフォルダに保存します。

0. Azure上に ストレージアカウント を新規作成します。既に 運用中の ストレージアカウント でも構いません。
・リソースグループ：任意
・ストレージアカウント名：任意（グローバルでユニーク）<br>　※サブスク利用者で共用して使う場合、ソート順で一番上に表示されるような名称にすることをお勧めします。
・冗長性：任意<br>　※ローカル冗長ストレージ(LRS) で十分　※一番安価なコストで利用できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/98f2d8ae-da9d-198a-7d6a-42e4ad24d5c0.png)
0. ストレージアカウント がデプロイ出来たら、「ストレージブラウザー」－「BLOBコンテナー」を選択して、「＋コンテナーを追加する」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/24e88f12-9f4d-f2ec-6cab-6a374b259c6b.png)
0. 判りやすい 任意 のコンテナ名を付与して、「作成」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/4af281c6-38f1-d461-4a3e-f3cd1c84b87f.png)
0. コンテナーが作成されたら、そのコンテナ名をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/45112253-58af-eb3f-0fcb-c1d2588621c3.png)
0. 下線のとおり、コンテナ名が表示されている事を確認して、「アップロード」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/2fb34d63-9cc5-6b83-2421-85df836ed6e5.png)
0. BLOBのアップロード画面で「ファイルの参照」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/f99b772f-5e1c-7840-80dd-69a6aeb92b2c.png)
0. ファイル選択ダイアログで、あらかじめ保存しておいた スクリプトを選択して「開く」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/c3c98317-85a5-91b3-1594-bfbf633403a5.png)
0. 下線部の通りに ファイルが選択されている事を確認して「アップロード」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/75938541-88e0-78a6-2856-57aaadfa335a.png)
0. アップロードに成功すると、下線部の通りに 表示される。これで 事前準備は OK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/fa3ca032-bd24-5a44-7630-ff6ad3bcdf6c.png)

---

## Office 365 アプリの日本語化（Office展開ツール）の仕込み
Office365アプリ を全自動スクリプトで日本語化する方法ですが、試行錯誤した結果、Office展開ツール(ODT) を採用することにしました。

任意の共有フォルダを作成して、この章の内容を構成してください（AD DS 上に作成するのが楽です）

VMイメージに Officeが導入されている時だけ そのプロダクトを日本語化させたかったのですが、Product ID に Office製品のバージョンを指定してしまうと Officeが入っていないVMイメージにまで Officeが導入されてしまいます。

試行錯誤した結果、下記のように LanguagePack のみを指定することで、Officeが導入されていれば日本語化し、無ければ無視・・・という一番ベストな形で動作することが判りました。
ここで使用する Configファイル(xml) について、最終的に 以下の内容になりました。

````
<Configuration>
  <Add>
    <Product ID="LanguagePack">
      <Language ID="ja-jp" />
    </Product>
  </Add>
  <Display Level="None" AcceptEULA="TRUE" />
</Configuration>
````

[Office展開ツール（setup.exe）](https://learn.microsoft.com/ja-jp/deployoffice/overview-office-deployment-tool#download-the-office-deployment-tool "Office展開ツールダウンロード")と、上記の構成ファイル（メモ帳に転記して OfficeLanguagePackOnlySilent.xml というファイル名で保存）を下図の通り共有フォルダに保存しておき。AVDをデプロイする際の カスタム構成スクリプト から実行させることで、連係動作するようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/21b8f194-1207-a7cc-fb4b-2a85dea59a49.png)

フォルダのアクセス許可は、以下の通り（Everyone は、読み取りと実行 のみにする）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/1e38adab-569c-a4b8-9588-c28cb13c8238.png)

フォルダの共有アクセス許可は、以下の通り（Everyone フルコントロールで問題ない）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/6d79b837-b063-5e3f-5e26-eb8601b47ef2.png)

カスタム構成スクリプトから起動する際の コマンド は、以下の通りです。
````
\\[共有フォルダのパス]\ODT\setup.exe /configure \\[共有フォルダのパス]\ODT\OfficeLanguagePackOnlySilent.xml
````

https://learn.microsoft.com/ja-jp/deployoffice/overview-office-deployment-tool

---

## ログフォルダの仕込み
スクリプトの実行状態を把握するために、ログを保存できる仕組みを用意します。
スクリプトの実行中にエラーになった場合などに活用する事ができますし、デプロイ中に待っている際にも、ログをみることで、どこまで進捗しているのかが判りやすくなります。

DeployLog という名称の 共有フォルダ を作成しておきます。
※AD DS 上に作成してしまうのが楽です。

共有アクセス権は、以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/73bf4992-383d-8c4c-4b80-d96b25a90fe1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/1b5c1876-621a-70b7-c758-8b75d73b2c45.png)

あとは、カスタム構成スクリプトが実行される際に リアルタイムで ホスト名ごとのログが追記されていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/97f53831-d8f6-9f97-059b-c8f7bc6ab734.png)

ログは、以下のような内容で出力されます（本サイトで見やすいように 若干成形しています）

````
Delpoy Start
#1 Time Zone
Monday, August 28, 2023 3:40:45 PM

Id                         : Tokyo Standard Time
DisplayName                : (UTC+09:00) Osaka, Sapporo, Tokyo
StandardName               : Tokyo Standard Time
DaylightName               : Tokyo Daylight Time
BaseUtcOffset              : 09:00:00
SupportsDaylightSavingTime : False

#2 OS LanguagePack Install
Monday, August 28, 2023 3:40:45 PM

Language Language Packs  Language Features                                               -------- --------------  -----------------                                               ja-JP    LpCab           BasicTyping, Handwriting, Speech, TextToSpeech, OCR             

#3 System Locale
Monday, August 28, 2023 4:14:52 PM

LCID             Name             DisplayName                                            ----             ----             -----------                                            1033             en-US            English (United States)                                

#4 Locale Setting
Monday, August 28, 2023 4:14:52 PM

LCID             Name             DisplayName                                            
----             ----             -----------                                            1033             en-US            English (United States)                                

#5 System Preferred UI Language
Monday, August 28, 2023 4:14:53 PM

ja-JP

#6 Location
Monday, August 28, 2023 4:14:53 PM

GeoId HomeLocation
----- ------------
  122 Japan       

#7 Language List
Monday, August 28, 2023 4:14:53 PM

LanguageTag     : ja
Autonym         : 日本語
EnglishName     : Japanese
LocalizedName   : Japanese
ScriptName      : Japanese
InputMethodTips : {0411:{03B5835F-F03C-411B-9CE2-AA23E1171E36}{A76C93D9-5523-4E90-AAFA-4DB112F9AC76}}
Spellchecking   : True
Handwriting     : True

#8 Welcome Display Copy
Monday, August 28, 2023 4:14:54 PM

Language Language Packs  Language Features                                               -------- --------------  -----------------                                               ja-JP    LpCab           BasicTyping, Handwriting, Speech, TextToSpeech, OCR             

#9 Office Language Pack (ja-jp) Install
Monday, August 28, 2023 4:15:02 PM

#10 Windows Update
Monday, August 28, 2023 4:19:50 PM

Name                           Version          Source           Summary                 ----                           -------          ------           -------                 nuget                          2.8.5.208        https://onege... NuGet provider for the OneGet meta-package manager

X ComputerName Result     KB          Size Title                                         - ------------ ------     --          ---- -----                                         1 AVD082803... Accepted   KB890830    57MB 悪意のあるソフトウェアの削除ツール x64 - v5.116 (KB890830)                                   
1 AVD082803... Accepted   KB5029718   60MB .NET Framework 3.5 用の2023-08累積的な更新プログラムのプレビューと x64 (KB5029718) のWindows 1...
1 AVD082803... Accepted   KB5007651   18MB Windows Security platform マルウェア対策プラットフォームの更新プログラム - KB5007651 (Version 1....
1 AVD082803... Accepted   KB2267602  137MB Microsoft Defender Antivirus のセキュリティ インテリジェンス更新プログラム - KB2267602 (バージョン 1...
1 AVD082803... Accepted   KB5029263  128GB 2023-08 x64 ベース システム用 Windows 11 Version 22H2 の累積更新プログラム (KB5029263)        
2 AVD082803... Downloaded KB890830    57MB 悪意のあるソフトウェアの削除ツール x64 - v5.116 (KB890830)                                   
2 AVD082803... Downloaded KB5029718   60MB .NET Framework 3.5 用の2023-08累積的な更新プログラムのプレビューと x64 (KB5029718) のWindows 1...
2 AVD082803... Downloaded KB5007651   18MB Windows Security platform マルウェア対策プラットフォームの更新プログラム - KB5007651 (Version 1....
2 AVD082803... Downloaded KB2267602  137MB Microsoft Defender Antivirus のセキュリティ インテリジェンス更新プログラム - KB2267602 (バージョン 1...
2 AVD082803... Downloaded KB5029263  128GB 2023-08 x64 ベース システム用 Windows 11 Version 22H2 の累積更新プログラム (KB5029263)        
3 AVD082803... Installed  KB890830    57MB 悪意のあるソフトウェアの削除ツール x64 - v5.116 (KB890830)                                   
3 AVD082803... Installed  KB5029718   60MB .NET Framework 3.5 用の2023-08累積的な更新プログラムのプレビューと x64 (KB5029718) のWindows 1...
3 AVD082803... Installed  KB5007651   18MB Windows Security platform マルウェア対策プラットフォームの更新プログラム - KB5007651 (Version 1....
3 AVD082803... Installed  KB2267602  137MB Microsoft Defender Antivirus のセキュリティ インテリジェンス更新プログラム - KB2267602 (バージョン 1...
3 AVD082803... Installed  KB5029263  128GB 2023-08 x64 ベース システム用 Windows 11 Version 22H2 の累積更新プログラム (KB5029263)        


Deploy Finished

Monday, August 28, 2023 4:27:29 PM
````

---

# AVD デプロイ手順
0. Azure Virtual Desktop の「ホストプールを作成する」にて「仮想マシン」を作成する際に、カスタム構成スクリプトのURL を指定します（前章でBLOBに保存したスクリプトのURLを設定する）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/5215bb0b-e29a-46d2-d3bf-87e182d08c74.png)

0. デプロイが始まると、以下の画面になり、約60分程度 待つと処理が終了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/dd3c6164-0a83-696a-8cac-5be1b31141f4.png)

# ユーザーの割り当て
デプロイが終わったら、以下の記事を参照して 作成された アプリケーショングループ にユーザーを割り当てます。

https://qiita.com/carol0226/items/1115fb9f6f13b03dfa8c

---

# 初回サインイン
下図のように、初めから 日本語化 された状態で サインイン ができます。
※プレインストール済みの Officeアプリを起動したところ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1619637/8a67b735-7df9-bbfe-1353-ab952024097a.png)

# 参考にしたサイト
**【言語パックの管理に便利な PowerShell コマンドレットのご紹介】**
https://jpwinsup.github.io/blog/2023/03/06/UserInterfaceAndApps/LanguageSupport_IME/InstallLanguage/
→　このサイトを発見したことが この取り組みを実施する きっかけ になりました。しかしながら このコマンドだけだと VMデプロイ時に組み込むと RDP できなくなったり 不具合に見舞われてしまい、成功パターンを作り上げるために こんなに苦労するとは思っていませんでした。
※ブログ記事には、下記の「ご留意点」を実施しなくても OK と書かれてはいるのですけどね。 

**【MicrosoftサポートBlog：言語パックインストール時のご留意点】**
https://jpwinsup.github.io/blog/2022/06/22/UserInterfaceAndApps/LanguageSupport_IME/lp-consideration/
→　Microsoftサポート に問合せして、上記の記事のアドバイスを受けられなかったら 完成することはできず、ずっとハマったままだったかもしれません。大変参考になりました。

**【PowerShell】Windows Updteを自動化**
https://boonx4m312s.hatenablog.com/entry/2023/02/02/180000
→　Windows Update を コマンドで全自動化する方法は、ネットで調べると いくつかの方法がみつかるのですが、VM デプロイ時に組み込もうとすると、うまく動作させられず なかなか良い方法がありませんでした。そんな中で この記事で紹介されていた NuGetプロバイダー インストール時の一時中断を回避する方法が決め手となり、採用に至りました。
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
なお、バージョン(2.8.5.201) を固定で指定している箇所は、今後 永続的に使えるかどうかが定かではなく ちょっと気持ち悪い気がしており 改変の余地があると考えています。もっと良い方法をご存じの方がいたら、コメント欄で教えてください。

**【Azure コラム 第8回：Windows VM 全自動日本語化】**
https://www.intellilink.co.jp/column/ms/2022/020800.aspx
→　弊社の原田さんの記事です。初めは この記事をベースに 日本語化 を試行していたのですが、MSサポートブログで 2022年8月から Install-languageコマンドが提供されていたことを知ってから、もっとシンプル化できないか？ という思いが生じたため カスタマイズを繰り返して 完成形までこぎつけました。システムロケールや地域、言語バーなどの設定は このサイトを参考にしながら コマンドリファレンスや その他の日本語化のサイトなどを比較検討して組み立てを行っています。

**【Windows でのカスタムのスクリプト拡張機能】**
https://learn.microsoft.com/ja-jp/azure/virtual-machines/extensions/custom-script-windows#tips
→　このサイトに、カスタムスクリプト内で「再起動を行わないように」との注意書きの記載があり、スクリプトの検討時に参考になりました。

**【私の記事：手動で Azure VM の日本語化】**
https://qiita.com/carol0226/items/8cc48bb262bf81dfc9df
→　スクリプトではなく、GUI操作 で日本語化する場合は、この手順になります。基本的には この GUI操作 と同じようなことを、本記事では スクリプトで実装しています。

**【AVD ARM 日本語化イメージ作成手順について - Windows 11】**
<2024/8/8 追記>
この記事の内容を マージする必要があると思って、記録しています。
https://jpwinsup.github.io/blog/2024/08/30/RemoteDesktopService/AVD/AVD-ARM-Japanese-image-windows11/

# 検証済みイメージ
- Windows 10 Enterprise multi-session, Version-21h2
- Windows 10 Enterprise multi-session, Version-21h2 - Gen2
- Windows 10 Enterprise multi-session, Version-21h2 + Microsoft 365 Apps
- Windows 10 Enterprise multi-session, Version-21h2 + Microsoft 365 Apps - Gen2
- Windows 10 Enterprise multi-session, Version-22h2
- Windows 10 Enterprise multi-session, Version-22h2 - Gen2
- Windows 10 Enterprise multi-session, Version-22h2 + Microsoft 365 Apps
- Windows 10 Enterprise multi-session, Version-22h2 + Microsoft 365 Apps - Gen2
- Windows 11 Enterprise multi-session, Version-21h2
- Windows 11 Enterprise multi-session, Version-21h2 + Microsoft 365 Apps
- Windows 11 Enterprise multi-session, Version-22h2
- Windows 11 Enterprise multi-session, Version-22h2 + Microsoft 365 Apps

※Windows 10 Pro 20H2 以降であれば 仕様上は動作するはず。
　それより古い（2004以前）バージョンは、コマンドの互換性が無くて動作しません。
　Windows Server OS もコマンドの互換性が無くて動作しません。