更新履歴(5件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- 社内リポジトリの権限設定を、`icacls /grant`で足すだけから、許可リストで置き換える形に直しました。`/grant`は既存のACEを消さないため、以前このフォルダーが別の共有で公開されていた、誰かに一時的に変更権限を付けた、といった経緯があると、その書き込み経路が残ります。全端末が`Trusted = $true`で登録する前提のリポジトリなので、残った1人が全社のPowerShell実行環境に任意のコードを配れることになります。継承の切断だけでも足りず(`SetAccessRuleProtection`が扱えるのは継承ACEだけです)、直接付与も落としてから許可リストの主体を入れ直し、1回の`Set-Acl`で適用するようにしました。
- 開発から社内リポジトリを経て利用側へ届くまでの構成図を1章末に追加しました。あわせて対象読者と前提環境の表、共有フォルダーで配る場合の権限設定の例、AllUsersスコープに管理者権限が必要である旨を追加しています。
- 本文中の関連記事へのリンクの文言が、リンク先の現在のタイトルと食い違っていたのを、実際のタイトルに揃えました。本文の内容は変えていません。
- 初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21547456)
以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。
小村 豪(2026)「PowerShellモジュールの社内配布と更新 ── PSResourceGetと社内リポジトリ」合同会社小村ソフト. https://comcomponent.com/blog/powershell-module-distribution-psresourceget/
- DOI(登録済みアーカイブ)
- 10.5281/zenodo.21547456
- DOI(前回登録した版)
- 10.5281/zenodo.21733163
「集計.ps1」「集計_v2.ps1」「集計_修正版_最新.ps1」。共有フォルダーから各自がコピーして使う運用では、元のファイルを直しても手元のコピーには届かず、どの版がどこで動いているか分からなくなります。
この問題への基本方針は、スクリプトをモジュールにし、バージョンを付け、リポジトリから配布することです。PowerShell 7.4以降には、そのためのPSResourceGetが同梱されています。1 ただし、配布先に新版を置くこと、各端末へ入れること、本番の更新を決めることは、別々の作業として管理します。
この記事では、共有フォルダーでスクリプトを配っている情シス・運用担当者に向けて、実行環境の確認 → 配布物の作成 → 配布先の保護 → 署名 → 公開と取得 → 継続運用の順に説明します。専用のリポジトリサーバーを立てず、ファイル共有から始める構成です。関数のモジュール化そのものは「PowerShellの引数設計とモジュール化」を参照してください。
1. まず結論
新規の配布にはPSResourceGetを使い、配る版と使う環境を明示します。従来のPowerShellGet 2.2.5とは並存できるので、既存スクリプトを一斉に書き換える必要はありません。1
最初に決めることは、次の3つです。
| 決めること | この記事の基本方針 | 説明する章 |
|---|---|---|
| 何を配るか | マニフェストに版番号・依存関係・公開関数を明示する。破壊的変更はメジャーバージョンで示す | 3章 |
| 誰が配れるか | ファイル共有から始め、配布担当者だけが書き込み、利用者は読み取りだけにする | 4章 |
| どう使い続けるか | 実行アカウントから見える場所に入れ、署名要件を満たし、本番は検証して計画的に更新する | 2章、5〜7章 |
この方針を支えるコマンドは、配布先の登録がRegister-PSResourceRepository、公開がPublish-PSResource、取得がInstall-PSResource、更新がUpdate-PSResourceです。234
flowchart TB
accTitle: モジュールが利用側へ届くまで
accDescr: テストしたモジュールを配布担当者が公開し、利用側が用途に合うスコープへ取得する。
DEV["開発側がモジュールを作る"]
TEST["Pesterと静的解析で確認"]
REPO["社内リポジトリ"]
USER["利用側のPC"]
SRV["無人実行のサーバー"]
DEV --> TEST
TEST -->|"Publish-PSResource"| REPO
REPO -->|"Install-PSResource"| USER
REPO -->|"AllUsers等へ取得"| SRV
図1: テストしたモジュールを配布担当者が公開し、利用側が用途に合うスコープへ取得する。
リポジトリは配布側・利用側の双方で最初に登録します。以降は公開・取得・更新の操作を使います。この配布経路のセキュリティ境界は「書き込めるのは配布担当者だけ」というアクセス制御です。信頼済みとして登録する前に、4章の権限設定を済ませます。
1.1. 目的から読む場所を選ぶ
初めて構築するなら2〜6章を順に読みます。「入れたのに見つからない」なら2章、「署名したのに動かない」なら5章、無人実行の更新や導入状況の把握なら7章から確認してください。選択肢だけを比較する場合は8章の判断表を使えます。
1.2. 共有フォルダーのps1で何が管理できなくなるか
| 症状 | 根本原因 |
|---|---|
| どの版が動いているか分からない | バージョン番号という概念がない |
| 修正しても全員に反映されない | 各自がコピーを持っている |
| 誰が使っているか分からない | 取得の記録が残らない(※後述のとおり、これだけは配布方式の選択が必要) |
| 一部の環境だけ壊れる | 依存関係(必要なモジュール・PSバージョン)が宣言されていない |
| 直したいが影響範囲が読めない | 公開している関数と内部関数の区別がない |
モジュール化とリポジトリ配布は、版番号、更新の配り方、依存関係、公開範囲の管理に効きます。ただし、「誰が使っているか」は配布方式だけでは解決しません。ファイル共有リポジトリで取得履歴を把握するには、読み取り監査や端末ごとの導入状況の集約などを別に用意します。方法は7章で説明します。
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全22件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. 準備 ── PowerShellと実行アカウントを確認する
配布に使うPowerShellと、モジュールを実行するPowerShellを先に確認します。PSResourceGetの導入、モジュールの探索先、インストールする権限を、公開操作より前に揃えておくためです。
2.1. 対象読者・前提環境
| 項目 | 内容 |
|---|---|
| 対象読者 | 共有フォルダーに置いた .ps1 を配っている情シス・運用担当 |
| 対象バージョン | Windows PowerShell 5.1とPowerShell 7.xの両方。ただしPSResourceGetが同梱されているのはPowerShell 7.4以降で、5.1では配る側・使う側の双方で事前インストールが必要です(次節)1 |
| 既存環境との併存 | PSResourceGetは従来のPowerShellGet 2.2.5と並存できます。既存スクリプトを書き換えずに導入できます1 |
| サンプルの検証環境 | 記事末尾で配布しているサンプルコードは PowerShell 7.6 で実行して検証しています |
| 必要な権限 | リポジトリ用共有フォルダーの作成と権限設定、-Scope AllUsers でのインストールには管理者権限が必要です |
2.2. Windows PowerShell 5.1ではPSResourceGetを先に入れる
PowerShell 7.4以降には同梱されていますが、Windows PowerShell 5.1には入っていません。5.1でこの後のコマンドを使う場合は、配る側・使う側の双方で事前導入が必要です。使うエディションで次の処理を行います。1
この例は-Scope AllUsersなので、管理者として起動したPowerShellで実行します。5
# 【Windows PowerShell 5.1のみ】PSResourceGetを導入する。
# 5.1と7では読み込まれるモジュールパスが別なので、使うエディションで実行すること
if (-not (Get-Module -ListAvailable -Name Microsoft.PowerShell.PSResourceGet)) {
Install-Module -Name Microsoft.PowerShell.PSResourceGet -Scope AllUsers -Force
}
2.3. モジュールの探索パスと5.1/7の違い
PowerShellは $env:PSModulePath に列挙されたフォルダーからモジュールを探します。Windows PowerShell 5.1とPowerShell 7では既定のパスが異なります。5
| エディション | ユーザースコープの既定パス |
|---|---|
| Windows PowerShell 5.1 | %USERPROFILE%\Documents\WindowsPowerShell\Modules |
| PowerShell 7 | %USERPROFILE%\Documents\PowerShell\Modules |
両方で使うモジュールは、CompatiblePSEditions に Desktop と Core の両方を宣言し、両方の環境でテストしてから配布します。互換性の検証にはPSScriptAnalyzerの PSUseCompatibleSyntax が役立ちます(「PSScriptAnalyzerでPowerShellスクリプトの品質を守る」)。
トラブル時の確認は次の3点です。
$env:PSModulePath -split ';' # 探索パス
Get-Module -Name KsOps -ListAvailable # 見つかっているか・どの版か
(Get-Module KsOps -ListAvailable).ModuleBase # 実際に読まれる場所
上の-ListAvailableで確認するのは、その環境で見つかるモジュールと配置先です。インストールした端末だけでなく、実際に動かすエディションとアカウントで確認します。5
2.4. 無人実行では実行アカウントから見える場所に入れる
-Scope の使い分けは重要です。タスクスケジューラの無人実行で使うモジュールは AllUsers(またはサービスアカウント自身の環境)に入れる必要があります。「自分の環境では動くのに夜間バッチだけ コマンドレットが認識されません で落ちる」の典型原因が、CurrentUser スコープへのインストールです。45
なお -Scope AllUsers は全ユーザー共通の場所(Program Files 配下)へ書き込むため、管理者として起動したPowerShellでないと失敗します。5 初回の導入で「アクセスが拒否されました」となったら、まず昇格しているかを確認してください。
flowchart TB
accTitle: 実行環境とインストール先を合わせる
accDescr: エディションと実行アカウントを確認してから配置先を決め、その環境でモジュールが見えるかを確かめる。
A["使うPowerShellを確認"]
B["実行アカウントを確認"]
C["CurrentUserかAllUsers等"]
D["その環境の探索パスを確認"]
A --> B --> C --> D
C -.-> E["AllUsersへの配置は要管理者"]
図2: エディションと実行アカウントを確認してから配置先を決め、その環境でモジュールが見えるかを確かめる。
3. 配布物を作る ── マニフェスト・公開関数・バージョン
3.1. 実装と配布情報を分ける
配布可能なモジュールの最小形は、フォルダー・.psm1・.psd1 の3点です。
KsOps\
KsOps.psd1 ← マニフェスト(バージョン・公開関数・依存関係)
KsOps.psm1 ← 実装(またはPublic/Privateフォルダーからのドットソース)
Public\
Get-KsShareUsage.ps1
Invoke-KsArchive.ps1
Private\
ConvertTo-KsSize.ps1
マニフェストは New-ModuleManifest で雛形を作り、必要な項目を埋めます。6
$manifest = @{
Path = '.\KsOps\KsOps.psd1'
RootModule = 'KsOps.psm1'
ModuleVersion = '1.0.0'
GUID = [guid]::NewGuid().Guid
Author = '情報システム部'
CompanyName = '株式会社サンプル'
Description = '社内運用スクリプト共通モジュール(ファイルサーバー棚卸し・アーカイブ)'
PowerShellVersion = '5.1'
CompatiblePSEditions = @('Desktop', 'Core') # 5.1と7の両方で使う場合
# ワイルドカードにしない。公開するものだけを明示する
FunctionsToExport = @('Get-KsShareUsage', 'Invoke-KsArchive')
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
RequiredModules = @() # 依存があればここで宣言する
Tags = @('internal', 'operations')
ProjectUri = 'https://git.example.co.jp/it/ksops'
}
New-ModuleManifest @manifest
flowchart TB
accTitle: マニフェストが配布物を説明する
accDescr: マニフェストが実装の入口を示し、版番号・公開関数・依存関係を利用側へ伝える。
M["KsOps.psd1"]
I["KsOps.psm1を読み込む"]
V["版番号と対応環境"]
F["公開関数と依存関係"]
M --> I
M --> V
M --> F
図3: マニフェストが実装の入口を示し、版番号・公開関数・依存関係を利用側へ伝える。
3.2. 公開関数だけを明示する
.psm1 は、Public/Privateのスクリプトを読み込んで公開関数だけをエクスポートする定型で書けます。
# KsOps.psm1
$public = @(Get-ChildItem -Path "$PSScriptRoot\Public\*.ps1" -ErrorAction SilentlyContinue)
$private = @(Get-ChildItem -Path "$PSScriptRoot\Private\*.ps1" -ErrorAction SilentlyContinue)
foreach ($file in @($public + $private)) {
try { . $file.FullName }
catch { throw "モジュールの読み込みに失敗しました: $($file.FullName) ── $_" }
}
# 公開するのはPublic配下の関数だけ(マニフェストの宣言と一致させる)
Export-ModuleMember -Function $public.BaseName
FunctionsToExport をワイルドカードにしない理由は2つあります。ひとつはコマンド探索の性能で、明示すればモジュール本体を解析せずに「どのコマンドがどこにあるか」を判断できます。もうひとつは設計上の理由で、内部ヘルパーが外から呼べると、それが事実上の公開APIになり、後で変更できなくなります。6
上の例はCompatiblePSEditionsにDesktopとCoreを宣言していますが、宣言しただけで両方で動くようになるわけではありません。2章の導入環境・探索パスを確認し、5.1と7の両方でテストしてから配布してください。
flowchart TB
accTitle: 公開関数を外部との約束にする
accDescr: 実装は公開用と内部用の両方を読み込み、利用側へ出す関数だけをマニフェストと一致させる。
L["psm1がスクリプトを読む"]
P["Publicの関数"]
H["Privateのヘルパー"]
E["公開関数だけエクスポート"]
M["FunctionsToExportと一致"]
L --> P --> E --> M
L --> H
H -.-> N["利用側のAPIにしない"]
図4: 実装は公開用と内部用の両方を読み込み、利用側へ出す関数だけをマニフェストと一致させる。
3.3. バージョニングの決め方
利用側が安心して更新できるかどうかは、バージョン番号の付け方で決まります。セマンティックバージョニング(メジャー.マイナー.パッチ)を採用し、破壊的変更を必ずメジャーで表現してください。7
| 変更内容 | 上げる箇所 |
|---|---|
| パラメーター名の変更、関数の削除、戻り値の形の変更 | メジャー(1.2.3 → 2.0.0) |
| 関数やパラメーターの追加(既存はそのまま動く) | マイナー(1.2.3 → 1.3.0) |
| 不具合修正のみ | パッチ(1.2.3 → 1.2.4) |
プレリリース版は明示して取得する
検証用の版を配りたいときはプレリリース版が使えます。マニフェストの PrivateData.PSData.Prerelease に beta1 のような文字列を設定すると(バージョンとの区切りのハイフンは自動的に付き、1.3.0-beta1 になります)、通常の取得では降ってこず、-Prerelease を明示したときだけインストールされます。文字列に使えるのはASCIIの英数字とハイフンだけで、ピリオドや + は使えません。7
破壊的変更の扱いについては、インターフェース設計の考え方が参考になります(「DLL・COMインターフェースの後方互換性」)。
flowchart TB
accTitle: 変更の性質を版番号に反映する
accDescr: 変更の互換性を判断して版番号と変更履歴へ反映し、検証してから利用側の更新を決める。
C["変更内容と互換性を確認"]
V["版番号へ反映"]
R["破壊的変更を履歴に記載"]
T["新版を検証"]
U["利用側の更新を計画"]
C --> V --> R --> T --> U
図5: 変更の互換性を判断して版番号と変更履歴へ反映し、検証してから利用側の更新を決める。
4. 配布先を作る ── 権限を整えてから登録する
4.1. まずはファイル共有で始める
PSResourceGetは、ファイル共有上のフォルダーをリポジトリとして扱えます。UNCパスを登録する構成なら、専用のリポジトリサーバーやデータベースは不要です。2 ただし、共有を作ることと、各端末にその配布先を登録することは別です。先にサーバー側の権限を整えます。
4.2. 書き込み権限を配布担当者だけにする
共有フォルダーのアクセス権は「配布担当者だけ書き込み可、利用者は読み取りのみ」にします。ここが緩いと、誰でも任意のコードを全社に配れる経路になってしまいます。共有アクセス許可とNTFSのアクセス許可は両方設定してください(実効権限は厳しいほうになります)。8
次は管理者がファイルサーバー側で行う設定例です。組織名・グループ名・パスは自社の値へ読み替え、配布物を置いて利用を始める前に設定結果を確認してください。
# ファイルサーバー側で実行する(要管理者権限)
$path = 'D:\PSRepository'
$null = New-Item -Path $path -ItemType Directory -Force
# 共有: 利用者は読み取りのみ、配布担当者だけが書き込める
New-SmbShare -Name 'PSRepository' -Path $path `
-ReadAccess 'EXAMPLE\Domain Users' -ChangeAccess 'EXAMPLE\モジュール配布担当'
# NTFS: 「足す」のではなく「許可リストで置き換える」。
# New-Item -Force は既存フォルダーでも成功するので、以前このフォルダーが
# 別の共有で公開されていた・誰かに一時的に変更権限を付けた、という経緯があると、
# icacls /grant を足すだけではその書き込み経路が残る。全端末が Trusted として
# 登録するリポジトリなので、残った1人が全社にコードを配れることになる
$acl = Get-Acl -Path $path
$acl.SetAccessRuleProtection($true, $false) # 継承を切り、継承ACEも引き継がない
foreach ($ace in @($acl.Access)) { # 直接付与されたACEも落とす
[void]$acl.RemoveAccessRuleSpecific($ace)
}
# 残すのはここに書いた主体だけ
$allow = @(
@{ Id = 'EXAMPLE\モジュール配布担当'; Rights = 'Modify' } # 公開できる
@{ Id = 'EXAMPLE\Domain Users'; Rights = 'ReadAndExecute' } # 取得だけ
@{ Id = 'BUILTIN\Administrators'; Rights = 'FullControl' }
@{ Id = 'NT AUTHORITY\SYSTEM'; Rights = 'FullControl' }
)
foreach ($a in $allow) {
$acl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new(
$a.Id, $a.Rights, 'ContainerInherit, ObjectInherit', 'None', 'Allow'))
}
# 「空にする」と「入れ直す」を別々に Set-Acl すると、誰も触れない瞬間ができる。
# 1回で適用する
Set-Acl -Path $path -AclObject $acl
icacls $path # 設定結果を確認する。意図しない主体が並んでいないかを目で見る
既存の権限を足し算で残さない
icacls /grant を足すだけで済ませないでください。/grant は既存のACEを消しません。このフォルダーに書ける経路を持つ人が他に居ても、そのまま残ります。8 しかも、このリポジトリは全端末が Trusted = $true で登録する前提です。信頼済みリポジトリに置かれたモジュールは、確認なしにインストールされます。残った1人が、全社の PowerShell 実行環境に任意のコードを配れることになり、「配布担当者だけが公開できる」という前提がその時点で崩れます。
継承の切断(SetAccessRuleProtection($true, $false))だけでも足りません。このメソッドが扱えるのは継承ACEだけで、そのフォルダーに直接付与されたACEは対象外だからです。8 上のように直接付与も落としてから、許可リストの主体だけを入れ直します。新規に作ったフォルダーなら結果は同じですが、既存フォルダーを流用したときだけ壊れる種類の問題なので、手順に入れておくほうが確実です。
flowchart TB
accTitle: NTFS権限は許可リストで置き換える
accDescr: 継承された権限と直接付与された権限を整理してから、許可した主体だけを一度のSet-Aclで反映する。
A["既存ACLを取得"]
B["継承を切り引き継がない"]
C["直接付与のACEも外す"]
D["許可する主体を入れ直す"]
E["Set-Aclを一度だけ実行"]
A --> B --> C --> D --> E
図6: 継承された権限と直接付与された権限を整理してから、許可した主体だけを一度のSet-Aclで反映する。
配布担当はグループで管理する
「配布担当者」は個人アカウントではなくグループにしてください。担当者の異動でモジュールを更新できなくなる、という止まり方が実際に起こります。認証が必要なNuGet互換フィードを使う場合は、利用側に配る資格情報を読み取り専用の権限で発行し、公開用の資格情報(APIキー)と分けます。共有フォルダーの権限設計そのものは「PowerShellでファイルサーバーを棚卸しする」も参考にしてください。
4.3. 配布側・利用側でリポジトリを登録する
共有とNTFSの両方を確認したら、配布側・利用側でそれぞれ次を実行します。Trusted = $trueは信頼済みリポジトリとして扱う設定です。この設定をするから安全になるのではなく、公開できる人を制限してあることが前提です。2
# 【配布側・利用側の両方で1回だけ実行】社内リポジトリを登録する
# Trusted: 社内配布物なので信頼済みとして扱う / Priority: PSGalleryより優先して検索する
$repo = @{
Name = 'KsInternal'
Uri = '\\fileserver\PSRepository'
Trusted = $true
Priority = 10
}
Register-PSResourceRepository @repo
Get-PSResourceRepository | Format-Table Name, Uri, Trusted, Priority
flowchart TB
accTitle: リポジトリの構築と登録は別の作業
accDescr: サーバー側で配布先を保護した後、配布側と利用側が同じリポジトリを登録して使う。
S["共有とNTFSの権限を設定"]
V["書き込める主体を確認"]
P["配布側で登録"]
U["利用側で登録"]
S --> V
V --> P
V --> U
図7: サーバー側で配布先を保護した後、配布側と利用側が同じリポジトリを登録して使う。
4.4. 認証・監査が必要ならフィードを検討する
より本格的に運用するなら、Azure ArtifactsやGitHub PackagesのようなNuGet互換フィードを登録します。認証が必要なリポジトリでは、資格情報をSecretManagementの保管庫から参照する構成にできます(「PowerShellでの資格情報の安全な扱い」)。2
取得履歴の把握は、リポジトリを登録するだけでは完了しません。ファイル共有を使い続ける場合の読み取り監査や導入状況の集約は、7章で運用として決めます。
flowchart TB
accTitle: 公開用と取得用の資格情報を分ける
accDescr: 認証付きフィードでは公開用の資格情報と、利用側に渡す読み取り専用の資格情報を分離する。
P["配布担当者"]
W["公開用の資格情報"]
R["NuGet互換フィード"]
U["利用者"]
K["読み取り専用の資格情報"]
P --> W --> R
U --> K --> R
図8: 認証付きフィードでは公開用の資格情報と、利用側に渡す読み取り専用の資格情報を分離する。
5. 公開前に署名する ── 実行ポリシーと完全性
5.1. AllSignedとカタログの役割を分ける
署名には目的の違う2つの仕組みがあり、混同すると「署名したのに実行できない」ことになります。9
| 仕組み | 何を担保するか | 実行ポリシー AllSigned を満たすか |
|---|---|---|
Authenticode署名(Set-AuthenticodeSignature) |
個々のスクリプトファイルの発行元と完全性 | 満たす(各ファイルに署名が必要) |
カタログ署名(New-FileCatalog + 署名) |
モジュール一式(パッケージ)の完全性 | 満たさない |
実行ポリシーは、読み込もうとしている .ps1 / .psm1 そのもののAuthenticode署名を検証します。カタログファイル(.cat)に署名しても、中のスクリプトファイルは未署名のままなので、AllSigned 環境では実行がブロックされます。したがって AllSigned を運用しているなら、実行される各ファイルに署名するのが必須です。
flowchart TB
accTitle: 署名は二つの目的を分けて考える
accDescr: 実行対象ファイルの署名を確かめることと、カタログでモジュール一式の完全性を確かめることを区別する。
A["配布するモジュール"]
B["各スクリプトへ署名"]
C["一式のカタログを作り署名"]
D["AllSignedの署名要件"]
E["取得した一式の完全性検証"]
A --> B --> D
B --> C --> E
C -.-> F["各ファイルの署名の代わりではない"]
図9: 実行対象ファイルの署名を確かめることと、カタログでモジュール一式の完全性を確かめることを区別する。
5.2. 各ファイルへの署名後にカタログを作る
# (1) AllSigned環境で必須: 実行される各ファイルにAuthenticode署名する
Get-ChildItem .\KsOps -Recurse -Include *.ps1, *.psm1, *.psd1 | ForEach-Object {
Set-AuthenticodeSignature -FilePath $_.FullName -Certificate $cert `
-TimestampServer 'http://timestamp.digicert.com'
}
# (2) 加えて、パッケージ全体の改ざん検知にカタログを併用する
New-FileCatalog -Path .\KsOps -CatalogFilePath .\KsOps\KsOps.cat -CatalogVersion 2
Set-AuthenticodeSignature -FilePath .\KsOps\KsOps.cat -Certificate $cert `
-TimestampServer 'http://timestamp.digicert.com'
# 利用側での検証(実行ポリシーとは別に、配布物が壊れていないかを確かめる)
Test-FileCatalog -Path .\KsOps -CatalogFilePath .\KsOps\KsOps.cat -Detailed
カタログの価値は「取得したモジュール一式が配布時と同一か」を確認できる点にあり、PSResourceGet側にも署名・カタログを検証する -AuthenticodeCheck があります。4 実行の可否はAuthenticode署名、配布物の完全性はカタログ、と役割を分けて理解してください。
タイムスタンプを付けておくと、署名証明書の有効期限が切れた後も署名が有効なままになります。実行ポリシーと署名運用の全体像は「PowerShellの実行ポリシーとスクリプト署名」にまとめています。
6. 公開・取得・更新 ── 配布側と利用側の作業
2〜5章で、実行環境、配布物、配布先の権限、必要な署名を揃えました。ここからが日々の公開・取得・更新です。
次のブロックは、立場と目的ごとに選ぶコマンド例です。上から全部を順番に実行する手順ではありません。配布側は公開、利用側は検索と取得を行い、更新は7章の方針に従って実施します。1.2.3は版指定の例なので、実際に公開・検証した版へ読み替えてください。
# 【配布側】モジュールを公開する
Publish-PSResource -Path .\KsOps -Repository 'KsInternal'
# 【利用側】検索して入れる
Find-PSResource -Name 'KsOps' -Repository 'KsInternal'
Install-PSResource -Name 'KsOps' -Repository 'KsInternal' -Scope CurrentUser
# バージョンを固定して入れる(本番サーバーはこちらを推奨)
Install-PSResource -Name 'KsOps' -Version '1.2.3' -Repository 'KsInternal' -Scope AllUsers
# 更新する
Update-PSResource -Name 'KsOps' -Repository 'KsInternal'
# 何が入っているかを確認する(あくまで「この端末」の状態)
Get-InstalledPSResource -Name 'KsOps' | Format-Table Name, Version, Repository, InstalledDate
# 全社の導入状況を知りたい場合は、各端末で実行して集約する
Invoke-Command -ComputerName $servers -ScriptBlock {
Get-InstalledPSResource -Name 'KsOps' -ErrorAction SilentlyContinue |
Select-Object Name, Version
} | Sort-Object PSComputerName
6.1. 本番は取得する版を決めてから入れる
本番サーバーでは、検証済みの版を指定して取得する運用を勧めます。固定した版を入れた直後に、版を指定しない更新例まで続けて実行しないでください。無人実行が知らないうちに新版で動くことを避け、更新を計画するのが目的です。4
また、-Scope AllUsersへの配置は管理者権限で行います。CurrentUserで入れる場合は、自分ではなく実際の実行アカウントから見えるかが基準です。2章の探索パスの確認へ戻って切り分けてください。5
flowchart TB
accTitle: 公開と端末への反映を分ける
accDescr: 新版の公開後、利用側で取得または更新を行い、その端末に入った版を確認する。
P["新版をリポジトリへ公開"]
D["利用側が採用する版を決定"]
I["対象端末へ取得・更新"]
C["導入された版を確認"]
P --> D --> I --> C
P -.-> N["公開だけで全端末を更新しない"]
図10: 新版の公開後、利用側で取得または更新を行い、その端末に入った版を確認する。
6.2. 入っている版と利用状況を混同しない
Get-InstalledPSResourceで分かるのは、コマンドを実行した端末の状態です。全社の状況を見る場合は、上のInvoke-Commandの例のように端末ごとの結果を集約します。$serversには確認対象のサーバーを設定します。これも単独の運用例であり、個人PCへインストールするたびに実行する処理ではありません。
7. 運用ルール ── 導入状況と更新を管理する
7.1. 誰が取得したかは別に記録する
ファイル共有リポジトリは手軽ですが、誰がいつ取得したかの記録は、その構成だけでは残りません。利用状況を把握したい場合は、次のいずれかを併用してください。
- 共有フォルダーの読み取り監査を有効にする(ファイル アクセスの監査。「Get-WinEventでイベントログを実務的に調べる」)
- ダウンロード統計が取れるNuGet互換フィード(Azure Artifacts など)を使う
- 各端末で
Get-InstalledPSResourceを実行して結果を集約する(「PowerShell Remoting(WinRM)入門」)
flowchart TB
accTitle: 端末の状態を集約して導入状況を知る
accDescr: 各端末で得られる導入済みモジュールの情報を集め、組織としての状況を確認する。
A["端末Aの導入済み版"]
B["端末Bの導入済み版"]
C["サーバーの導入済み版"]
M["結果を集約"]
R["配布・更新の状況を確認"]
A --> M
B --> M
C --> M
M --> R
図11: 各端末で得られる導入済みモジュールの情報を集め、組織としての状況を確認する。
7.2. 公開・変更・廃止の責任を決める
技術より運用のほうが重要です。最低限、次を決めてください。
- 誰が公開できるか。共有フォルダーの書き込み権限を持つ人=全社にコードを配れる人です
- 変更履歴をどこに書くか。
ReleaseNotes(マニフェストのPrivateData.PSData)かCHANGELOGに、破壊的変更を明記します - 本番サーバーはバージョン固定か。無人実行が依存するモジュールは、固定して計画的に更新するのが安全です
- 廃止の手順。関数を削除するときは、メジャーバージョンを上げ、事前に非推奨を告知します
- テストとlintを通してから公開する。公開はCIから行うのが理想です(「PesterによるPowerShellのテスト整備」)
7.3. 無人実行の更新には検証と計画を挟む
Update-PSResourceで更新はできますが、夜間バッチが依存するモジュールを無条件に自動更新する運用は避けます。検証環境で新版を確認してから、本番の更新を計画します。自動化する場合も、FAQで示した-Version '1.*'のようにメジャーバージョンの範囲を固定する歯止めを設けます。4
flowchart TB
accTitle: 本番更新までの運用を決める
accDescr: テストと静的解析を通した新版を検証し、変更履歴を踏まえて本番の更新を計画する。
A["テストと静的解析"]
B["検証環境で新版を確認"]
C["変更履歴と影響を確認"]
D["本番の更新を計画"]
E["更新後の導入版を確認"]
A --> B --> C --> D --> E
図12: テストと静的解析を通した新版を検証し、変更履歴を踏まえて本番の更新を計画する。
8. 実務の定石(判断表)
| 論点 | 選択肢 | 判断の目安 |
|---|---|---|
| 配布形式 | 共有フォルダーの .ps1 / モジュール + リポジトリ |
バージョンと更新の管理ができるかどうかが分岐点 |
| モジュール管理 | PowerShellGet 2.x / PSResourceGet | 7.4以降は同梱。新規はPSResourceGet1 |
| リポジトリ | ファイル共有 / NuGet互換フィード | まずファイル共有で開始。認証・監査が要るならフィード2 |
| マニフェスト | 省略 / 必須 | バージョンがないと運用が成立しない6 |
| 公開関数 | '*' / 配列で明示 |
探索性能と、内部関数の非公開のため6 |
| インストール先 | CurrentUser / 無人実行はAllUsers | サービスアカウントから見えるかが基準5 |
| 本番の更新 | 自動更新 / バージョン固定 + 計画的更新 | 夜間バッチが勝手に新版で動くのを防ぐ |
| 署名 | なし / 各ファイルのAuthenticode署名(+カタログ) | AllSigned環境ではファイル単位の署名が必須。カタログは完全性検証用9 |
flowchart TB
accTitle: 採用した構成を公開前に点検する
accDescr: 配布物・配布先・実行環境の確認を終え、利用状況の把握と更新方法まで決めてから運用を始める。
A["版番号と公開範囲を確認"]
B["配布先の書き込み権限を確認"]
C["探索先と署名要件を確認"]
D["導入状況と更新方法を決定"]
E["配布運用を開始"]
A --> B --> C --> D --> E
図13: 配布物・配布先・実行環境の確認を終え、利用状況の把握と更新方法まで決めてから運用を始める。
9. まとめ
- 共有フォルダーの
.ps1配布は、バージョン・更新・依存関係・公開範囲のすべてが管理不能になります。モジュール化とリポジトリ配布で解決できます。ただし「誰が使っているか」だけは別です。ファイル共有リポジトリには取得の記録が残らないため、共有フォルダーの読み取り監査、ダウンロード統計の取れるフィード、各端末でのGet-InstalledPSResourceの集約のいずれかを併用してください。 - マニフェストは必須です。
FunctionsToExportは配列で明示し、内部関数は公開しないでください。 - 社内リポジトリはファイル共有をUNCパスで登録するだけで始められます。書き込み権限の管理が実質的なセキュリティ境界です。
- 公開は
Publish-PSResource、取得はInstall-PSResource、更新はUpdate-PSResource。無人実行が使うモジュールはAllUsersスコープに入れます。 - 5.1と7では探索パスが違います。両対応するなら
CompatiblePSEditionsを宣言し、両方でテストしてください。 AllSigned環境では各スクリプトファイルにAuthenticode署名が必要です。カタログ署名は配布物の完全性検証用で、実行の可否とは別物です。タイムスタンプは必ず付けてください。
flowchart TB
accTitle: 配布を継続運用の単位にする
accDescr: モジュールと版番号を起点に、権限を制限した配布先と計画的な更新を組み合わせる。
A["モジュールと版番号"]
B["権限を制限した配布先"]
C["実行環境に合う取得と署名"]
D["導入状況の把握と計画的更新"]
A --> B --> C --> D
図14: モジュールと版番号を起点に、権限を制限した配布先と計画的な更新を組み合わせる。
サンプルコードのダウンロード
この記事で扱ったコードは、そのまま動かせる形にまとめて配布しています。公開/非公開を分けたモジュール一式と、社内リポジトリへの配布手順が入っています。
この記事のサンプルは、PowerShell 7.6 で実際に実行して検証しています(Pester 16件)。zipに含まれる Invoke-SampleTests.ps1 を実行すれば、お手元でも同じ検証を再現できます。
# 構文解析 + 静的解析 + Pesterテスト
./Invoke-SampleTests.ps1
設定値(パス、サーバー名、テナントIDなど)は例です。そのまま本番環境で実行せず、自社の環境に合わせて読み替えてください。
関連記事
- PowerShellスクリプトの引数設計とモジュール化 ── 「動くスクリプト」から「人に渡せるスクリプト」へ
- PowerShellの実行ポリシーとスクリプト署名 ── 「Bypassで蓋をする」運用から卒業する
- PSScriptAnalyzerでPowerShellスクリプトの品質を守る ── ルール選定とCI導入
- PesterによるPowerShellのテスト整備 ── 運用スクリプトを壊しにくくする実務の型
- PowerShellでの資格情報の安全な扱い ── 平文パスワードをスクリプトから追放する
- 「作った人が辞めても止まらない」ための属人化対策
関連する相談領域
合同会社小村ソフトでは、社内スクリプト資産のモジュール化と配布基盤の整備、属人化した運用の標準化、既存スクリプトの保守性改善を扱っています。
参考リンク
-
Microsoft Learn, Package management for PowerShell. Microsoft.PowerShell.PSResourceGetがPowerShellGetおよびPackageManagementを置き換えるモジュールであること、PowerShell 7.4に同梱され従来のPowerShellGet 2.2.5と並存すること、Windows PowerShell 5.1でもPowerShell Galleryから導入できることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Register-PSResourceRepository. -Uriにローカルフォルダーやファイル共有(UNCパス)、NuGet互換フィードのURLを指定してリポジトリを登録できること、-Trustedによる信頼済み設定、-Priorityによる検索順序、認証が必要なリポジトリでの資格情報の指定について。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Publish-PSResource. -Pathで指定したモジュールフォルダーをリポジトリへ公開すること、-Repositoryによる公開先の指定、-ApiKeyによる認証について。 ↩
-
Microsoft Learn, Install-PSResource. -Name / -Version / -Repository によるインストール、-Scope(CurrentUser / AllUsers)による配置先の指定、-TrustRepositoryによる確認の省略について。あわせてUpdate-PSResourceによる更新について。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_PSModulePath. PowerShellが$env:PSModulePathに列挙されたフォルダーからモジュールを検索すること、Windows PowerShellとPowerShell 7でユーザースコープ・全ユーザースコープの既定パスが異なることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, How to write a PowerShell module manifest. New-ModuleManifestによるマニフェストの作成、RootModule・ModuleVersion・GUID・PowerShellVersion・CompatiblePSEditions・RequiredModulesなどの各キー、FunctionsToExportなどのエクスポート指定にワイルドカードを使わず明示すべき理由(コマンド探索の性能)について。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Prerelease module versions. セマンティックバージョニングに基づくバージョン付け、PrivateData.PSData.Prereleaseによるプレリリース版の指定、プレリリース版が既定の取得対象にならないことについて。 ↩ ↩2
-
Microsoft Learn, New-FileCatalog. フォルダー配下のファイルのハッシュを含むカタログファイル(.cat)を生成できること、Set-AuthenticodeSignatureでカタログに署名できること、Test-FileCatalogによりカタログとファイル群を照合して改ざんを検出できることについて。実行ポリシーが検証するのは実行対象のスクリプトファイル自体のAuthenticode署名である点はabout_Execution_Policies(AllSignedでは信頼された発行元によって署名されたスクリプトのみ実行できること)およびSet-AuthenticodeSignature(ファイルにAuthenticode署名を付与すること、-TimestampServerによるタイムスタンプ付与)を参照。 ↩ ↩2
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
winget + PowerShellでPCキッティングを自動化する ── 手順書を実行可能にする
新入社員PCのセットアップを再現可能にする方法をまとめます。wingetによるアプリ導入とexport/import、WinGet Configurationの宣言的な構成、PowerShellで補う設定、無人実行時の注意点までを解説します。
Write-Hostをやめる ── PowerShellの出力ストリームとログ設計
PowerShellの6つの出力ストリームの使い分け、Write-Hostが抱える問題と正しい使いどころ、関数の戻り値が汚れる原因、-Verboseや-InformationVariableによる呼び出し側制御、構造化ログの残し方を整理します。
Microsoft Graph PowerShell入門 ── AzureAD・MSOnline廃止後のMicrosoft 365運用
AzureAD・MSOnlineモジュール廃止後のMicrosoft 365運用をMicrosoft Graph PowerShellへ移行する実務ガイドです。接続とスコープ設計、証明書による無人実行、ユーザー棚卸しやライセンス集計の定番スクリプトを解説します。
PowerShellのセキュリティ強化 ── ログ・AMSI・言語モード・JEA
PowerShellを禁止せずに安全に使うための実務をまとめます。スクリプトブロックログとトランスクリプションの有効化、AMSIと古いバージョンの無効化、言語モードによる制限、JEAによる権限委任までを解説します。
Get-WinEventでイベントログを実務的に調べる ── 絞り込みの速さが調査時間を決める
Windowsのイベントログ調査をPowerShellで効率化する方法をまとめます。Where-Objectで絞ると遅い理由、FilterHashtableとXPathの使い分け、再起動やログオン・アプリ異常終了の調査レシピ、複数台からの収集までを解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- PowerShellGetとPSResourceGetは何が違いますか?どちらを使うべきですか?
- PSResourceGet(Microsoft.PowerShell.PSResourceGet)は、従来のPowerShellGetとPackageManagementを置き換える新しいモジュール管理の仕組みで、PowerShell 7.4には最初から同梱されています。従来のPowerShellGet 2.2.5と並存できるため、既存スクリプトを壊さずに移行できます。新規に書くなら、コマンドレット名が-PSResource系(Install-PSResource、Publish-PSResourceなど)のPSResourceGetを使うのが推奨です。Windows PowerShell 5.1でも、PowerShell Galleryから導入すれば利用できます。
- 社内リポジトリを立てるのに、専用のサーバーは必要ですか?
- 不要です。最も簡単なのは、ファイル共有上のフォルダーをリポジトリとして登録する方法で、Register-PSResourceRepositoryにUNCパスを指定するだけで動きます。専用サーバーもデータベースも要りません。組織としてアクセス制御や監査を効かせたい場合や、社外からも取得したい場合は、Azure ArtifactsやGitHub PackagesのようなNuGet互換フィードを使います。まずはファイル共有で始め、必要が出てから移行するのが現実的です。
- モジュールマニフェスト(.psd1)は必ず必要ですか?
- 実務では必要と考えてください。.psm1だけでもモジュールとして読み込めますが、マニフェストがないとバージョン番号を持てず、「どの版が入っているか」が分からなくなります。バージョンがなければ更新の管理も、不具合発生時の切り分けもできません。加えて、マニフェストではエクスポートする関数の明示、依存モジュールの宣言、対応するPowerShellのバージョンとエディションの指定ができます。New-ModuleManifestで雛形を作れるので、作成コストもわずかです。
- FunctionsToExportに'*'を書くと何が問題になりますか?
- コマンドの自動探索が遅くなり、意図しない内部関数まで公開されます。PowerShellはモジュールを読み込む前に、どのコマンドがどのモジュールにあるかを知る必要があり、ワイルドカードだとモジュール本体を解析しないと分かりません。公開する関数を配列で明示すれば、この解析が不要になります。また、内部用のヘルパー関数が外から呼べてしまうと、それが事実上の公開APIになってしまい、後で変更できなくなります。
- 配布したモジュールを、利用者側で自動更新させることはできますか?
- Update-PSResourceで更新できますが、業務スクリプトが依存するモジュールの自動更新は慎重に設計してください。無人実行の夜間バッチが、知らないうちに新しいバージョンで動くことになるためです。実務では、検証環境で新版を確認してから本番の更新を計画的に行う運用が安全です。どうしても自動化するなら、メジャーバージョンを固定して更新する(-Version '1.*'のように範囲指定する)といった歯止めを設けてください。