Wingetを活用したアプリの一括オフラインインストール

# やりたいこと
- **オフライン環境**のWindows11数十台に複数のアプリを一括インストールしたい
- このとき可能な限り手作業は減らしたい
  - サイレントインストールで行いたい
  - 必要なオプション等を可能な限り自動で取得したい

:::note info
## オンラインでインストールできる場合
　例として次の10種のアプリをインストールするとき、オンライン環境ならばパッケージマネージャ「winget」を用いて、以下のように `winget install` のコマンドで一括インストールできる。

``` .ps1
winget install --silent --scope machine --id Google.JapaneseIME
winget install --silent --scope machine --id Google.Chrome
winget install --silent --scope machine --id 7zip.7zip
winget install --silent --scope machine --id voidtools.Everything
winget install --silent --scope machine --id Adobe.Acrobat.Reader.64-bit
winget install --silent --scope machine --id PDFsam.PDFsam
winget install --silent --id TrackerSoftware.PDF-XChangeEditor
winget install --silent --scope machine --id Microsoft.VisualStudioCode
winget install --silent --id DuongDieuPhap.ImageGlass
winget install --silent --scope machine --id VideoLAN.VLC
```
:::

# 方針
0. オンライン環境でWindowsのパッケージマネージャ「winget」を用いて、インストーラおよびマニフェストをダウンロード
0. ダウンロードしたインストーラおよびマニフェストをインストール対象にコピー
0. マニフェストから必要なオプションを抽出してインストールコマンドを出力
0. 出力したインストールコマンドを必要に応じて微調整してから実行


# wingetでインストーラをダウンロードする
　wingetのdownloadコマンドでインストーラおよびマニフェストをダウンロードする。デフォルトでは [ダウンロード] フォルダへアプリ名のフォルダ単位で保存される。
　なお、依存関係あるものもサブフォルダに自動的にダウンロードされる。
 
