PowerShellモジュールの社内配布と更新 ── PSResourceGetと社内リポジトリ

· · PowerShell, モジュール, 配布, バージョン管理, 運用改善, 保守性, 情報システム, 自動化

更新履歴(初版のみ・2026年07月25日公開)
初版公開
この記事を引用する(DOI: 10.5281/zenodo.21547456)

この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。

小村 豪(2026)「PowerShellモジュールの社内配布と更新 ── PSResourceGetと社内リポジトリ」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21547456

DOI(最新版)
10.5281/zenodo.21547456
DOI(この版)
10.5281/zenodo.21547457

「便利なスクリプトを書いたので共有フォルダーに置きました」── その瞬間から、静かに保守の負債が積み上がり始めます。誰かがコピーしてローカルで改造し、元のファイルが直っても反映されず、どの版がどこで動いているか誰も把握していない。数年後、共有フォルダーには 集計.ps1集計_v2.ps1集計_修正版_最新.ps1 が並びます。

この問題の答えは、PowerShellの世界では明確です。モジュールにして、バージョンを付けて、リポジトリから配布する。これだけで「どの版が入っているか」「更新したら全員に届くか」という問いに答えられるようになります。そしてPowerShell 7.4以降には、そのための仕組み(PSResourceGet)が最初から入っています。

この記事では、社内で共有しているスクリプトをモジュール化し、社内リポジトリを立てて配布・更新する手順を、専用サーバーを立てない現実的な構成で解説します。関数のモジュール化そのものについては「PowerShellの引数設計とモジュール化」を先に読んでおくと理解が早いはずです。

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-PSResource56
  • モジュールの探索パスは5.1と7で別です。$env:PSModulePath の違いを理解しておかないと「入れたのに見つからない」が起きます。7
  • 実行ポリシーが AllSigned なら、各スクリプトファイルへのAuthenticode署名が必須です。カタログ署名はパッケージの完全性検証用で、実行ポリシーは満たしません。8
  • 無人実行が依存するモジュールの自動更新は慎重に。検証してから計画的に上げる運用が安全です。

2. 「共有フォルダーのps1」が抱える問題

まず、何を解決しようとしているのかを明確にします。

症状 根本原因
どの版が動いているか分からない バージョン番号という概念がない
修正しても全員に反映されない 各自がコピーを持っている
誰が使っているか分からない 取得の記録が残らない(※後述のとおり、これだけは配布方式の選択が必要)
一部の環境だけ壊れる 依存関係(必要なモジュール・PSバージョン)が宣言されていない
直したいが影響範囲が読めない 公開している関数と内部関数の区別がない

モジュール化とリポジトリ配布は、このうち上の4つには直接効きます。ただし「誰が使っているか」だけは配布の仕組み次第です。次章以降で紹介するファイル共有リポジトリは手軽な反面、誰がいつ取得したかの記録は残りません(Get-InstalledPSResource で分かるのは、そのコマンドを実行した端末の状態だけです)。利用状況を把握したい場合は、次のいずれかを併用してください。

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

4. バージョニングの決め方

利用側が安心して更新できるかどうかは、バージョン番号の付け方で決まります。セマンティックバージョニング(メジャー.マイナー.パッチ)を採用し、破壊的変更を必ずメジャーで表現してください。4

変更内容 上げる箇所
パラメーター名の変更、関数の削除、戻り値の形の変更 メジャー(1.2.3 → 2.0.0)
関数やパラメーターの追加(既存はそのまま動く) マイナー(1.2.3 → 1.3.0)
不具合修正のみ パッチ(1.2.3 → 1.2.4)

