更新履歴(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)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「PowerShellモジュールの社内配布と更新 ── PSResourceGetと社内リポジトリ」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21547456 https://staging.comcomponent.com/blog/powershell-module-distribution-psresourceget/
- DOI(最新版)
- 10.5281/zenodo.21547456
- DOI(この版)
- 10.5281/zenodo.21733163
「便利なスクリプトを書いたので共有フォルダーに置きました」── その瞬間から、静かに保守の負債が積み上がり始めます。誰かがコピーしてローカルで改造し、元のファイルが直っても反映されず、どの版がどこで動いているか誰も把握していない。数年後、共有フォルダーには 集計.ps1、集計_v2.ps1、集計_修正版_最新.ps1 が並びます。
この問題の答えは、PowerShellの世界では明確です。モジュールにして、バージョンを付けて、リポジトリから配布する。これだけで「どの版が入っているか」「更新したら全員に届くか」という問いに答えられるようになります。そしてPowerShell 7.4以降には、そのための仕組み(PSResourceGet)が最初から入っています。
この記事では、社内で共有しているスクリプトをモジュール化し、社内リポジトリを立てて配布・更新する手順を、専用サーバーを立てない現実的な構成で解説します。関数のモジュール化そのものについては「PowerShellの引数設計とモジュール化」を先に読んでおくと理解が早いはずです。
対象読者・前提環境
| 項目 | 内容 |
|---|---|
| 対象読者 | 共有フォルダーに置いた .ps1 を配っている情シス・運用担当 |
| 対象バージョン | Windows PowerShell 5.1とPowerShell 7.xの両方。ただしPSResourceGetが同梱されているのはPowerShell 7.4以降で、5.1では配る側・使う側の双方で事前インストールが必要です(第5章)1 |
| 既存環境との併存 | PSResourceGetは従来のPowerShellGet 2.2.5と並存できます。既存スクリプトを書き換えずに導入できます1 |
| サンプルの検証環境 | 記事末尾で配布しているサンプルコードは PowerShell 7.6 で実行して検証しています |
| 必要な権限 | リポジトリ用共有フォルダーの作成と権限設定、-Scope AllUsers でのインストールには管理者権限が必要です |
1. まず結論
- PSResourceGet(
Microsoft.PowerShell.PSResourceGet)がPowerShell 7.4に同梱されています。従来のPowerShellGet 2.2.5と並存するため、既存スクリプトを壊さずに使えます。Windows PowerShell 5.1には同梱されていないので、使う側・配る側の両方で事前導入が必要です(次章)。1 - 社内リポジトリはファイル共有で始められます。
Register-PSResourceRepositoryにUNCパスを指定するだけです。専用サーバーは不要です。2 - マニフェスト(
.psd1)は必須と考えてください。バージョン番号がなければ、更新も切り分けもできません。3 FunctionsToExportはワイルドカードにせず、配列で明示します。コマンド探索が速くなり、内部関数の意図しない公開も防げます。3- バージョンはセマンティックバージョニングで。破壊的変更をメジャーで表現できないと、利用側は安心して更新できません。4
- 公開は
Publish-PSResource、取得はInstall-PSResource、更新はUpdate-PSResource。56 - モジュールの探索パスは5.1と7で別です。
$env:PSModulePathの違いを理解しておかないと「入れたのに見つからない」が起きます。7 - 実行ポリシーが
AllSignedなら、各スクリプトファイルへのAuthenticode署名が必須です。カタログ署名はパッケージの完全性検証用で、実行ポリシーは満たしません。8 - 無人実行が依存するモジュールの自動更新は慎重に。検証してから計画的に上げる運用が安全です。
この記事で作る仕組みは、全体として次の形になります。
flowchart LR
DEV["開発側<br/>情シス担当がモジュールを書く"]
TEST["テストと静的解析<br/>Pester / PSScriptAnalyzer"]
REPO["社内リポジトリ<br/>ファイル共有(UNCパス)または<br/>NuGet互換フィード"]
USER["利用側のPC<br/>Find / Install / Update-PSResource"]
SRV["利用側のサーバー<br/>夜間バッチなどの無人実行"]
DEV --> TEST
TEST -->|"Publish-PSResource"| REPO
REPO -->|"Install-PSResource"| USER
REPO -->|"Install-PSResource -Scope AllUsers"| SRV
矢印に乗っているコマンドが、この記事で扱う中心です。リポジトリは配布側・利用側の双方で最初に一度だけ Register-PSResourceRepository で登録し(第5章)、以降は公開・取得・更新のコマンドだけで回ります(第6章)。書き込み権限を持つのは配布担当者だけ、というのがこの図の唯一のセキュリティ境界です。
この記事の知識マップ
この記事は共有フォルダーでのps1配布から卒業し、PowerShell 7.4に同梱されるPSResourceGetで社内リポジトリを構築・運用する方法を扱います。PSResourceGetは従来のPowerShellGetの後継として、Register-PSResourceRepositoryで登録したリポジトリに対しPublish-PSResource・Install-PSResource・Update-PSResourceで公開・取得・更新を行います。マニフェストにバージョンとFunctionsToExportを配列で明示することが内部関数の意図しない公開を防ぎ、共有フォルダーのアクセス制御が配布の実質的なセキュリティ境界になります。無人実行にはAllUsersスコープと管理者権限が必要で、実行ポリシーAllSignedの環境では各ファイルへのAuthenticode署名が必須であり、カタログ署名だけではこの要件を満たせません。
flowchart LR
accTitle: PSResourceGetによるモジュール社内配布の知識マップ
accDescr: PSResourceGetが従来のPowerShellGetを置き換え社内リポジトリでの公開・取得・更新を担うこと、マニフェストとFunctionsToExportの明示が内部関数の意図しない公開を防ぐこと、共有フォルダーのアクセス制御がセキュリティ境界になること、AllSigned環境ではAuthenticode署名が必須でカタログ署名だけでは満たせないことの関係を示す図
psresourceget["PSResourceGet(Microsoft.PowerShell.PSResourceGet)"]
internal_module_repository["社内リポジトリ(PSResourceGet)"]
powershellget_2["PowerShellGet 2.2.5(従来版)"]
register_psresourcerepository["Register-PSResourceRepository"]
repository_share_acl["社内リポジトリ共有フォルダーのアクセス制御"]
publish_psresource["Publish-PSResource"]
install_psresource["Install-PSResource"]
update_psresource["Update-PSResource"]
install_scope["-Scope(CurrentUser/AllUsers)"]
task_scheduler["タスクスケジューラの無人実行"]
admin_rights["管理者権限"]
module_versioning_semver["セマンティックバージョニング(モジュール)"]
module_manifest["モジュールマニフェスト(.psd1)"]
prerelease_version["プレリリース版(PrivateData.PSData.Prerelease)"]
functionstoexport_wildcard["FunctionsToExportのワイルドカード指定('*')"]
internal_function_exposure["内部関数の意図しない公開"]
functionstoexport_explicit["FunctionsToExportの配列明示"]
authenticode_signature["Authenticode署名(スクリプトファイル)"]
code_signing_cert["コード署名証明書"]
code_signing_timestamp["コード署名のタイムスタンプ"]
allsigned_execution_policy["実行ポリシーAllSigned"]
file_catalog_signing["カタログ署名(New-FileCatalog)"]
psmodulepath["$env:PSModulePath(モジュールの探索パス)"]
psresourceget -->|"の後継"| powershellget_2
psresourceget -->|"利用する"| internal_module_repository
internal_module_repository -->|"で構成できる"| register_psresourcerepository
internal_module_repository -.->|"前提とする"| repository_share_acl
psresourceget -->|"利用する"| publish_psresource
psresourceget -->|"利用する"| install_psresource
psresourceget -->|"利用する"| update_psresource
install_psresource -->|"利用する"| install_scope
task_scheduler -.->|"前提とする"| install_scope
install_scope -.->|"前提とする"| admin_rights
module_versioning_semver -->|"推奨される対応"| module_manifest
module_manifest -.->|"利用する"| prerelease_version
functionstoexport_wildcard -->|"原因になり得る"| internal_function_exposure
functionstoexport_explicit -->|"防止する"| internal_function_exposure
functionstoexport_explicit -->|"推奨される対応"| module_manifest
authenticode_signature -->|"利用する"| code_signing_cert
authenticode_signature -.->|"利用する"| code_signing_timestamp
allsigned_execution_policy -->|"前提とする"| authenticode_signature
install_psresource -->|"利用する"| file_catalog_signing
authenticode_signature -.->|"推奨される対応"| internal_module_repository
install_psresource -->|"前提とする"| psmodulepath
internal_module_repository -.->|"前提とする"| module_manifest
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全22件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. 「共有フォルダーのps1」が抱える問題
まず、何を解決しようとしているのかを明確にします。
| 症状 | 根本原因 |
|---|---|
| どの版が動いているか分からない | バージョン番号という概念がない |
| 修正しても全員に反映されない | 各自がコピーを持っている |
| 誰が使っているか分からない | 取得の記録が残らない(※後述のとおり、これだけは配布方式の選択が必要) |
| 一部の環境だけ壊れる | 依存関係(必要なモジュール・PSバージョン)が宣言されていない |
| 直したいが影響範囲が読めない | 公開している関数と内部関数の区別がない |
モジュール化とリポジトリ配布は、このうち上の4つには直接効きます。ただし「誰が使っているか」だけは配布の仕組み次第です。次章以降で紹介するファイル共有リポジトリは手軽な反面、誰がいつ取得したかの記録は残りません(Get-InstalledPSResource で分かるのは、そのコマンドを実行した端末の状態だけです)。利用状況を把握したい場合は、次のいずれかを併用してください。
- 共有フォルダーの読み取り監査を有効にする(ファイル アクセスの監査。「Get-WinEventでイベントログを実務的に調べる」)
- ダウンロード統計が取れるNuGet互換フィード(Azure Artifacts など)を使う
- 各端末で
Get-InstalledPSResourceを実行して結果を集約する(「PowerShell Remoting(WinRM)入門」)
3. モジュールの最小構成
配布可能なモジュールの最小形は、フォルダー・.psm1・.psd1 の3点です。
KsOps\
KsOps.psd1 ← マニフェスト(バージョン・公開関数・依存関係)
KsOps.psm1 ← 実装(またはPublic/Privateフォルダーからのドットソース)
Public\
Get-KsShareUsage.ps1
Invoke-KsArchive.ps1
Private\
ConvertTo-KsSize.ps1
マニフェストは New-ModuleManifest で雛形を作り、必要な項目を埋めます。3
$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
.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になり、後で変更できなくなります。3
上の例では CompatiblePSEditions に Desktop(Windows PowerShell 5.1)と Core(PowerShell 7)の両方を宣言していますが、宣言しただけで両方で動くようになるわけではありません。5.1でPSResourceGet自体を使うための準備は第5章、5.1と7でモジュールの探索パスが違うために起きる「入れたのに見つからない」は第7章にまとめています。両対応で配るつもりなら、先にその2つを読んでおいてください。
4. バージョニングの決め方
利用側が安心して更新できるかどうかは、バージョン番号の付け方で決まります。セマンティックバージョニング(メジャー.マイナー.パッチ)を採用し、破壊的変更を必ずメジャーで表現してください。4
| 変更内容 | 上げる箇所 |
|---|---|
| パラメーター名の変更、関数の削除、戻り値の形の変更 | メジャー(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の英数字とハイフンだけで、ピリオドや + は使えません。4
破壊的変更の扱いについては、インターフェース設計の考え方が参考になります(「DLL・COMインターフェースの後方互換性」)。
5. 社内リポジトリを作る ── ファイル共有で十分
PSResourceGetは、ファイル共有上のフォルダーをリポジトリとして扱えます。2 これが最も導入コストの低い構成です。
まず前提の確認です。PowerShell 7.4以降には同梱されていますが、Windows PowerShell 5.1には入っていません。5.1でこの後のコマンド(Register-PSResourceRepository など)を使うには、あらかじめモジュールを導入してください。1
# 【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
}
# 【配布側・利用側の両方で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
共有フォルダーのアクセス権は「配布担当者だけ書き込み可、利用者は読み取りのみ」にします。ここが緩いと、誰でも任意のコードを全社に配れる経路になってしまいます。共有アクセス許可とNTFSのアクセス許可は両方設定してください(実効権限は厳しいほうになります)。9
# ファイルサーバー側で実行する(要管理者権限)
$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を消しません。このフォルダーに書ける経路を持つ人が他に居ても、そのまま残ります。9 しかも、このリポジトリは全端末が Trusted = $true で登録する前提です。信頼済みリポジトリに置かれたモジュールは、確認なしにインストールされます。残った1人が、全社の PowerShell 実行環境に任意のコードを配れることになり、「配布担当者だけが公開できる」という前提がその時点で崩れます。
継承の切断(SetAccessRuleProtection($true, $false))だけでも足りません。このメソッドが扱えるのは継承ACEだけで、そのフォルダーに直接付与されたACEは対象外だからです。9 上のように直接付与も落としてから、許可リストの主体だけを入れ直します。新規に作ったフォルダーなら結果は同じですが、既存フォルダーを流用したときだけ壊れる種類の問題なので、手順に入れておくほうが確実です。
「配布担当者」は個人アカウントではなくグループにしてください。担当者の異動でモジュールを更新できなくなる、という止まり方が実際に起こります。認証が必要なNuGet互換フィードを使う場合は、利用側に配る資格情報を読み取り専用の権限で発行し、公開用の資格情報(APIキー)と分けます。共有フォルダーの権限設計そのものは「PowerShellでファイルサーバーを棚卸しする」も参考にしてください。
より本格的に運用するなら、Azure ArtifactsやGitHub PackagesのようなNuGet互換フィードを登録します。認証が必要なリポジトリでは、資格情報をSecretManagementの保管庫から参照する構成にできます(「PowerShellでの資格情報の安全な扱い」)。2
6. 公開・取得・更新
# 【配布側】モジュールを公開する
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
-Scope の使い分けは重要です。タスクスケジューラの無人実行で使うモジュールは AllUsers(またはサービスアカウント自身の環境)に入れる必要があります。「自分の環境では動くのに夜間バッチだけ コマンドレットが認識されません で落ちる」の典型原因が、CurrentUser スコープへのインストールです。67
なお -Scope AllUsers は全ユーザー共通の場所(Program Files 配下)へ書き込むため、管理者として起動したPowerShellでないと失敗します。7 初回の導入で「アクセスが拒否されました」となったら、まず昇格しているかを確認してください。
7. モジュールの探索パスと5.1/7の違い
PowerShellは $env:PSModulePath に列挙されたフォルダーからモジュールを探します。Windows PowerShell 5.1とPowerShell 7では既定のパスが異なります。7
| エディション | ユーザースコープの既定パス |
|---|---|
| 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 # 実際に読まれる場所
8. 署名と実行ポリシー
署名には目的の違う2つの仕組みがあり、混同すると「署名したのに実行できない」ことになります。8
| 仕組み | 何を担保するか | 実行ポリシー AllSigned を満たすか |
|---|---|---|
Authenticode署名(Set-AuthenticodeSignature) |
個々のスクリプトファイルの発行元と完全性 | 満たす(各ファイルに署名が必要) |
カタログ署名(New-FileCatalog + 署名) |
モジュール一式(パッケージ)の完全性 | 満たさない |
実行ポリシーは、読み込もうとしている .ps1 / .psm1 そのもののAuthenticode署名を検証します。カタログファイル(.cat)に署名しても、中のスクリプトファイルは未署名のままなので、AllSigned 環境では実行がブロックされます。したがって AllSigned を運用しているなら、実行される各ファイルに署名するのが必須です。
# (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 があります。6 実行の可否はAuthenticode署名、配布物の完全性はカタログ、と役割を分けて理解してください。
タイムスタンプを付けておくと、署名証明書の有効期限が切れた後も署名が有効なままになります。実行ポリシーと署名運用の全体像は「PowerShellの実行ポリシーとスクリプト署名」にまとめています。
9. 運用ルールとして決めておくこと
技術より運用のほうが重要です。最低限、次を決めてください。
- 誰が公開できるか。共有フォルダーの書き込み権限を持つ人=全社にコードを配れる人です
- 変更履歴をどこに書くか。
ReleaseNotes(マニフェストのPrivateData.PSData)かCHANGELOGに、破壊的変更を明記します - 本番サーバーはバージョン固定か。無人実行が依存するモジュールは、固定して計画的に更新するのが安全です
- 廃止の手順。関数を削除するときは、メジャーバージョンを上げ、事前に非推奨を告知します
- テストとlintを通してから公開する。公開はCIから行うのが理想です(「PesterによるPowerShellのテスト整備」)
10. 実務の定石(判断表)
| 論点 | 選択肢 | 判断の目安 |
|---|---|---|
| 配布形式 | 共有フォルダーの .ps1 / モジュール + リポジトリ |
バージョンと更新の管理ができるかどうかが分岐点 |
| モジュール管理 | PowerShellGet 2.x / PSResourceGet | 7.4以降は同梱。新規はPSResourceGet1 |
| リポジトリ | ファイル共有 / NuGet互換フィード | まずファイル共有で開始。認証・監査が要るならフィード2 |
| マニフェスト | 省略 / 必須 | バージョンがないと運用が成立しない3 |
| 公開関数 | '*' / 配列で明示 |
探索性能と、内部関数の非公開のため3 |
| インストール先 | CurrentUser / 無人実行はAllUsers | サービスアカウントから見えるかが基準7 |
| 本番の更新 | 自動更新 / バージョン固定 + 計画的更新 | 夜間バッチが勝手に新版で動くのを防ぐ |
| 署名 | なし / 各ファイルのAuthenticode署名(+カタログ) | AllSigned環境ではファイル単位の署名が必須。カタログは完全性検証用8 |
11. まとめ
- 共有フォルダーの
.ps1配布は、バージョン・更新・依存関係・公開範囲のすべてが管理不能になります。モジュール化とリポジトリ配布で解決できます。ただし「誰が使っているか」だけは別です。ファイル共有リポジトリには取得の記録が残らないため、共有フォルダーの読み取り監査、ダウンロード統計の取れるフィード、各端末でのGet-InstalledPSResourceの集約のいずれかを併用してください。 - マニフェストは必須です。
FunctionsToExportは配列で明示し、内部関数は公開しないでください。 - 社内リポジトリはファイル共有をUNCパスで登録するだけで始められます。書き込み権限の管理が実質的なセキュリティ境界です。
- 公開は
Publish-PSResource、取得はInstall-PSResource、更新はUpdate-PSResource。無人実行が使うモジュールはAllUsersスコープに入れます。 - 5.1と7では探索パスが違います。両対応するなら
CompatiblePSEditionsを宣言し、両方でテストしてください。 AllSigned環境では各スクリプトファイルにAuthenticode署名が必要です。カタログ署名は配布物の完全性検証用で、実行の可否とは別物です。タイムスタンプは必ず付けてください。
サンプルコードのダウンロード
この記事で扱ったコードは、そのまま動かせる形にまとめて配布しています。公開/非公開を分けたモジュール一式と、社内リポジトリへの配布手順が入っています。
この記事のサンプルは、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
-
Microsoft Learn, Register-PSResourceRepository. -Uriにローカルフォルダーやファイル共有(UNCパス)、NuGet互換フィードのURLを指定してリポジトリを登録できること、-Trustedによる信頼済み設定、-Priorityによる検索順序、認証が必要なリポジトリでの資格情報の指定について。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, How to write a PowerShell module manifest. New-ModuleManifestによるマニフェストの作成、RootModule・ModuleVersion・GUID・PowerShellVersion・CompatiblePSEditions・RequiredModulesなどの各キー、FunctionsToExportなどのエクスポート指定にワイルドカードを使わず明示すべき理由(コマンド探索の性能)について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Prerelease module versions. セマンティックバージョニングに基づくバージョン付け、PrivateData.PSData.Prereleaseによるプレリリース版の指定、プレリリース版が既定の取得対象にならないことについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Publish-PSResource. -Pathで指定したモジュールフォルダーをリポジトリへ公開すること、-Repositoryによる公開先の指定、-ApiKeyによる認証について。 ↩
-
Microsoft Learn, Install-PSResource. -Name / -Version / -Repository によるインストール、-Scope(CurrentUser / AllUsers)による配置先の指定、-TrustRepositoryによる確認の省略について。あわせてUpdate-PSResourceによる更新について。 ↩ ↩2 ↩3
-
Microsoft Learn, about_PSModulePath. PowerShellが$env:PSModulePathに列挙されたフォルダーからモジュールを検索すること、Windows PowerShellとPowerShell 7でユーザースコープ・全ユーザースコープの既定パスが異なることについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, New-FileCatalog. フォルダー配下のファイルのハッシュを含むカタログファイル(.cat)を生成できること、Set-AuthenticodeSignatureでカタログに署名できること、Test-FileCatalogによりカタログとファイル群を照合して改ざんを検出できることについて。実行ポリシーが検証するのは実行対象のスクリプトファイル自体のAuthenticode署名である点はabout_Execution_Policies(AllSignedでは信頼された発行元によって署名されたスクリプトのみ実行できること)およびSet-AuthenticodeSignature(ファイルにAuthenticode署名を付与すること、-TimestampServerによるタイムスタンプ付与)を参照。 ↩ ↩2 ↩3
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
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.*'のように範囲指定する)といった歯止めを設けてください。