:::note warn
　このときに、ダウンロードするパソコンとインストールするパソコンについてプラットフォームやアーキテクチャ等が異なる場合には、インストールするパソコンに合わせた `--platform` や `--architecture` のオプションを指定することで最適なインストーラをダウンロードできる。細かいオプションについては[公式サイト](https://learn.microsoft.com/ja-jp/windows/package-manager/winget/download)を参照。
:::

``` downloader.ps1
winget download --scope machine --id Google.JapaneseIME
winget download --scope machine --id Google.Chrome
winget download --scope machine --id 7zip.7zip
winget download --scope machine --id voidtools.Everything
winget download --scope machine --id Adobe.Acrobat.Reader.64-bit
winget download --scope machine --id PDFsam.PDFsam
winget download --id TrackerSoftware.PDF-XChangeEditor
winget download --scope machine --id Microsoft.VisualStudioCode
winget download  --id DuongDieuPhap.ImageGlass
winget download --scope machine --id VideoLAN.VLC
```


# ダウンロードしたものをインストール対象にコピーする
　ダウンロードしたインストーラおよびマニフェストをフォルダごとインストール対象のパソコンにコピーする。
　今回は例として、インストール対象にAdministratorでログインして、[デスクトップ] > [installer] に各種フォルダをコピーすることとする。

>C: Users/Administrator/Desktop/installer/
>├ 7zip.7zip_24.09/
>│　├ 7-Zip_24.09_Machine_X64_exe_en-US.exe
>│　└ 7-Zip_24.09_Machine_X64_exe_en-US.yaml
>├ Adobe.Acrobat.Reader.64-bit_24.005.20421/
>│　├ Adobe Acrobat Reader (64-bit)_24.005.20421_Machine_X64_exe_en-US.exe
>│　└ Adobe Acrobat Reader (64-bit)_24.005.20421_Machine_X64_exe_en-US.yaml
>├ DuongDieuPhap.ImageGlass_9.2.0.1208/
>│　├ Dependencies/ 
>│　│　├ Microsoft .NET Windows Desktop Runtime 8.0_8.0.13_X64_burn_en-US.exe
>│　│　└ Microsoft .NET Windows Desktop Runtime 8.0_8.0.13_X64_burn_en-US.yaml
>│　├ ImageGlass_9.2.0.1208_X64_msi_en-US.msi
>│　└ ImageGlass_9.2.0.1208_X64_msi_en-US.yaml
>├ Google.Chrome_133.0.6943.142/
>│　├ Google Chrome_133.0.6943.142_Machine_X64_wix_en-US.msi
>│　└ Google Chrome_133.0.6943.142_Machine_X64_wix_en-US.yaml
>├ Google.JapaneseIME_2.30.5620.0/ 
>│　├ Google 日本語入力_2.30.5620.0_Machine_X64_wix_en-US.msi
>│　└ Google 日本語入力_2.30.5620.0_Machine_X64_wix_en-US.yaml
>├ Microsoft.VisualStudioCode_1.97.2/ 
>│　├ Microsoft Visual Studio Code_1.97.2_Machine_X64_inno_en-US.exe
>│　└ Microsoft Visual Studio Code_1.97.2_Machine_X64_inno_en-US.yaml
>├ PDFsam.PDFsam_5.3.0/ 
>│　├ PDFsam Basic_5.3.0_Machine_X64_wix_en-US.msi
>│　└ PDFsam Basic_5.3.0_Machine_X64_wix_en-US.yaml
>├ TrackerSoftware.PDF-XChangeEditor_10.5.2.395/ 
>│　├ PDF-XChange Editor_10.5.2.395_X64_wix_en-US.msi
>│　└ PDF-XChange Editor_10.5.2.395_X64_wix_en-US.yaml
>├ VideoLAN.VLC_3.0.21/ 
>│　├ VLC media player_3.0.21_Machine_X64_nullsoft_en-US.exe
>│　└ VLC media player_3.0.21_Machine_X64_nullsoft_en-US.yaml
>├ voidtools.Everything_1.4.1.1026/ 
>│　├ Everything_1.4.1.1026_Machine_X64_wix_en-US.msi
>│　└ Everything_1.4.1.1026_Machine_X64_wix_en-US.yaml
>└ builder.ps1

# 一括インストール用のスクリプトをビルドする
　インストーラをそのまま実行すると対話形式でインストールが開始されてしまうためサイレントインストールのオプションを指定する。このとき、YAML形式で記述されたマニフェストファイルに、サイレントインストールのオプションが「Silent」キーにマッピングされているためこれを利用した。同じく、各アプリに応じて必要なオプションは「Custom」キーにマッピングされているためこちらも利用した。
　また、.exeや.msiの実行コマンドを並べただけだと、他のインストールプロセスの終了を待たずに同時実行するため、`Start-Process`を用いて`-Wait -NoNewWindow -PassThru`のオプションを指定することで、インストールを逐次実行するようにした。

``` builder.ps1
$installerName = "installer.ps1"
# インストール順序をディレクトリ名の降順にすることで、サブディレクトリに保存されている依存関係のあるものを先にインストールしている
$f = ((Get-ChildItem *  -Recurse | Where-Object { $_.PSIsContainer }).fullname | Sort-Object -Descending)
(Write-Output "# This installer was generated on ") + (Get-date -f "[yyyy/MM/dd HH:mm:ss.ff]") > $installerName
for ($i=0; $i -lt $f.length; $i++){
    Write-Output ("Write-Output {0}" -f $f[$i]) >> $installerName
    $installerPath = $f[$i] + "\" + (Get-ChildItem $f[$i] -include *.msi,*.exe -Name)
    $installerPath = "`"" + (Convert-Path $installerPath) + "`""
    $installerArgv = ((Get-Content (Get-ChildItem $f[$i]*.yaml).fullname) -like "*Silent:*").split(":")[1]
    $installerArgv = $installerArgv + ("$i" + ((Get-Content (Get-ChildItem $f[$i]*.yaml).fullname) -like '*Custom:*')).split(":")[1]
    $isExe = (Get-ChildItem $f[$i] -include *.exe -Name)
    if($isExe){
        $installerArgv = "`"" + $installerArgv + "`""
        Write-Output "Start-Process -FilePath $installerPath -ArgumentList $installerArgv -Wait -NoNewWindow -PassThru" >> $installerName
    }else{
        $installerPath = "`'/i " + $installerPath +  $installerArgv + "`'"
        Write-Output "Start-Process -FilePath msiexec -ArgumentList $installerPath -Wait -NoNewWindow -PassThru" >> $installerName
    }
}
```
``` powershell:PowerShellでの実行コマンド
cd C:\Users\Administrator\Desktop\installer
.\2_builder.ps1
```

# 一括インストールを実行
　生成した一括インストール用スクリプトについて、マニフェストから取得できないオプション等を指定する場合には手動で書き換えてから実行する。

``` installer.ps1
# This installer was generated on [2025/03/02 01:02:14.68]
Write-Output C:\Users\Administrator\Desktop\installer\voidtools.Everything_1.4.1.1026
Start-Process -FilePath msiexec -ArgumentList '/i "C:\Users\Administrator\Desktop\installer\voidtools.Everything_1.4.1.1026\Everything_1.4.1.1026_Machine_X64_wix_en-US.msi" /quiet /norestart' -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\VideoLAN.VLC_3.0.21
Start-Process -FilePath "C:\Users\Administrator\Desktop\installer\VideoLAN.VLC_3.0.21\VLC media player_3.0.21_Machine_X64_nullsoft_en-US.exe" -ArgumentList " /S" -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\TrackerSoftware.PDF-XChangeEditor_10.5.2.395
Start-Process -FilePath msiexec -ArgumentList '/i "C:\Users\Administrator\Desktop\installer\TrackerSoftware.PDF-XChangeEditor_10.5.2.395\PDF-XChange Editor_10.5.2.395_X64_wix_en-US.msi" /quiet /norestart' -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\PDFsam.PDFsam_5.3.0
Start-Process -FilePath msiexec -ArgumentList '/i "C:\Users\Administrator\Desktop\installer\PDFsam.PDFsam_5.3.0\PDFsam Basic_5.3.0_Machine_X64_wix_en-US.msi" /qn SKIPTHANKSPAGE=Yes' -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\Microsoft.VisualStudioCode_1.97.2
Start-Process -FilePath "C:\Users\Administrator\Desktop\installer\Microsoft.VisualStudioCode_1.97.2\Microsoft Visual Studio Code_1.97.2_Machine_X64_inno_en-US.exe" -ArgumentList " /SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /mergetasks=!runcode" -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\Google.JapaneseIME_2.30.5620.0
Start-Process -FilePath msiexec -ArgumentList '/i "C:\Users\Administrator\Desktop\installer\Google.JapaneseIME_2.30.5620.0\Google 日本語入力_2.30.5620.0_Machine_X64_wix_en-US.msi" /quiet /norestart' -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\Google.Chrome_133.0.6943.142
Start-Process -FilePath msiexec -ArgumentList '/i "C:\Users\Administrator\Desktop\installer\Google.Chrome_133.0.6943.142\Google Chrome_133.0.6943.142_Machine_X64_wix_en-US.msi" /quiet /norestart' -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\DuongDieuPhap.ImageGlass_9.2.0.1208\Dependencies
Start-Process -FilePath "C:\Users\Administrator\Desktop\installer\DuongDieuPhap.ImageGlass_9.2.0.1208\Dependencies\Microsoft .NET Windows Desktop Runtime 8.0_8.0.13_X64_burn_en-US.exe" -ArgumentList " /quiet /norestart" -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\DuongDieuPhap.ImageGlass_9.2.0.1208
Start-Process -FilePath msiexec -ArgumentList '/i "C:\Users\Administrator\Desktop\installer\DuongDieuPhap.ImageGlass_9.2.0.1208\ImageGlass_9.2.0.1208_X64_msi_en-US.msi" /quiet /norestart' -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\Adobe.Acrobat.Reader.64-bit_24.005.20421
Start-Process -FilePath "C:\Users\Administrator\Desktop\installer\Adobe.Acrobat.Reader.64-bit_24.005.20421\Adobe Acrobat Reader (64-bit)_24.005.20421_Machine_X64_exe_en-US.exe" -ArgumentList " -sfx_nu /sAll /rs /msi" -Wait -NoNewWindow -PassThru
Write-Output C:\Users\Administrator\Desktop\installer\7zip.7zip_24.09
Start-Process -FilePath "C:\Users\Administrator\Desktop\installer\7zip.7zip_24.09\7-Zip_24.09_Machine_X64_exe_en-US.exe" -ArgumentList " /S" -Wait -NoNewWindow -PassThru
```
``` powershell:PowerShellでの実行コマンド
cd C:\Users\Administrator\Desktop\installer
.\installer.ps1
```

# 所感
　今回、実際の作業では数十台のクリーンインストールされたwindoswに30種類近くのアプリのインストールを求められた。作業環境は10mbps程度の回線速度が確保できず、wingetで一括インストールを行うと非常に時間がかかりタイムアウトするケースもあったためオフラインインストールで行った。wingetの本来の使い方ではないが、こういった使い方はある程度需要があるかと思い記録に残してみた。
　同じような境遇で苦労されている方がいましたら、ぜひ参考にしてみてください。