検証用の版を配りたいときはプレリリース版が使えます。マニフェストの PrivateData.PSData.Prereleasebeta1 のような文字列を設定すると(バージョンとの区切りのハイフンは自動的に付き、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

共有フォルダーのアクセス権は「配布担当者だけ書き込み可、利用者は読み取りのみ」にします。ここが緩いと、誰でも任意のコードを全社に配れる経路になってしまいます。共有フォルダーの権限設計は「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

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

両方で使うモジュールは、CompatiblePSEditionsDesktopCore の両方を宣言し、両方の環境でテストしてから配布します。互換性の検証には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署名が必要です。カタログ署名は配布物の完全性検証用で、実行の可否とは別物です。タイムスタンプは必ず付けてください。

サンプルコードのダウンロード

この記事で扱ったコードは、そのまま動かせる形にまとめて配布しています。公開/非公開を分けたモジュール一式と、社内リポジトリへの配布手順が入っています。

サンプルコードをダウンロード(zip)

この記事のサンプルは、PowerShell 7.6 で実際に実行して検証しています(Pester 16件)。zipに含まれる Invoke-SampleTests.ps1 を実行すれば、お手元でも同じ検証を再現できます。

# 構文解析 + 静的解析 + Pesterテスト
./Invoke-SampleTests.ps1

設定値(パス、サーバー名、テナントIDなど)は例です。そのまま本番環境で実行せず、自社の環境に合わせて読み替えてください。

関連記事

関連する相談領域

合同会社小村ソフトでは、社内スクリプト資産のモジュール化と配布基盤の整備、属人化した運用の標準化、既存スクリプトの保守性改善を扱っています。

参考リンク

  1. 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

  2. Microsoft Learn, Register-PSResourceRepository. -Uriにローカルフォルダーやファイル共有(UNCパス)、NuGet互換フィードのURLを指定してリポジトリを登録できること、-Trustedによる信頼済み設定、-Priorityによる検索順序、認証が必要なリポジトリでの資格情報の指定について。  2 3 4

  3. Microsoft Learn, How to write a PowerShell module manifest. New-ModuleManifestによるマニフェストの作成、RootModule・ModuleVersion・GUID・PowerShellVersion・CompatiblePSEditions・RequiredModulesなどの各キー、FunctionsToExportなどのエクスポート指定にワイルドカードを使わず明示すべき理由(コマンド探索の性能)について。  2 3 4 5 6

  4. Microsoft Learn, Prerelease module versions. セマンティックバージョニングに基づくバージョン付け、PrivateData.PSData.Prereleaseによるプレリリース版の指定、プレリリース版が既定の取得対象にならないことについて。  2 3

  5. Microsoft Learn, Publish-PSResource. -Pathで指定したモジュールフォルダーをリポジトリへ公開すること、-Repositoryによる公開先の指定、-ApiKeyによる認証について。 

  6. Microsoft Learn, Install-PSResource. -Name / -Version / -Repository によるインストール、-Scope(CurrentUser / AllUsers)による配置先の指定、-TrustRepositoryによる確認の省略について。あわせてUpdate-PSResourceによる更新について。  2 3

  7. Microsoft Learn, about_PSModulePath. PowerShellが$env:PSModulePathに列挙されたフォルダーからモジュールを検索すること、Windows PowerShellとPowerShell 7でユーザースコープ・全ユーザースコープの既定パスが異なることについて。  2 3 4

  8. Microsoft Learn, New-FileCatalog. フォルダー配下のファイルのハッシュを含むカタログファイル(.cat)を生成できること、Set-AuthenticodeSignatureでカタログに署名できること、Test-FileCatalogによりカタログとファイル群を照合して改ざんを検出できることについて。実行ポリシーが検証するのは実行対象のスクリプトファイル自体のAuthenticode署名である点はabout_Execution_Policies(AllSignedでは信頼された発行元によって署名されたスクリプトのみ実行できること)およびSet-AuthenticodeSignature(ファイルにAuthenticode署名を付与すること、-TimestampServerによるタイムスタンプ付与)を参照。  2 3

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

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.*'のように範囲指定する)といった歯止めを設けてください。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る