winget install 済みのWindowsのゴールデンイメージを作る

検証用にwindows serverや、クライアント用のwindowsをダウンロードして
インストールしてみることはよくあると思います。

しかし、検証までの環境構築が面倒だったり、何度も検証する必要がある場合は、
windows image自体に機能を有効にしたり、アプリケーションをインストールしておくほうが後々楽
です。

今回はwingetをインストールしたうえで、指定の環境まで自動で構築する方法をここに書きます。

winget はストア以外で簡単にインストールする方法はなく、githubにあるパッケージをダウンロードして
手動でインストールする必要があります。

また、windows server2022ではまだ公式にはwingetをサポートしていないので、
バグがある可能性もあるので注意。

mkdir以外はすべて管理者権限で行う必要があります。

また、2023-11-07日現在は*windows serverのcui(windows server core)ではwingetがインストールできないので、あきらめてください*。
wingetが依存しているライブラリがUI周りのライブラリのため、しばらくは動かすことはできないです。
windows server 2025の次のOSぐらいかな？winget動かせるようになるのは。

## offlineイメージ展開用のディレクリ作成

```powershell
# offline image展開用のディレクトリを作っておく。
mkdir C:\Users\yourname\offline\image\path
```

## windowsimageのマウント

windows serverのisoファイルにあるsource/install.wimファイルをコピーしてデスクストップに置こう。
(install.esdの場合もある。その場合も以降の処理は同じ。)


wimファイルにはWindowsのイメージが圧縮されて保存されており、
専用のコマンドレットを使うことにより、展開や一覧表示、imageの変更が行える。

下記のコマンドレットでinstall.wimに圧縮されているwindows imageのリストを取得できる。

```powershell
Get-Windowsimage -ImagePath .\Desktop\install.wim
```

window serverのdatacenterやら、standardやらエディションごとにimageが圧縮されおり、
それぞれindexが降られている。

自分が使いたいimageのインデックスを下記のように指定してマウントしよう。

```powershell
Mount-WindowsImage -ImagePath .\Desktop\install.wim -Path C:\Users\yourname\offline\image\path -Index 1
```

## wingetのインストール


### wingetのダウンロード

