更新履歴(5件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- ユーザーフェーズをタスクスケジューラで起動する手順が、直前の手順を実行していないと動かない書き方になっていたので、スクリプトの配置から独立して読める形にしました。あわせて、WinGet Configuration と併用する場合にアプリの一覧が二重管理になる点を正しく説明し直しています。
- キッティングスクリプトの3部構成を図にし、長いコードのどこに何が書いてあるかの対応表を追加しました。あわせてユーザーフェーズの起動方法3方式の比較と実際の登録手順、設定JSONの全キー一覧、WinGet Configurationと自前スクリプトの棲み分けを追加しています。
- 本文中の関連記事へのリンクの文言が、リンク先の現在のタイトルと食い違っていたのを、実際のタイトルに揃えました。本文の内容は変えていません。
- 初版公開
この記事を引用する(DOI: 10.5281/zenodo.21547466)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「winget + PowerShellでPCキッティングを自動化する ── 手順書を実行可能にする」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21547466 https://staging.comcomponent.com/blog/winget-powershell-pc-kitting/
- DOI(最新版)
- 10.5281/zenodo.21547466
- DOI(この版)
- 10.5281/zenodo.21733172
新入社員が入るたび、あるいはPCを入れ替えるたびに、担当者が手順書を見ながら1台ずつ設定していく ── 中小企業の情シスでは、いまだにこれが標準的な光景です。問題は時間だけではありません。手作業は再現性がないため、「この端末だけ設定が違う」というトラブルが後から効いてきます。手順書は更新されないまま古くなり、担当者が変わると細部が失われます。
Windowsには標準でパッケージマネージャー winget が入っており、アプリの導入は1行で書けます。さらにWinGet Configurationを使えば、アプリと設定を宣言的(手順を並べるのではなく「最終的にこうあってほしい状態」を書く方式)なYAMLファイル1つで表現できます。そしてwingetがカバーしない領域(プリンター、ネットワークドライブ、社内標準のレジストリ設定など)は、PowerShellで補えます。
この記事では、キッティング手順書を「実行可能なファイル」に置き換える方法を、実際に運用できる粒度でまとめます。
1. まず結論
- アプリ導入はwingetに任せます。
winget installは静かなインストールを標準でサポートします。1 - 既存PCの構成は
winget exportで吸い出せます。ただしwinget管理下のパッケージに限られ、設定は含まれません。2 - 宣言的にやるならWinGet Configuration(
winget configure)です。PowerShell DSCベースで、アプリと設定を1つのYAMLに書けます。Windows 10 1809以降 + winget 1.6以降が要件です。3 - wingetで足りない部分はPowerShellで補います。プリンター、共有ドライブ、レジストリ、Windows機能、ローカルアカウントなどです。
- PowerShellから扱うなら
Microsoft.WinGet.Clientモジュールがあります。Install-WinGetPackageなどのコマンドレットが使えます。4 - システムコンテキスト(SYSTEMアカウントなど、ログオン中の利用者とは別のアカウントでの実行)には要注意です。Microsoftも今後の開発項目として挙げている段階で、実際の実行アカウントでの検証が必須です。3
- 冪等性(べきとうせい。何度実行しても同じ結果になること)を必ず確保します。キッティングは途中で失敗するもので、何度でも再実行できる必要があります。
- 社内独自アプリは無理にwingetに載せず、PowerShellでサイレント実行するのが現実的です。
この記事の知識マップ
新入社員PCのキッティング手順書を実行可能なファイルに置き換える記事で、アプリ導入はwingetに任せ、winget export/importは既存標準機のパッケージ一覧だけを再現できる出発点にすぎないと位置づける。宣言的に構成したい場合はPowerShell DSCベースのWinGet Configurationが冪等な適用を実装するが、ネットワークドライブや共有プリンターなどログオンセッション単位の設定は対象外で、自前のPowerShellスクリプトで補う必要がある。そのスクリプトはマシン全体の設定を担う管理者フェーズと、利用者ごとの設定をログオンのたびに非昇格で適用するユーザーフェーズに分け、タスクスケジューラやHKLMのRunキーで後者を起動する。wingetはシステムコンテキストでの実行を前提としない部分があるため、Microsoft.WinGet.Clientモジュールや–scope machineが回避策として挙げられる。
flowchart LR
accTitle: winget + PowerShellでのPCキッティング自動化の知識マップ
accDescr: wingetとWinGet Configurationがアプリ導入とその冪等な宣言的構成を担う一方、ネットワークドライブや共有プリンターなどユーザー単位の設定はカバーせず自前PowerShellの管理者フェーズとユーザーフェーズで補う構造、システムコンテキスト実行の制約とその回避策の関係を示す図
winget["winget"]
winget_configuration["WinGet Configuration"]
idempotency["冪等性"]
winget_export_import["winget export/import"]
powershell_dsc["PowerShell DSC"]
custom_powershell_kitting["自前PowerShellキッティングスクリプト"]
winget_coverage_gap["wingetがカバーしない領域"]
elevated_phase["管理者フェーズ(昇格)"]
admin_rights["管理者権限"]
network_drive_mapping["ネットワークドライブの割り当て"]
user_phase["ユーザーフェーズ(非昇格)"]
shared_printer_connection["共有プリンターへの接続"]
task_scheduler["タスクスケジューラの無人実行"]
hklm_run_key["HKLMのRunキー"]
group_policy["グループポリシー"]
system_context_execution["システムコンテキストでの実行"]
ms_winget_client_module["Microsoft.WinGet.Clientモジュール"]
scope_machine_option["--scope machineオプション"]
msi_exit_code["Windows Installerの終了コード(3010/1641)"]
winget -->|"実装を担う"| winget_export_import
winget -->|"実装を担う"| winget_configuration
winget_configuration -->|"利用する"| powershell_dsc
winget_configuration -->|"実装を担う"| idempotency
custom_powershell_kitting -->|"実装を担う"| idempotency
custom_powershell_kitting -->|"軽減する"| winget_coverage_gap
elevated_phase -->|"前提とする"| admin_rights
network_drive_mapping -->|"前提とする"| user_phase
shared_printer_connection -->|"前提とする"| user_phase
task_scheduler -->|"自動化する"| user_phase
hklm_run_key -->|"自動化する"| user_phase
group_policy -->|"自動化する"| user_phase
system_context_execution -.->|"両立しない"| winget
ms_winget_client_module -->|"実装を担う"| winget
ms_winget_client_module -.->|"軽減する"| system_context_execution
scope_machine_option -.->|"軽減する"| system_context_execution
custom_powershell_kitting -->|"で確認できる"| msi_exit_code
winget -->|"利用する"| scope_machine_option
elevated_phase -->|"より先に行うべき"| user_phase
custom_powershell_kitting -->|"利用する"| winget
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全20件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. wingetの基本 ── 無人インストールの書き方
まず、対話なしで確実に入れるための定型を押さえます。1
# IDを正確に指定して入れる(-e は完全一致、--id はID指定)
# --silent : UIを出さない
# --accept-package-agreements : パッケージの使用条件に同意
# --accept-source-agreements : ソースの使用条件に同意
# --scope machine : 全ユーザー向けに導入(対応パッケージのみ)
# 行継続のバッククォートは行末に置く。後ろにコメントを書くと継続にならない
winget install --id Google.Chrome -e --silent `
--accept-package-agreements --accept-source-agreements --scope machine
# IDを調べる
winget search "Visual Studio Code"
winget show --id Microsoft.VisualStudioCode
--accept-* を付け忘れると、無人実行が同意待ちで止まります。これがキッティング自動化で最初につまずく点です。
--scope machine はすべてのパッケージで使えるわけではなく、ユーザー単位でしか導入できないアプリもあります。その場合は初回ログオン時にユーザーコンテキストで実行する構成にします。
対応可否は、入れる前に確認できます。winget show は --scope を受け付けるので、マシンスコープのインストーラーが用意されているかを事前に調べられます。5
# マシンスコープのインストーラーがあるかを確認する
winget show --id Google.Chrome -e --scope machine
# 比較のためユーザースコープ側も見ておく
winget show --id Google.Chrome -e --scope user
指定したスコープに該当するインストーラーがなければ、その旨のメッセージになります。キッティングの設定ファイルに "scope": "machine" と書く前に、対象パッケージを一通りこれで確認しておくと、無人実行の途中で初めて失敗するのを避けられます。
3. 現行PCの構成を吸い出す ── export / import
すでに整備済みの「標準PC」がある場合、その構成をファイル化できます。2
# 標準PCから、導入済みパッケージの一覧をJSONに書き出す
winget export --output D:\kitting\apps.json --include-versions
# 新しいPCで復元する
winget import --import-file D:\kitting\apps.json `
--accept-package-agreements --accept-source-agreements --ignore-unavailable
出力されるJSONは、こういう構造です(値は例です)。2
{
"CreationDate": "2026-07-25T10:40:00.000-00:00",
"Sources": [
{
"Packages": [
{ "PackageIdentifier": "Google.Chrome", "Version": "126.0.6478.127" },
{ "PackageIdentifier": "Microsoft.VisualStudioCode", "Version": "1.101.2" },
{ "PackageIdentifier": "7zip.7zip", "Version": "24.09" }
],
"SourceDetails": {
"Argument": "https://cdn.winget.microsoft.com/cache",
"Identifier": "Microsoft.Winget.Source_8wekyb3d8bbwe",
"Name": "winget",
"Type": "Microsoft.PreIndexed.Package"
}
}
],
"WinGetVersion": "1.9.25180"
}
見てのとおり、中身はパッケージ識別子とバージョンの一覧です(--include-versions を付けない場合は Version が入らず、import は最新版を入れます)。2 つまり winget import がやるのは「この識別子のパッケージを入れる」だけで、アプリの設定も、winget以外で入れたアプリも、ここには1文字も入っていません。JSONを実際に開いてみると、この制約が具体的に分かります。
制約を理解しておくことが重要です。
| できること | できないこと |
|---|---|
| winget管理下のパッケージ一覧の再現 | アプリ内の設定の移行 |
バージョンの固定(--include-versions) |
winget以外で入れたアプリ・社内独自アプリ |
存在しないパッケージのスキップ(--ignore-unavailable) |
ライセンス認証、サインイン状態 |
つまり winget import は出発点であって、キッティングの全体ではありません。残りをどう埋めるかが本題です。
4. 宣言的に書く ── WinGet Configuration
winget configure は、「最終的にこうあってほしい」状態をYAMLで宣言し、PowerShell DSCを通じて適用する仕組みです。3 手続き的なスクリプトと比べた利点は3つあります。
- 既に望ましい状態なら何もしない(冪等)
- アプリの導入と、Windows・アプリの設定を1ファイルで表現できる
- 途中で失敗しても、同じファイルを再実行すればよい
# kitting.winget ── 標準端末の望ましい状態を宣言する
properties:
configurationVersion: 0.2.0
resources:
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: chrome
directives:
description: Google Chrome を導入する
allowPrerelease: true
settings:
id: Google.Chrome
source: winget
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vscode
directives:
description: Visual Studio Code を導入する
settings:
id: Microsoft.VisualStudioCode
source: winget
- resource: Microsoft.Windows.Developer/DeveloperMode
id: devmode
directives:
description: 開発者モードを有効にする(開発端末のみ)
allowPrerelease: true
settings:
Ensure: Present
# 適用前に内容を確認する(何が実行されるかを表示)
winget configure show --file D:\kitting\kitting.winget
# 適用する
winget configure --file D:\kitting\kitting.winget --accept-configuration-agreements
要件はWindows 10 バージョン1809(ビルド17763)以降またはWindows 11、winget 1.6.2631以降です。3 古い端末が残っている環境では、次章のPowerShellによる構成のほうが確実です。
4.1. directives の読み方 ── allowPrerelease はアプリの話ではない
YAMLを他のサンプルからつなぎ合わせると必ず混乱するのが directives です。ここに書くのはリソースの取り扱いに関する指示であって、インストールするアプリの指定(それは settings の側)ではありません。
description… 実行時に表示される説明文。winget configure showの出力がそのまま読めるものになるので、必ず書きますallowPrerelease… DSCリソースを提供するPowerShellモジュールのプレリリース版の使用を許可する指示です。「アプリのプレリリース版(ベータ版)を入れる」という意味ではありません。
この区別が効いてきます。allowPrerelease はモジュール単位の話なので、同じ resource: を使っている項目のあいだで値が食い違っているのは、たいていサンプルの継ぎ接ぎによる不整合です。上の例では Microsoft.WinGet.DSC/WinGetPackage の2件のうちchromeにだけ付いていますが、アプリの都合で分ける理由はないので、同じリソース種別なら揃えてください。判断の基準は「そのリソースを提供するモジュールに安定版が公開されているか」の一点で、安定版があるなら外し、まだプレリリースしかないモジュールを使うときだけ付けます。
4.2. WinGet Configurationと自前PowerShellの棲み分け
「WinGet Configurationが冪等なのに、なぜ次章で自前の冪等コードを書くのか」は当然の疑問です。答えはカバーできる範囲が違うからで、置き換えの関係ではありません。要件ごとに整理します。
| 要件 | WinGet Configuration | 自前PowerShell(5章) | 実務での選び方 |
|---|---|---|---|
| winget公開アプリの導入 | ◎ 標準の WinGetPackage リソース |
○ 書けるが自分で冪等にする | Configuration |
| Windowsの設定(開発者モード等) | ○ 対応するDSCリソースがあれば | ○ | リソースがあるならConfiguration |
| 任意のレジストリ値(社内標準設定) | △ 対応リソース次第 | ◎ 何でも書ける | PowerShell |
| 共有フォルダーの社内アプリ(MSI/EXE) | △ 標準リソースの範囲外 | ◎ | PowerShell |
| ネットワークドライブ・共有プリンター | × ユーザー単位・ログオン時の処理 | ◎ | PowerShell(非昇格フェーズ) |
| 部署別・機種別の差分 | △ YAMLを分ける | ◎ 設定JSONの差し替えで済む | PowerShell |
| 対象OSが古い(1809未満・winget 1.6未満) | × 要件を満たさない | ◎ | PowerShell |
| 終了コードで配布ツールに再起動要否を返す | △ 制御しにくい | ◎ 自分で決められる | PowerShell |
結論としては「wingetの土俵に載るものはConfigurationへ、載らないものはPowerShellへ」です。ただし両方を使うなら、アプリの一覧はどちらか一方にだけ置いてください。ConfigurationのYAMLにパッケージを書き、さらに5章の kitting.config.json の wingetPackages にも同じものを書くと、片方を直したときにもう片方が古いまま残ります。Configurationを併用する構成にするなら、PowerShell側からはアプリ導入の処理を外し、JSONにはレジストリ値や共有プリンターなどConfigurationが扱えない項目だけを持たせるのが実務的です。逆に、Configurationだけで全部を賄おうとして対応リソースを探し回るのは、投資に見合わないことが多い作業です。
5. PowerShellで補う ── wingetがやらない部分
実務のキッティングで手間の大半を占めるのは、実はアプリ導入以外です。ここはPowerShellの出番になります。すべて「実行済みなら何もしない」形で書くのがポイントです。
この章は長いので、先に全体像を示します。登場するのは3つのファイルだけで、「何を入れるか」を決めるJSONを1つ持ち、それを2つのスクリプトが別々の権限で読むという構成です。
flowchart TB
CFG["kitting.config.json<br/>「この端末はこうあるべき」の定義"]
subgraph ADM["管理者フェーズ(昇格。端末につき1回)"]
A1["Invoke-KsKitting.ps1"]
A2["フォルダー / HKLM のレジストリ /<br/>Windowsの機能 / wingetパッケージ /<br/>社内アプリ(MSI・EXE)"]
A3["設定JSONを ProgramData 配下へ配る"]
A1 --> A2
A1 --> A3
end
subgraph USR["ユーザーフェーズ(非昇格。利用者のログオンごと)"]
U1["Invoke-KsUserKitting.ps1"]
U2["HKCU のレジストリ /<br/>ネットワークドライブ /<br/>共有プリンター"]
U1 --> U2
end
CFG --> A1
A3 -->|"配布された設定を読み直す"| U1
図1: キッティングの3部構成。同じ定義を、権限の違う2つのフェーズが読む
分け方の原則は1つだけです。マシン全体に効く設定は管理者フェーズ、利用者ごとに作られる設定はユーザーフェーズ。この2つを混ぜると、後述のとおり「割り当てたはずのドライブが見えない」という形で必ず事故ります。
この先に出てくるコードは長いので、どこに何が書いてあるかを先に示します。管理者スクリプトの中は --- N. --- のコメントで区切ってあるので、必要なところだけ読んでください。
| 区切り | やっていること | ここを読むべき人 |
|---|---|---|
| 冒頭(前処理) | 設定JSONの読み込み、C:\ProgramData への配布、Start-Transcript によるログ開始 |
全員 |
--- 1. 標準フォルダー --- |
フォルダー作成。冪等の書き方のいちばん簡単な例 | 冪等の書き方を知りたい人 |
--- 2. レジストリ設定 --- |
HKLMへの書き込み。値だけでなく型も比較するのが要点 | 社内標準設定を配りたい人 |
--- 3. Windowsの機能 --- |
機能の有効化と、再起動要否の拾い方 | .NET Framework 3.5などが必要な人 |
--- 4. wingetパッケージ --- |
導入済み判定と、終了コードを信用しない書き方 | winget部分だけ見たい人 |
--- 5. 社内アプリ --- |
共有フォルダーのMSI/EXEをサイレント実行。版の比較と終了コードの扱い | 社内アプリを配りたい人 |
| 末尾(終了処理) | 3010 / 1641 / 1 / 0 の返し分け | 配布ツール(Intune等)と繋ぐ人 |
このスクリプトは kitting.config.json を読み込みます。設定ファイルの完全な例は、この記事のサンプルコード(記事末尾のzip)に kitting.config.json として同梱していますが、構造だけ先に示しておきます。
{
"folders": [ "C:\\Work", "C:\\KsTools" ],
"registry": [
{ "key": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Edge",
"name": "HomepageLocation", "value": "https://intra.example.co.jp/", "type": "String" }
],
"userRegistry": [
{ "key": "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced",
"name": "HideFileExt", "value": 0, "type": "DWord" }
],
"windowsFeatures": [ "NetFx3" ],
"wingetPackages": [ { "id": "Google.Chrome", "scope": "machine" } ],
"internalApps": [
{ "displayName": "KsApp 業務システム クライアント",
"installer": "\\\\fileserver\\deploy\\KsApp\\KsAppSetup.msi",
"arguments": "/quiet /norestart INSTALLDIR=\"C:\\Program Files\\KsApp\"",
"version": "3.2.0" }
],
"drives": [ { "letter": "S", "path": "\\\\fileserver\\share" } ],
"printers": [ { "name": "\\\\printserver\\MFP-1F", "connection": "\\\\printserver\\MFP-1F" } ]
}
各キーの役割と、どちらのフェーズが読むかを表にします。設計の意図はこの表に全部出ています。
| キー | 内容 | 読むフェーズ | 補足 |
|---|---|---|---|
folders |
作成する標準フォルダーのパス | 管理者(昇格) | 存在すれば何もしない |
registry |
HKLMに書く社内標準設定 | 管理者(昇格) | ポリシー系(Edgeのホームページ等)。全ユーザーに効く |
userRegistry |
HKCUに書く利用者ごとの設定 | ユーザー(非昇格) | 拡張子の表示など。HKLMに書いても効かない |
windowsFeatures |
有効化するWindowsのオプション機能 | 管理者(昇格) | 再起動が必要になることがある |
wingetPackages |
wingetで入れるパッケージの id と scope |
管理者(昇格) | scope は事前に winget show --scope で確認する(2章) |
internalApps |
共有フォルダーの社内アプリ(MSI/EXE) | 管理者(昇格) | displayName は「プログラムと機能」の表示名と完全一致させる |
drives |
ネットワークドライブの割り当て | ユーザー(非昇格) | ログオンセッション単位。昇格側で作ると利用者に見えない |
printers |
共有プリンターへの接続 | ユーザー(非昇格) | 同上。ユーザーごとの接続が作られる |
フェーズ列が2種類に分かれていることが、この設定ファイルの設計そのものです。新しい項目を足すときは、まず「これはマシンの設定か、利用者の設定か」を決めてから、どちらのキーに入れるかを選んでください。
registry と userRegistry を分けているのは、適用先が違うからです。エクスプローラーの「拡張子を表示する」(HideFileExt)のような設定は、HKLM のポリシーではなく利用者ごとの HKCU を見ます。管理者フェーズで HKLM に書いても表示は変わりません。こうしたユーザー単位の設定は userRegistry に入れ、後述の非昇格フェーズで適用します。
internalApps の displayName は「プログラムと機能」に表示される名前と完全に一致させてください。version を指定すると、その版以上が入っているかまで確認します(省略時は名前の一致だけで判定します)。
アプリ導入そのものは前章の winget import や WinGet Configuration でも行えますが、このスクリプトにも wingetPackages の導入を含めています。設定ファイルを1つにまとめておけば、「この端末に何を入れるか」の定義が一箇所に収まり、実行も1回で済むためです。逆に、設定ファイルに書いた項目をスクリプトが読まないまま放置すると、入っていない端末を「キッティング成功」と記録してしまいます。
#Requires -RunAsAdministrator
[CmdletBinding()]
param(
[string] $ConfigPath = "$PSScriptRoot\kitting.config.json"
)
$ErrorActionPreference = 'Stop'
# 5.1の Get-Content はBOMがなければANSIコードページで読む。
# UTF-8のJSONを日本語環境で読むと化けるため、明示する
$config = Get-Content $ConfigPath -Raw -Encoding utf8 | ConvertFrom-Json
$rebootRequired = $false
$rebootInitiated = $false
$log = "C:\ProgramData\KsKitting\kitting_$(Get-Date -f yyyyMMdd_HHmmss).log"
$null = New-Item -Path (Split-Path $log) -ItemType Directory -Force
# 後述のユーザー単位フェーズは別プロセスで動くため、設定を
# 全ユーザーが読める場所へ配っておく。
# 配布物をこの場所に置いてそのまま実行すると、コピー元と先が同じファイルになる。
# Copy-Item は自分自身へのコピーをエラーにするため、$ErrorActionPreference = 'Stop'
# の下ではキッティングが始まる前にスクリプトごと止まってしまう
$sharedConfig = 'C:\ProgramData\KsKitting\kitting.config.json'
$sourceFull = (Resolve-Path $ConfigPath).ProviderPath
$sharedFull = if (Test-Path $sharedConfig) { (Resolve-Path $sharedConfig).ProviderPath } else { '' }
if (-not [string]::Equals($sourceFull, $sharedFull, 'OrdinalIgnoreCase')) {
Copy-Item $ConfigPath $sharedConfig -Force
}
Start-Transcript -Path $log -Append
try {
# --- 1. 標準フォルダー ------------------------------------------------
foreach ($dir in $config.folders) {
if (-not (Test-Path $dir)) {
$null = New-Item -Path $dir -ItemType Directory
Write-Verbose "作成: $dir"
}
}
# --- 2. 社内標準のレジストリ設定 --------------------------------------
foreach ($reg in $config.registry) {
if (-not (Test-Path $reg.key)) { $null = New-Item -Path $reg.key -Force }
$item = Get-Item -Path $reg.key
$exists = $item.GetValueNames() -contains $reg.name
# 値だけでなく「型」も比較する。REG_SZ の "1" と DWORD の 1 は
# PowerShellの比較では等しくなってしまい、実際には効いていない設定を
# 「適用済み」と誤判定する
$sameValue = $exists -and $item.GetValue($reg.name) -eq $reg.value
$sameKind = $exists -and $item.GetValueKind($reg.name).ToString() -eq $reg.type
if (-not ($sameValue -and $sameKind)) {
Set-ItemProperty -Path $reg.key -Name $reg.name -Value $reg.value -Type $reg.type
Write-Verbose "設定: $($reg.key)\$($reg.name) = $($reg.value) ($($reg.type))"
}
}
# --- 3. Windowsの機能 --------------------------------------------------
foreach ($feature in $config.windowsFeatures) {
$state = Get-WindowsOptionalFeature -Online -FeatureName $feature
if ($state.State -ne 'Enabled') {
# -NoRestart で再起動を抑止した場合、要否は戻り値のRestartNeededに出る。
# 拾っておかないと「再起動が必要なのに0で終了」してしまう
$result = Enable-WindowsOptionalFeature -Online -FeatureName $feature -NoRestart
if ($result.RestartNeeded) { $rebootRequired = $true }
}
}
# --- 4. wingetパッケージ ----------------------------------------------
# winget の終了コードは「既に導入済み」でも 0 以外になることがあり、
# 逆に 0 でも実際には入っていない場合がある。成否は状態の照会で判断する。
# --scope を付けないと、管理者自身のユーザー単位の導入を拾って
# 「machine で入っている」と誤判定する
function Test-KsWingetPackage {
param([string] $Id, [string] $Scope)
$arguments = @('list', '--id', $Id, '--exact', '--accept-source-agreements')
if ($Scope) { $arguments += @('--scope', $Scope) }
$null = winget @arguments 2>&1
return ($LASTEXITCODE -eq 0)
}
foreach ($package in $config.wingetPackages) {
if (Test-KsWingetPackage -Id $package.id -Scope $package.scope) { continue }
# 行継続のバッククォートは行末に置く。後ろにコメントを書くと継続にならない
winget install --id $package.id -e --silent `
--accept-package-agreements --accept-source-agreements `
--scope $package.scope
$wingetExit = $LASTEXITCODE
# ここで警告だけ出して先に進むと、必須アプリが入っていない端末を
# 配布ツールが「キッティング成功」と記録してしまう
if (-not (Test-KsWingetPackage -Id $package.id -Scope $package.scope)) {
throw "$($package.id) の導入に失敗しました (winget ExitCode=$wingetExit)"
}
}
# --- 5. 社内アプリ(共有フォルダーのインストーラーをサイレント実行) ----
# 導入済み一覧は、32bit/64bitの両ビューを明示して開く。
# 32bitのPowerShellが64bit Windows上で動くと(Intuneの構成によっては起こる)、
# WOW64のリダイレクトで HKLM:\SOFTWARE\... が32bitビューを指し、
# 64bitのアプリを「未導入」と誤判定して毎回入れ直してしまう
$installedApps = foreach ($view in 'Registry64', 'Registry32') {
$baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', $view)
try {
$uninstall = $baseKey.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')
if (-not $uninstall) { continue }
try {
foreach ($name in $uninstall.GetSubKeyNames()) {
$appKey = $uninstall.OpenSubKey($name)
if (-not $appKey) { continue }
try {
$displayName = $appKey.GetValue('DisplayName')
if ($displayName) {
[pscustomobject]@{
DisplayName = [string] $displayName
DisplayVersion = [string] $appKey.GetValue('DisplayVersion')
}
}
}
finally { $appKey.Dispose() }
}
}
finally { $uninstall.Dispose() }
}
finally { $baseKey.Dispose() }
}
foreach ($app in $config.internalApps) {
$installed = $installedApps | Where-Object DisplayName -eq $app.displayName
# DisplayNameの一致だけで「導入済み」と判断しない。古い版が入ったままの端末に
# 新しい版が入らず、再実行しても設定ファイルの状態に収束しなくなる。
# 設定に version があれば、その版以上が入っているかまで見る
$upToDate = if ($app.version) {
$wanted = [version] $app.version
[bool]($installed | Where-Object {
$cur = $_.DisplayVersion -as [version] # 版形式でない表記は対象外
$cur -and $cur -ge $wanted
})
} else {
[bool] $installed
}
if ($upToDate) { continue }
# Start-Process の -ArgumentList は配列を空白で連結して1本のコマンドラインにするため、
# 空白を含む値は設定ファイル側で引用符を付けておく
# 例: '/quiet /norestart INSTALLDIR="C:\Program Files\KsApp"'
# .msi は実行ファイルではないため、そのまま Start-Process に渡すと
# 「有効なアプリケーションではありません」で失敗する。msiexec 経由で起動する
if ([System.IO.Path]::GetExtension($app.installer) -eq '.msi') {
$msiArgs = @('/i', "`"$($app.installer)`"") + $app.arguments
$proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList $msiArgs -Wait -PassThru -NoNewWindow
}
else {
$proc = Start-Process -FilePath $app.installer -ArgumentList $app.arguments -Wait -PassThru -NoNewWindow
}
# Windows Installerの終了コードは「0だけが成功」ではない。
# 3010と1641はどちらも成功で、再起動の扱いだけが違う
switch ($proc.ExitCode) {
0 { } # 成功
3010 { $rebootRequired = $true } # 成功。要再起動(ERROR_SUCCESS_REBOOT_REQUIRED)
1641 { $rebootInitiated = $true } # 成功。インストーラーが再起動を開始した
default {
throw "$($app.displayName) のインストールに失敗しました (ExitCode=$($proc.ExitCode))"
}
}
# 再起動が始まっているなら、後続のインストールは走らせても中断される
if ($rebootInitiated) { break }
}
if ($rebootInitiated) {
# 1641は「成功。ただし再起動を開始済み」。0を返すと配布ツールは
# 「完了したのに勝手に再起動した」と扱うため、そのまま伝える
Write-Host 'インストーラーが再起動を開始しました。再起動後に再実行してください' -ForegroundColor Yellow
exit 1641
}
if ($rebootRequired) {
# 3010をそのまま返すと、Intuneや配布ツール側が「成功。要再起動」と解釈し、
# 再起動のスケジュールと報告を行える。ここで0を返すと再起動が忘れられる
Write-Host 'キッティング完了(再起動が必要)' -ForegroundColor Yellow
exit 3010
}
Write-Host 'キッティング完了' -ForegroundColor Green
exit 0
}
catch {
Write-Warning "失敗: $($_.Exception.Message)"
Write-Warning $_.InvocationInfo.PositionMessage
exit 1
}
finally {
Stop-Transcript
}
この管理者スクリプトからネットワークドライブの割り当てを意図的に外している点に注意してください。ドライブレターの割り当てはログオンセッション単位の設定なので、管理者に昇格したセッションで作成しても、UACの下では利用者の通常のエクスプローラーからは見えません。Intuneなどからシステム権限で実行した場合は、そもそもSYSTEMのセッションに割り当てられてしまい、利用者には無関係です。ユーザー固有の設定は、そのユーザーのログオン時に非昇格で実行するのが正解です。
# ユーザー単位の設定(非昇格で、利用者のログオン時に実行する。登録方法は後述)
# 管理者スクリプトとは別プロセスなので、$config は引き継がれない。
# 管理者フェーズで ProgramData へ配っておいた設定を読み直す
$configPath = 'C:\ProgramData\KsKitting\kitting.config.json'
if (-not (Test-Path $configPath)) {
Write-Warning "設定ファイルが見つかりません: $configPath"
exit 1
}
$config = Get-Content $configPath -Raw -Encoding utf8 | ConvertFrom-Json
# ユーザー単位のレジストリ設定(HideFileExt など。HKLMに書いても効かない)
foreach ($reg in $config.userRegistry) {
if (-not (Test-Path $reg.key)) { $null = New-Item -Path $reg.key -Force }
$item = Get-Item -Path $reg.key
$exists = $item.GetValueNames() -contains $reg.name
$same = $exists -and
$item.GetValue($reg.name) -eq $reg.value -and
$item.GetValueKind($reg.name).ToString() -eq $reg.type
if (-not $same) {
Set-ItemProperty -Path $reg.key -Name $reg.name -Value $reg.value -Type $reg.type
}
}
# ネットワークドライブ
foreach ($drive in $config.drives) {
$local = "$($drive.letter):"
$existing = Get-SmbMapping -LocalPath $local -ErrorAction SilentlyContinue
if ($existing) {
# 「割り当てがあるか」だけで判断すると、共有の移転などで設定を変えても
# 古い割り当てが残ったまま「成功」と報告されてしまう。接続先まで比較する
if ($existing.RemotePath.TrimEnd('\') -eq $drive.path.TrimEnd('\')) { continue }
Remove-SmbMapping -LocalPath $local -Force
}
New-SmbMapping -LocalPath $local -RemotePath $drive.path -Persistent $true
}
# 共有プリンターへの接続もユーザー単位。管理者やSYSTEMで実行すると、
# そのアカウントにだけ接続が作られ、利用者からは見えない
foreach ($printer in $config.printers) {
if (-not (Get-Printer -Name $printer.name -ErrorAction SilentlyContinue)) {
Add-Printer -ConnectionName $printer.connection
}
}
共有プリンターへの接続(Add-Printer -ConnectionName)も同じ扱いです。これはユーザーごとの接続を作る操作なので、管理者スクリプトやIntuneのSYSTEM実行で行っても、後からログオンする従業員には見えません。全端末共通で持たせたい場合は、プリンターをマシン単位で展開する仕組み(印刷サーバーのポリシー配布など)を使うか、この非昇格フェーズで実行してください。
同じ理由で、ユーザープロファイル配下のファイル配置、HKCUへの書き込み、ユーザー向けショートカットの作成も、この非昇格フェーズにまとめます。「マシン全体の設定は管理者で1回、ユーザー固有の設定はログオンごとに非昇格で」という二段構えが、キッティングスクリプトの基本形です。UNCパスとドライブ割り当ての注意点は「ネットワーク共有・UNCパスの落とし穴」も参照してください。
ユーザーフェーズをどう起動するか。二段構えの肝は、この非昇格スクリプトを「利用者のログオン時に、その利用者自身の権限で」走らせる仕掛けです。ここが繋がっていないと、管理者フェーズだけが動いて「ドライブもプリンターも設定されていない端末」ができあがります。方法は3つあります。
| 方法 | 走るタイミング | 向いている環境 | 注意点 |
|---|---|---|---|
| HKLMのRunキー | 全ユーザーのログオンごと | ドメイン参加なし。Intune・配布ツールから1本のスクリプトで完結させたい | 毎回走るので冪等が前提。ウィンドウを出さない指定が要る |
| タスクスケジューラ(ログオン時トリガー) | 全ユーザーのログオンごと | 実行結果の履歴を残したい。遅延実行したい | 「最上位の特権で実行する」をオフにする(オンにすると昇格して意味がなくなる) |
| ログオンスクリプト(グループポリシー) | 全ユーザーのログオンごと | ドメイン参加済み | 既存のGPO運用に載せられるが、単独端末では使いにくい |
方法1: HKLMのRunキーに登録する(管理者フェーズの最後に実行します)
# ユーザーフェーズのスクリプトを、全ユーザーが読める場所へ配置する
$userScript = 'C:\ProgramData\KsKitting\Invoke-KsUserKitting.ps1'
Copy-Item "$PSScriptRoot\Invoke-KsUserKitting.ps1" $userScript -Force
# HKLM の Run は、ログオンした利用者の権限(非昇格)で実行される。
# HKCU に書こうとしても、管理者/SYSTEMで動いている以上、書き込まれるのは
# 「管理者自身のHKCU」であって、これから使う従業員のHKCUではない
$runKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
$command = 'powershell.exe -NoProfile -ExecutionPolicy Bypass ' +
"-WindowStyle Hidden -File `"$userScript`""
Set-ItemProperty -Path $runKey -Name 'KsUserKitting' -Value $command
方法2: タスクスケジューラに登録する(履歴を残したい場合)
方法1を飛ばしてここから読む場合に備えて、スクリプトの配置からもう一度書きます。
# 全ユーザーから読める場所へ配置する(方法1と同じ)
$userScript = 'C:\ProgramData\KsKitting\Invoke-KsUserKitting.ps1'
New-Item -ItemType Directory -Path (Split-Path $userScript) -Force | Out-Null
Copy-Item -Path '.\Invoke-KsUserKitting.ps1' -Destination $userScript -Force
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$userScript`""
$trigger = New-ScheduledTaskTrigger -AtLogOn
# BUILTIN\Users(S-1-5-32-545)を指定すると、ログオンした本人の権限で実行される。
# RunLevel Limited が「昇格しない」の指定。ここを Highest にすると
# 昇格したセッションで走ってしまい、ドライブ割り当てが利用者から見えなくなる
$principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-545' -RunLevel Limited
Register-ScheduledTask -TaskName 'KsUserKitting' `
-Action $action -Trigger $trigger -Principal $principal -Force
方法3: グループポリシーのログオンスクリプト
設定場所は ユーザーの構成 > ポリシー > Windows の設定 > スクリプト (ログオン/ログオフ) > ログオン です(単独端末の gpedit.msc では ポリシー の階層がなく、ユーザーの構成 > Windows の設定 > スクリプト (ログオン/ログオフ))。ダイアログの 「PowerShell スクリプト」タブに追加してください。「スクリプト」タブに .ps1 を直接書くと、実行ファイルとして扱われて意図した動きになりません。ローカルポリシーの場合、スクリプトの実体は %SystemRoot%\System32\GroupPolicy\User\Scripts\Logon に置かれます。
どの方法でも、ログオンのたびに実行される点は同じです。ユーザーフェーズのスクリプトを冪等に書いてある(現在の状態を確認してから変更する)のは、このためでもあります。「1回だけ実行したい」なら、HKCU配下に完了マーカーを書いて先頭で確認する形にしてください。
設計上のポイントを挙げます。
- 昇格が必要な設定と、ユーザー単位の設定を分ける。上記のとおり、混ぜると「割り当てたはずのドライブが見えない」事故になります
- 設定はJSONに外出しする。部署ごと・機種ごとの差分を、スクリプトを変えずに表現できます
- 各処理の前に現在の状態を確認する。これで何度実行しても安全になります
Start-Transcriptで証跡を残す。「この端末で何をしたか」が後から追えます(「PowerShellの出力ストリームとログ設計」)- 終了コードを返す。Intuneや配布ツールから成否を判定できます。Windows Installerでは0だけが成功ではない点に注意してください。3010(ERROR_SUCCESS_REBOOT_REQUIRED、要再起動)と1641(ERROR_SUCCESS_REBOOT_INITIATED、再起動を開始済み)はいずれも成功です。6 これらを
defaultに落として失敗扱いにすると、成功したインストールが配布ツール上では赤く記録されます。成功扱いにしたうえで、最後にそのコードのまま呼び出し元へ返すのがポイントです。0で返すと、配布ツール側が再起動の必要を知る手段を失います(「PowerShellのエラー処理と再実行設計」) - インストーラーの引数の引用符に注意する。
Start-Process -ArgumentListは配列を空白で連結するだけなので、引数の区切りは保持されません。空白を含むパスは設定ファイル側で引用符付きにするか、ProcessStartInfo.ArgumentList(PowerShell 7)を使ってください(「PowerShellから外部exeを正しく呼ぶ」)
6. PowerShellモジュールからwingetを使う
コマンドラインの出力を文字列で解析するより、PowerShellモジュールを使うほうが堅牢です。Microsoft.WinGet.Client には Find-WinGetPackage / Install-WinGetPackage / Get-WinGetPackage / Update-WinGetPackage などのコマンドレットがあります。4
Install-Module -Name Microsoft.WinGet.Client -Scope AllUsers -Force
# 導入済みかを確認してから入れる(冪等)
foreach ($id in 'Google.Chrome', 'Microsoft.VisualStudioCode', '7zip.7zip') {
if (Get-WinGetPackage -Id $id -ErrorAction SilentlyContinue) {
Write-Verbose "導入済み: $id"
continue
}
Install-WinGetPackage -Id $id -Mode Silent -Scope System
}
オブジェクトとして結果が返るため、成否判定や一覧の突合がそのまま書けるのが利点です。
7. 無人実行・システムコンテキストの注意
キッティングを完全自動化しようとすると、必ず「どのアカウントで実行するか」の問題にぶつかります。
- wingetはユーザーコンテキストでの実行を前提とした部分があります。システムコンテキストでの実行は、Microsoftが今後の機能として挙げている段階です3
- 昇格が必要な処理と、ユーザー固有の処理は分けます。マシン全体の設定は管理者権限で、ユーザープロファイル配下の設定は初回ログオン時に実行する構成が扱いやすくなります
- 必ず実際の実行アカウントで検証してください。「手元の管理者アカウントでは動いたのに、配布したら動かない」は、この分野で最も多い失敗です(「タスクスケジューラのタスクが実行されない」)
8. 実務の定石(判断表)
| やること | 手段 | 補足 |
|---|---|---|
| 市販・OSS製品の導入 | winget install / configure | --silent --accept-* は必須1 |
| 既存標準機の構成の吸い出し | winget export |
設定は含まれない。出発点として使う2 |
| アプリ + Windows設定を宣言的に | winget configure(YAML) |
Win10 1809以降 + winget 1.6以降3 |
| レジストリ・Windows機能・社内アプリ | PowerShell(管理者) | 状態確認してから変更(冪等) |
| 共有ドライブ・共有プリンター・ユーザー固有の設定 | PowerShell(ログオン時・非昇格) | 昇格・SYSTEMで作ると利用者から見えない |
| 社内独自アプリ | PowerShell + サイレントインストーラー | 専用リポジトリ構築は小規模には過剰 |
| PowerShellから制御したい | Microsoft.WinGet.Client |
出力の文字列解析が不要になる4 |
| 無人実行 | 実行アカウントで検証 | システムコンテキストは制約あり3 |
| 実施記録 | Start-Transcript + 終了コード |
「この端末に何をしたか」を残す |
| 再起動が必要な場合 | 終了コード3010 / 1641を返す | どちらも成功。0で返すと配布ツールが再起動を認識できない6 |
9. まとめ
- キッティング手順書は、実行可能なファイルに置き換えられます。アプリ導入はwinget、それ以外の設定はPowerShellという分担が現実的です。
winget installでは--silentと--accept-package-agreements--accept-source-agreementsを必ず付けます。付け忘れると無人実行が止まります。winget exportは既存の標準機から構成を吸い出せますが、設定やwinget管理外のアプリは含まれません。- WinGet Configuration(
winget configure)を使えば、アプリと設定を宣言的な1ファイルにまとめられ、再実行に強い構成になります。 - PowerShell側の処理は、必ず「現在の状態を確認してから変更する」書き方にして冪等性を確保してください。
- マシン全体の設定は管理者権限で、ネットワークドライブや共有プリンターなどユーザー固有の設定はログオン時に非昇格で、と実行フェーズを分けてください。昇格セッションやSYSTEMで作った接続は利用者に見えません。
- 無人実行では実行アカウントの検証が最重要です。システムコンテキストでのwinget実行には制約があることを前提に設計してください。
サンプルコードのダウンロード
この記事で扱ったコードは、そのまま動かせる形にまとめて配布しています。管理者フェーズ・ユーザーフェーズ・設定ファイルの完全な例が入っています。
この記事のサンプルは、Windowsやテナントに依存するため実行検証はしていません。構文解析とPSScriptAnalyzerによる静的解析までは全ファイルに対して実施していますが、動作は必ずご自身の検証機で確認してください。
# 構文解析 + 静的解析(Windows以外でも実行できる)
./Invoke-SampleTests.ps1
設定値(パス、サーバー名、テナントIDなど)は例です。そのまま本番環境で実行せず、自社の環境に合わせて読み替えてください。
関連記事
- PowerShellから外部exeを正しく呼ぶ ── 引数のクォート・終了コード・文字化けの落とし穴
- PowerShellのエラー処理と再実行設計 ── try/catchが効かない罠からexit code・リトライの定石まで
- Write-Hostをやめる ── PowerShellの出力ストリームとログ設計
- タスクスケジューラのタスクが実行されない・0x1で終わる ── 原因の切り分けと安全な運用設計
- キオスクモードで業務端末を固める ── Assigned Access・Shell Launcherの選び方と運用設計
- Windows 10サポート終了後の現実解 ── ESU・LTSC・買い替えの判断表
関連する相談領域
合同会社小村ソフトでは、PCキッティングや社内標準環境の自動化、手順書として属人化している運用の実行可能化、配布スクリプトの設計支援を扱っています。
参考リンク
-
Microsoft Learn, install コマンド (winget). –id / -e による対象の指定、–silentによる無人インストール、–accept-package-agreements / –accept-source-agreements による使用条件への同意、–scopeによるインストール範囲(user / machine)の指定について。あわせてUse WinGet to install and manage applicationsのコマンド一覧について。 ↩ ↩2 ↩3
-
Microsoft Learn, export コマンド (winget). インストール済みパッケージの一覧をJSONへ書き出せること、–include-versionsによるバージョンの記録、import コマンドによる復元と–ignore-unavailableの動作、エクスポートの対象がwinget管理下のパッケージに限られること、および出力JSONの階層(Sources / Packages / PackageIdentifier / Version、Versionは任意)について。JSONの構造はpackages.schema.2.0.jsonにも定義されており、トップレベルがWinGetVersion・CreationDate・Sources、Sourcesの各要素がSourceDetails(Name / Identifier / Argument / Type)とPackagesを持つこと、Packagesの各要素がPackageIdentifierを必須としVersion等を任意で持つことについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, WinGet Configuration. WinGet ConfigurationがYAMLで望ましい状態を宣言しPowerShell DSCを用いて適用する仕組みであること、無人セットアップに利用できること、Windows 10 バージョン1809(ビルド17763)以降またはWindows 11とWinGet v1.6.2631以降が必要であること、管理者シェルから実行した場合のUACの扱い、システムコンテキストでの実行が今後の開発項目として挙げられていることについて。あわせてconfigure コマンドのshow / –accept-configuration-agreementsについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
GitHub, microsoft/winget-cli ─ Microsoft.WinGet.Client PowerShell モジュール. PowerShell GalleryからMicrosoft.WinGet.Clientモジュールを導入できること、Find-WinGetPackage / Get-WinGetPackage / Install-WinGetPackage / Update-WinGetPackage / Uninstall-WinGetPackage などのコマンドレットが提供され、結果をオブジェクトとして扱えることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, show コマンド (winget). 指定したアプリケーションの詳細(メタデータとインストーラー情報)を表示するコマンドであること、–scopeオプションでインストールスコープ(user / machine)を選べること、表示されるインストーラー情報が指定した引数とWinGetの判断にもとづくものであることについて。 ↩
-
Microsoft Learn, Windows インストーラーのエラー コード. ERROR_SUCCESS_REBOOT_REQUIRED(3010)が「変更を有効にするには再起動が必要。インストール自体は成功」であること、ERROR_SUCCESS_REBOOT_INITIATED(1641)が「インストーラーが再起動を開始した。成功を示すコード」であることについて。 ↩ ↩2
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
PowerShellスクリプトの引数設計とモジュール化 ── 「動くスクリプト」から「人に渡せるスクリプト」へ
PowerShellスクリプトを他人に渡せる品質に引き上げる手順を整理します。paramブロックと[CmdletBinding()]、入力検証、パイプライン入力、-WhatIf対応、.psm1モジュール化、社内共有とGit管理の勘所まで解説します。
PowerShellのセキュリティ強化 ── ログ・AMSI・言語モード・JEA
PowerShellを禁止せずに安全に使うための実務をまとめます。スクリプトブロックログとトランスクリプションの有効化、AMSIと古いバージョンの無効化、言語モードによる制限、JEAによる権限委任までを解説します。
Get-WinEventでイベントログを実務的に調べる ── 絞り込みの速さが調査時間を決める
Windowsのイベントログ調査をPowerShellで効率化する方法をまとめます。Where-Objectで絞ると遅い理由、FilterHashtableとXPathの使い分け、再起動やログオン・アプリ異常終了の調査レシピ、複数台からの収集までを解説します。
PowerShellモジュールの社内配布と更新 ── PSResourceGetと社内リポジトリ
共有フォルダーのps1をコピーして使い回す運用から卒業する方法をまとめます。モジュールマニフェストの書き方、バージョニング、PSResourceGetによる社内リポジトリの構築と配布・更新、署名との組み合わせまで解説します。
PowerShellでREST APIと連携する ── Invoke-RestMethodの実務
PowerShellから社内APIやSaaSのREST APIを呼ぶ実務をまとめます。認証ヘッダーの渡し方、日本語JSONの文字化け対策、4xx/5xxのエラー処理、429のリトライ、ページング、プロキシとTLSの落とし穴まで解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- winget importで、キッティングは全部自動化できますか?
- アプリのインストールまでは自動化できますが、それだけでは足りません。winget importが再現するのはパッケージの一覧であり、アプリ内の設定、プリンターの追加、ネットワークドライブの割り当て、電源設定、レジストリによる社内標準設定などは対象外です。また、winget以外の方法で入れたアプリや、社内独自の業務アプリはエクスポートに含まれません。実務では、アプリ導入をwingetに任せ、残りの設定をPowerShellスクリプトで補う二段構えが現実的です。
- wingetとWinGet Configuration(winget configure)はどう違いますか?
- wingetのinstall/importは「この順番でこれを入れる」という手続き的な指示ですが、WinGet Configurationは「最終的にこの状態であってほしい」という宣言をYAMLファイルに書く方式です。内部でPowerShell DSCを使い、アプリの導入だけでなくWindowsの設定やアプリの構成まで1つのファイルで表現できます。すでに望ましい状態なら何もしないため、途中で失敗しても同じファイルを再実行すればよく、キッティングのやり直しに強いのが利点です。Windows 10 1809以降とwinget 1.6以降が必要です。
- タスクスケジューラやIntuneからSYSTEM権限でwingetを実行しても大丈夫ですか?
- 注意が必要です。wingetはユーザーコンテキストでの実行を前提とした部分があり、システムコンテキストでの実行はMicrosoftが今後の開発項目として挙げている段階です。実務では、全ユーザー向けにインストールする--scope machineを使う、PowerShellモジュール(Microsoft.WinGet.Client)経由で実行する、あるいは初回ログオン時にユーザーコンテキストで走らせるといった回避策を取ります。いずれにしても、実際に使う実行アカウントで必ず検証してください。
- キッティングスクリプトは何度実行しても安全であるべきですか?
- はい。冪等性(何度実行しても同じ結果になること)は必須と考えてください。キッティングは途中で失敗することが日常的にあり、そのたびに最初からやり直せる必要があります。フォルダー作成はTest-Pathで存在確認してから、レジストリ設定は現在値を確認してから、アプリ導入は導入済みかを確認してから実行する、という書き方にしておけば、失敗した箇所から再開できます。WinGet Configurationはこの考え方が最初から組み込まれています。
- 社内独自の業務アプリはwingetで配れますか?
- 社内向けのプライベートリポジトリ(REST APIのソース)を用意すれば可能ですが、そのためのサーバーを構築・維持する手間があります。数本のアプリのためであれば、PowerShellスクリプトから共有フォルダー上のインストーラーをサイレント実行するほうが簡単です。市販・OSS製品はwingetに任せ、社内アプリはPowerShellで入れる、という切り分けが小規模組織では最も現実的です。