[Githubのwinget](https://github.com/microsoft/winget-cli)のRealeaseからwingetとライセンスをダウンロードしておく。
msixbundleとLicense1.xmlというファイル名になっているやつです。

lisenceがない場合はインストールできても実行できないので必ずライセンスが必要です。

### wingetの依存関係

wingetの依存関係を解決するために取得取得する必要があります。

wingetと依存関係のファイルはそれぞれDownloadディレクトリに入れてください。

#### Microsoft.UI.Xaml

下記リンクのnugetのDonwload packageからnugetpackageをダウンロードします。
[nuget Microsoft.UI.Xaml](https://www.nuget.org/packages/Microsoft.UI.Xaml/2.7.0#readme-body-tab)

zipで配布されているので、解凍しておいてください。


#### VClibs

ここから対象のPCにあったアーキテクトのものをダウンロードしてください。

[VClibs](https://learn.microsoft.com/en-us/troubleshoot/developer/visualstudio/cpp/libraries/c-runtime-packages-desktop-bridge#how-to-install-and-update-desktop-framework-packages)

### wingetのインストール

下記のコマンドでmountしたwindows imageにインストールしてください。
powershell公式に書かれていませんが、msixbundleもAdd-ProvisionedAppPackageでインストールできます。

```powershell
Add-ProvisionedAppPackage `
    -Path C:\Users\yourname\offline\image\path `
    -PackagePath .\Downloads\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle `
    -DependencyPackagePath .\Downloads\microsoft.ui.xaml.2.7.0.nupkg\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx, .\Downloads\Microsoft.VCLibs.x64.14.00.Desktop.appx `
    -LicensePath .\Downloads\7bcb1a0ab33340daa57fa5b81faec616_License1.xml
```

これで管理者ユーザーにはwingetがインストールされます。
しかし、一般ユーザーでは使えないため、一般ユーザーでも使う場合は
そのユーザーごとに下記のようにインストールする必要があります。

```powershell
# 依存関係は上ですでにImageにインストールされているため、インストールする必要はない。
Add-AppxPackage .\Downloads\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
```

これは今現在は実際にwindowsを起動した後にwingetをインストールするという方法でしか
インストールできません。

## SetupComplete.cmdの設定

配布するwindows imageでみんな同じ処理をするものはここに書いておき、
個別対応が必要なものは、起動後に処理してもよい。

SetupComplete.cmdはsysprep後か初めてwindowsを立ち上げた時にのみ実行されるcmdファイルである。
%Windir%は(インストールしたディレクトリ):\windowsとなる。

応答ファイルは設定が困難な上にOsのバージョンに激しく依存するため、
よほど大規模に配布するか応答ファイルでないとできないことをさせる時以外は
応答ファイルを使うコストのほうが大きいと思う。

今回はC:\Users\yourname\offline\image\pathにイメージを展開しているので、

C:\Users\yourname\offline\image\path\Windows\となればよい。

errorlog

```cmd:%WinDir%\Setup\Scripts\SetupComplete.cmd
powershell.exe -ExecutionPolicy RemoteSigned -File %WinDir%\Setup\Scripts\init.ps1 >> %WinDir%\Setup\Scripts\SetupComplete.log 2>&1

@rem 必要ならば、ユーザーに見られないように削除しておく。
@rem 作業後に消えるのは応答ファイルと同じムーブなので特にセキュリティ的にまずい仕様ではない。
@rem rm %WinDir%\Setup\Scripts\init.ps1
```

```powershell:%WinDir%\Setup\Scripts\init.ps1
# wingetへのパスを通す。
$envvalue = Get-ItemPropertyValue "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" -Name Path
Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" -Name Path -Value "${envvalue};C:\Users\Administrator\AppData\Local\Microsoft\WindowsApps"

# ほかのレジストリを操作するにはハイブファイルをいじる必要があるが
# powershellにハイブファイルを編集する機能がないため難しく、手間のわりにメリットが少ない。

# Administratorはファイルの拡張子を表示
Set-ItemProperty -LiteralPath "HKLM:Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -Name "HideFileExt" -Value "0" -Force
# ファイルの拡張子の表示
Set-ItemProperty -LiteralPath "HKCU:Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -Name "HideFileExt" -Value "0" -Force

# Ctrl + Alt + Delでログインでなくて、普通にログインできるようにする。
Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name DisableCAD -Value 1
Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name DisableCAD -Value 1

# タッチパッドのスクロール方向をmacと一緒にする
# これはBootCampのセットアップを行ったときに有効に動く。
Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Enum\HID\VID_05AC&PID_0274&MI_02&Col01\7&26b02425&0&0000\Device Parameters" -Name FlipFlopWheel -Value 1

# Server Managerが勝手に立ち上がらないようにする。
# Get-ScheduledTask -TaskPath "\Microsoft\Windows\Server Manager\" -TaskName ServerManager
# Disable-ScheduledTask -TaskPath "\Microsoft\Windows\Server Manager\" -TaskName ServerManager

# その他やりたい作業があるならそれをする
# 初回起動時の場合はしばらく立ってから出ないと
# 実行できないなら、タスクスケジューラに1時間後みたいに登録するのもよい。

# powershellの実行ポリシー変更
Set-ExecutionPolicy RemoteSigned

# ex ユーザー作成 パスワードは全部情シス側で管理しておいて、
# パスワードを忘れたときの手間を防ぐため、常に同じにしておく。
# ただし、16桁以上と長く、部署が変わるか、退職するとパスワードが変更になる。
# もしくは管理者が使う一般ユーザーとして各PCにおいておくのもよい。
$username = "HelloName1"
$password = 'HelloHello'
$SecureString = ConvertTo-SecureString $password -AsPlainText -Force

New-LocalUser `
    -Name $username `
    -Password $SecureString `
    -Description "Description of this account." `
    -PasswordNeverExpires `
    -UserMayNotChangePassword

# usersグループに追加
# usersグループにいないと、ログインができない。
Add-LocalGroupMember -Group Users -Member $username

# Administratorsグループに入れる必要があるなら入れる。
# linuxのrootユーザー並みに強力な権限なので注意。
# Add-LocalGroupMember -Group Administrators -Member $username

# 特定のサービスの初期状態変更
# Set-Service ...

# windowsのDNS設定

# windowsのネットワーク設定
# 自分のIPアドレスはこれ固定で、default gatewayはこうとするなど。

# 特定のポートを解放しとくなど
# Set-NetFirewallProfile -Profile Domain,Public,Private 

# プロキシの設定
# etc...

# ネットワークがつながってから、

# アメリカ英語をインストールしておくとドキュメント、ヘルプを見るのが楽。
# Install-Language en-US

```

## イメージのセーブ

上までの設定をwimファイルに反映させて、unmountします。

```powershell
DisMount-WindowsImage -Path .\offline\ -save
```


ここからは作成したゴールデンイメージの使い方です。

## イメージの使い方

二通りある。インストールメディアにするか、
直接wimをボリュームに展開するか。

### インストールメディア

MSから配布されているisoファイルからwimを取り出して、
編集、そしてそれをインストールメディアとして使いたいなら次のようにする。

#### oscdimgコマンドのインストール

windows配布用のisoファイルを作るために必要。

さらに、oscdimgをインストールするために、windows ADKをインストールする必要がある(Windows SDKではないADK)。

昔はmsのサイトにごちゃごちゃと散らばっていたが、
今はwingetで簡単にインストールできる。

```powershell
winget install Microsoft.WindowsADK
winget install Microsoft.ADKPEAddon
winget install Microsoft.DeploymentToolkit
```

上記のコマンドを実行すると、
C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg
配下にoscdimgコマンドがインストールされるはずだ。

install.wimから解答した場合は、iso

```powershell

# 新しくisoファイル作成用のディレクトリを作る
# G:\ はmsから配布されたisoのインストールメディアへのマウントディレクトリ
mkdir C:\Users\yourname\iso
Copy-Item -Recurse G:\　-Destination C:\Users\yourname\iso\

# 新しいwimで上書き
Copy-Item .\Desktop\server_ja_JP.wim C:\Users\yourname\iso\source\install.wim

# マザーボードがBIOSならboot\etfsboot.com
# UEFIならefi\microsoft\boot\efisys.binを使う。
& "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe" -bC:\Users\yourname\iso\efi\microsoft\boot\efisys.bin  -m -o -u2 -udfver102 C:\Users\yourname\iso\ C:\Users\yourname\installer.iso
```

このisoファイルを各クライアントに配布するか、 USBに焼いて渡せばよい。

#### isoをusbに焼く

wingetから

```powershell
winget install Rufus.Rufus
```

後は Rufusの画面からUSBを焼けばよい。

### ボリュームに展開

デュアルブートを作ったり、ほかのPCのディスクを外して、展開するなら
こっちのほうが楽。
Hyper-Vで使う仮想ディスクに展開することもできる。

パーティションの区切り方、仮想ディスクの作り方は下記参照。
[windowsが起動するディスクの作成](https://zenn.dev/oto/articles/974ecf5912955b)

ほかのPCのディスクを外して云々は、他人のPCの復旧に役立つだろう。

あらかじめ、GUIのパーティションツールからパーティションを作っておき、
新たにNTFSのボリュームを作成しておくこと。

ここでは新しく作成したボリュームをE:とする

```powershell
# indexが1の場合
Expand-WindowsImage -ImagePath .\Desktop\init_server_ja_JP.wim -ApplyPath E:\ -Index 1

# マルチブートの場合
# 今現在動いているシステムのbcdbootを使えばよい。
# WindowsDriveLetterをEに置き換えて実行。
Start-Process "${WindowsDriveLetter}:\Windows\System32\bcdboot.exe" `
    -ArgumentList "${WindowsDriveLetter}:\Windows" `
    -Confirm `
    -Wait

# ほかのPCのディスクの場合
# そのほかのディスクにあらかじめブートセクタを区切っているとする。
# 勝手に外付けのディスクにあるブートセクタを探していい感じに修正してくれる。
# BootDriveLetterにほかのPCのディスクのドライブレターを入力
Start-Process "${WindowsDriveLetter}:\Windows\System32\bcdboot.exe" `
    -ArgumentList "${WindowsDriveLetter}:\Windows",/s,"${BootDriveLetter}:",/f,UEFI `
    -Confirm `
    -Wait

# ブートエントリに入っているボリューム一覧を取得
bcdedit

# bcdeditと実行したときのidentifyを使う
# descriptionを
bcdedit /set "{default}" description "Windows Server Test"
```

## まとめ

ネットだと古い情報が入り混じっており、覚えなくて良いことも結構書いてある。
2023年1月現在だと上記のやり方が一番シンプルかつ汎用性が効き、管理も楽かな。