PowerShellでの資格情報の安全な扱い ── 平文パスワードをスクリプトから追放する

· 更新日: · · PowerShell, Windows, セキュリティ, 資格情報, DPAPI, 自動化, 運用改善, スクリプト

更新履歴(3件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
gMSAの導入手順の骨格(KDSルートキー、`New-ADServiceAccount`、`Install-`と`Test-ADServiceAccount`、前提条件、イベントIDでの確認)を追加しました。DPAPIが「同一ユーザー・同一マシン」でしか復号できない理由を本文で完結させ、タスクの実行アカウントとして資格情報を保存する具体手順、マネージドIDへの導線、Protected Event Loggingが有効かの確認方法を追記しました。タスクスケジューラへのgMSA登録は、指定すべき値を一次情報で確認できなかったため調べ先の提示にとどめています。
初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21590170)

以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。

小村 豪(2026)「PowerShellでの資格情報の安全な扱い ── 平文パスワードをスクリプトから追放する」合同会社小村ソフト. https://comcomponent.com/blog/powershell-credential-secretmanagement/

DOI(登録済みアーカイブ)
10.5281/zenodo.21590170
DOI(前回登録した版)
10.5281/zenodo.21733113

スクリプトから平文パスワードを消したい。しかし、SecureString、Export-Clixml、SecretManagement、資格情報マネージャー、Azure Key Vaultのどれを選べばよいのか分からない。運用スクリプトの見直しで、よく迷うところです。

ファイルサーバーへの接続、基幹システムのDB、メール送信、Web APIのキー。動作を優先して埋め込んだパスワードは、スクリプトと一緒に共有フォルダやGit履歴へ残ります。大切なのは、保存の道具を選ぶ前に、スクリプトがその秘密を持つ必要があるかを決めることです。

この記事は、社内の運用スクリプトや定期バッチを管理する情シス・運用担当者向けです。保存しない設計 → 対話での受け取り → 無人実行での保存 → ログへの露出防止の順に整理します。PowerShell 7.xを基準に、Windows PowerShell 5.1への注意も併記します。

1. まず結論 ── 「持たない」を先に、保存方法は後に選ぶ

人がその場で実行するなら、Get-CredentialでPSCredentialを受け取るのが基本です。無人実行なら、まず実行アカウントへの権限付与やマネージドIDで、スクリプトからパスワードをなくせないかを調べます。123

無人実行では、次の優先順位で検討します。

  1. 接続先がWindows認証にできるなら、実行アカウント(ドメインアカウント/gMSA)への権限付与で資格情報を消す。ドメイン環境の無人ジョブはまずこれを検討します。2
  2. それが無理な相手(SQL認証のDB、APIキー、ワークグループのNASなど)だけ、秘密を保存する。単発ならExport-ClixmlのDPAPI保存、複数あるならSecretManagement+SecretStore。45
  3. クラウド資源への接続は、キーの保存よりマネージドID/フェデレーション資格情報の利用を優先する。保存する場合もAzure Key Vaultのような専用ボールトへ。67
保存方法を決める前の分岐スクリプトに秘密を持たせない方法を先に検討し、必要な秘密だけを実行条件に合う方法で受け取ります。できる別の秘密が必要できる無人実行実行アカウントやIDで認証できるかスクリプトに秘密を保存しない人が入力できるかGet-Credentialで受け取るDPAPIやボールトで保存する

図1: スクリプトに秘密を持たせない方法を先に検討し、必要な秘密だけを実行条件に合う方法で受け取ります。

どの方法でも、SecureStringへ変換するだけでは、元の平文がスクリプトやログに残る問題は解消しません。 受け渡しの形式、保存先の保護、無人で取り出す条件、記録への露出を別々に確認します。8910

知りたいこと 読む章
直書きをなぜやめるか、何を移すか 2章
Windows認証・gMSA・マネージドIDで保存をなくせるか 3章
Get-Credential・PSCredential・SecureStringをどう使うか 4章
Windowsの無人ジョブに少数の秘密を保存する 5章
SecretManagementの採用条件と無人での解錠 6章
ログへの漏えいと、採用方法の最終確認 7〜9章

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全24件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. 平文の何が問題か ── 漏えい経路はコピーの数だけある

2.1 コピーと一緒に秘密も広がる

パスワードの直書きが危険な理由は、「悪意ある侵入者に読まれるかもしれない」だけではありません。スクリプトという成果物の性質上、正規の運用をしているだけでコピーが増殖することです。

コピーが生まれる場所 何が起きるか
Gitリポジトリ 一度コミットしたパスワードは履歴に永久保存。後からファイルを直しても履歴には残る
共有フォルダ 読み取り権限のある全員+バックアップ+世代コピーに拡散
メール・チャット 「このスクリプト使って」と添付した瞬間に制御不能
イベントログ スクリプトブロックログが有効なら、実行されたコードの内容がログに記録される10
トランスクリプト Start-Transcriptや組織のトランスクリプション設定が、画面に流れた内容を記録する10

この構造が意味するのは、平文パスワードの対策は「ファイルを見られないようにする」ことでは達成できない、ということです。アクセス権をどれだけ絞っても、Git履歴とバックアップまでは締められません。パスワードをスクリプトの外へ、暗号化された保管場所へ出すことが本筋です。この考え方は、.NETアプリの設定ファイルについて「Windowsアプリの機密情報保存 - DPAPIで平文設定を避ける」で書いたことと同じで、PowerShellでも原則は変わりません。

コピー先を守るだけでは足りないスクリプトから平文を外さなければ、正規の共有や保存によって秘密を含むコピーも増えていきます。平文を含むスクリプト共有や履歴にコピーが残るコピーが漏えい経路になる秘密をスクリプトの外へ出す

図2: スクリプトから平文を外さなければ、正規の共有や保存によって秘密を含むコピーも増えていきます。

2.2 SecureStringへの変換だけでは解決しない

次は書き換え対象の例です。安全な保存方法の例ではありません。

# アンチパターン: どちらも「平文がスクリプトに残る」点で同じ
$password = "P@ssw0rd123"

# SecureStringに変換しても、元の平文は1行目に書かれたまま。
# PSScriptAnalyzerはこの -AsPlainText の使い方をエラーとして検出する
$secure = ConvertTo-SecureString $password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('svc-transfer', $secure)

平文をその場でSecureStringに変換するConvertTo-SecureString "P@ssw0rd" -AsPlainText -Forceは解決策ではありません。PSScriptAnalyzer(公式の静的解析ツール)はこの書き方をエラー(Severity: Error)として検出します。暗号化を迂回して平文をメモリに晒すうえ、元の平文がスクリプト内に残り続けるからです。8

変換しても元の平文は残る実行時にSecureStringへ変換しても、コードや履歴に書かれた元の平文が消えるわけではありません。コード内の平文パスワードSecureStringへ変換コードと履歴には平文が残る保存方法そのものを見直す

図3: 実行時にSecureStringへ変換しても、コードや履歴に書かれた元の平文が消えるわけではありません。

3. 最優先の現実解 ── そもそも資格情報を持たない

保存方法を工夫する前に、スクリプトが資格情報を持つ必要をなくせないかを確認します。

3.1 Windows認証は実行アカウントへ権限を与える

Windowsのタスクやサービスは、実行アカウントのセキュリティコンテキストで動きます。接続先がWindows認証で守られているなら ── 共有フォルダのACL、SQL ServerのWindows認証、社内APIのWindows統合認証 ── 実行アカウント自身に接続先の権限を与えれば、スクリプトにはパスワードもGet-Secretも一切登場しません。認証はKerberosが実行アカウントの身元で勝手にやってくれます。

この構成を支えるのがgMSA(グループ管理サービスアカウント)です。gMSAはドメインの管理されたアカウントで、パスワード管理をWindows OSが引き受けます。2 パスワードは240バイトのランダム生成で30日ごとにOSが自動変更するため、人間は誰もパスワードを知らず、期限切れによる停止も起きません。11 そしてgMSAは、Windowsサービスだけでなくタスクスケジューラのタスクにも使えます。12

実行アカウントで接続する接続先がWindows認証を使う場合は実行アカウントへ権限を与え、スクリプト自身のパスワード管理をなくします。タスクやサービスの実行アカウント接続先へ権限を付与アカウントの身元で認証スクリプトにパスワード不要gMSAならOSがパスワード管理

図4: 接続先がWindows認証を使う場合は実行アカウントへ権限を与え、スクリプト自身のパスワード管理をなくします。

3.2 gMSAは前提確認・作成・サーバーへの導入を分ける

ここではgMSAの導入に必要な手順の骨格を示します。前提として、ドメイン・フォレストの機能レベルがWindows Server 2012以上であること、作業アカウントがDomain Admins相当であること、ドメインコントローラー以外で作業するならRSAT(ActiveDirectoryモジュール)が入っていることが必要です。12

# 1) ドメインに1つだけ: KDSルートキーを作る(既にあればこの手順は不要)
#    全ドメインコントローラーへの複製を待つため、実際にgMSAを作れるまで最大10時間かかる
Add-KdsRootKey -EffectiveImmediately

# 2) gMSAを作る。PrincipalsAllowedToRetrieveManagedPasswordには、
#    パスワードの取得を許すコンピューター(をまとめたセキュリティグループ)を指定する
New-ADServiceAccount -Name svc-transfer -DNSHostName svc-transfer.contoso.local `
    -PrincipalsAllowedToRetrieveManagedPassword 'gMSA-Hosts'

# 3) 実際にジョブを動かすサーバー側で: gMSAをインストールし、パスワードを取得できるか確認する
Install-ADServiceAccount -Identity svc-transfer
Test-ADServiceAccount -Identity svc-transfer

KDSルートキーの作成はドメインで1回だけの作業です。作成直後は複製の都合でgMSAを作れないため、検証環境で待ちたくない場合に-EffectiveTime ((Get-Date).AddHours(-10))で過去日時を指定する手順が公式に案内されています(本番では使わないでください)。13 作成後は、KDSサービスの運用ログにイベントID 4004が記録されているかで確認できます。12

gMSAを使うまでの確認順ドメイン側の準備とサーバー側の導入・確認を済ませてから、タスクへgMSAを割り当てます。ADと作業権限の前提を確認KDSルートキーを確認・作成複製の収束を待つgMSAと取得を許すホストを設定実行サーバーへ導入・テストタスクへ割り当てる

図5: ドメイン側の準備とサーバー側の導入・確認を済ませてから、タスクへgMSAを割り当てます。

タスクへの割り当ては、アカウントの作成とは別の作業

タスクへの割り当ては、タスクの実行アカウントにgMSA(CONTOSO\svc-transfer$のように末尾が$のアカウント名)を指定します。gMSAは人間がパスワードを知らないので、「実行アカウントのパスワードを入力する」前提のGUI手順とは勝手が違います。実務ではScheduledTasksモジュールのNew-ScheduledTaskPrincipalとRegister-ScheduledTaskで登録することになるため、この2つのコマンドレットのドキュメントを次の調べ先にしてください。あわせて、そのサーバーでgMSAに「バッチ ジョブとしてログオン」の権利を与える設定が必要になることがあります。

3.3 クラウドではマネージドIDを先に検討する

クラウド資源への接続では、キーの保存よりもマネージドIDやフェデレーション資格情報を優先して検討します。67 マネージドIDは、Azure上のリソースに割り当てたIDでトークンを取得し、パスワードや鍵を持たずにEntra ID対応のサービスへ認証する仕組みです。次に何を読むかという意味では、まず概要ページでシステム割り当てとユーザー割り当ての違いを押さえ、3 PowerShellから使うならAzモジュールのConnect-AzAccount -Identity(マネージドIDでのサインイン)を入口にすると迷いません。

クラウド資源はIDによる認証を先に考えるマネージドIDではAzureリソースに割り当てたIDでトークンを取得し、対応するサービスへ認証します。Azureリソースに割り当てたIDトークンを取得Entra ID対応サービスへ認証パスワードや鍵をスクリプトに置かない

図6: マネージドIDではAzureリソースに割り当てたIDでトークンを取得し、対応するサービスへ認証します。

リモートのサーバーに対して資格情報を明示的に渡して処理を実行する場面では、PowerShell Remotingの認証の仕組みも絡みます。同時公開の「PowerShell Remoting/WinRM入門」を参照してください。

4. 対話実行と受け渡し ── PSCredential・SecureStringの役割

4.1 Get-Credentialで受け取る

対話的なスクリプトの基本形はこれだけです。

# ユーザー名とパスワードの入力を求め、PSCredentialオブジェクトとして受け取る
$cred = Get-Credential -Message '基幹DBの接続アカウントを入力してください'

# -Credentialパラメーターを持つコマンドにそのまま渡せる
Invoke-Command -ComputerName APPSV01 -Credential $cred -ScriptBlock { hostname }

Get-Credentialはユーザー名とパスワードの入力を促し、PSCredentialオブジェクトを返します。Windows PowerShell 5.1ではダイアログ、PowerShell 6以降ではコンソールでの入力になります。1 パスワードはPSCredentialの中でSecureStringとして保持され、画面には表示されません。「人がその場で入力する」運用が許される場面では、これ以上複雑なことをする必要はありません。

人が入力する場合の受け渡し人が入力した資格情報をPSCredentialとして受け取り、対応するコマンドのCredentialパラメーターへ渡します。人がユーザー名とパスワードを入力Get-CredentialPSCredentialコマンドのCredentialへ渡す

図7: 人が入力した資格情報をPSCredentialとして受け取り、対応するコマンドのCredentialパラメーターへ渡します。

4.2 自作スクリプトもPSCredentialで受け取る

自作関数を作る側は、パスワードを[string]で受けず、[PSCredential]の-Credentialパラメーターを受けるように設計します。書き方の詳細は公式の解説「Add Credential support to PowerShell functions」にまとまっています。14

# 自作スクリプトの受け口の作法: パスワードを[string]で受けない
[CmdletBinding()]
param(
    # 資格情報はPSCredentialで受ける。省略時はGet-Credentialで対話取得、
    # 無人実行ではImport-ClixmlやGet-Secretの結果を渡す
    [Parameter(Mandatory)]
    [System.Management.Automation.PSCredential]$Credential
)
# 秘密を含む変数はデバッグ出力しない。出すのはユーザー名まで
Write-Verbose "接続アカウント: $($Credential.UserName)"

この例のCredentialはMandatoryなので、呼び出し側で用意して渡します。コメントにある対話取得は、呼び出し側でGet-Credentialを使う場合として読み分けてください。無人実行なら5章・6章の取得結果を渡し、入力待ちを残さないようにします。

呼び出し側で資格情報を用意する対話入力や保存先から得たPSCredentialを呼び出し側で用意し、スクリプトへ渡します。対話ならGet-CredentialPSCredentialとして渡す無人なら保存先から取得秘密をデバッグ出力しない

図8: 対話入力や保存先から得たPSCredentialを呼び出し側で用意し、スクリプトへ渡します。

4.3 SecureStringの「非推奨」とPowerShellでの役割を分ける

SecureStringには知っておくべき事実が3つあります。

  • .NET公式は「新規開発では使わないことを推奨」しています。内部配列の暗号化はWindowsでのみ行われ、非Windowsプラットフォームでは内部ストレージが暗号化されません。また使う瞬間には結局平文表現へ変換されるため、露出時間を短くする効果しかありません。推奨される代替は「プロセス外に保存された資格情報への不透明なハンドル」、つまりOSの資格情報ストアやKey Vaultのような仕組みです。15
  • それでもPowerShellの標準装備はSecureString前提です。PowerShellは互換性のためSecureStringをサポートし続けており、コンソールやログへの偶発的な露出を避ける用途で今も使われています。平文文字列よりは安全である、という位置づけです。9
  • 簡単に平文へ戻せます。PowerShell 7ではConvertFrom-SecureString -AsPlainTextの一発で平文になります。16 SecureStringは「読めない金庫」ではなく「うっかり見えないための封筒」程度に考えるのが実像に合っています。

実務の結論はこうです。SecureStringを理由に複雑な自作暗号化へ走らない。PowerShellの道具(Get-Credential、SecretManagement)が要求する形式として使い、保護の本体はDPAPIやボールトなどプロセス外の仕組みに置く。

受け渡しの形式と保存の保護は別SecureStringはPowerShellの受け渡しに使い、保存する秘密の保護はDPAPIやボールトへ委ねます。PowerShellの標準形式を使う偶発的な画面・ログ露出を減らす保存する秘密を保護するDPAPIやボールトを使うSecureStringを万能な金庫にしない

図9: SecureStringはPowerShellの受け渡しに使い、保存する秘密の保護はDPAPIやボールトへ委ねます。

5. Windowsの無人実行 ── DPAPIで保存する条件と準備

タスクスケジューラで動く無人スクリプトは、Get-Credentialで人に聞くわけにいきません。別の秘密が必要な場合、最小構成の実用解がExport-Clixmlによる資格情報ファイルです。

5.1 保存したユーザー・コンピューターで使う

Export-Clixmlは資格情報オブジェクトをWindowsのDPAPI(データ保護API)で暗号化して保存します。復号できるのは、保存したユーザーアカウントが、保存したそのコンピューター上で開いたときだけです。エクスポートしたファイルは別のマシンや別のユーザーでは使えません。417 ファイルが持ち出されても開けない、という性質が無人実行の保存先として都合がよいわけです。

資格情報ファイルを開ける条件WindowsのExport-Clixmlで保存した資格情報は、保存したユーザーとコンピューターの組み合わせで復号します。実行アカウントと保存する端末Export-Clixmlで暗号化保存同じアカウント・同じ端末で読むImport-Clixmlで資格情報を取得

図10: WindowsのExport-Clixmlで保存した資格情報は、保存したユーザーとコンピューターの組み合わせで復号します。

「同一ユーザー・同一マシン限定」は保護であると同時に、導入前に確認すべき条件です。

  • タスクスケジューラの実行アカウントと合わせる必要があります。自分のアカウントで作った.credentialファイルは、タスクがサービスアカウントで動いた瞬間に復号できなくなります。準備作業は必ず「タスクの実行アカウントとして」行います(そのアカウントでPowerShellを起動して保存する)。タスクの実行アカウントとログオン種別の考え方は「タスクスケジューラのタスクが実行されない・0x1で終わる」で詳述しています。
  • サーバーの入れ替え・アカウント変更で必ず作り直しになります。「移行したら動かない」の定番原因なので、準備手順をスクリプト化してリポジトリに残しておきます(パスワード自体はもちろん含めません)。
  • 同一アカウントで動くプロセスからは復号できます。DPAPIはそのユーザーとして動くコードには開かれているため、実行アカウントに不要なソフトウェアを同居させない、アカウントの権限を最小にする、という周辺の衛生管理は引き続き必要です。
  • 非Windowsでは暗号化されません。macOS/Linuxでは実質平文(Unicode文字配列)で出力されると公式ドキュメントに明記されています。4
コピーできることと利用できることは違う別のサーバーやアカウントへ移るときは資格情報ファイルを作り直し、同じアカウントで動くコードへの対策も続けます。同じ端末・同じユーザー端末やユーザーを変更保存した資格情報ファイル使用する環境復号して利用できるその環境で作り直すアカウントの権限は最小にする

図11: 別のサーバーやアカウントへ移るときは資格情報ファイルを作り直し、同じアカウントで動くコードへの対策も続けます。

5.2 初回の対話準備と本番の無人処理を分ける

次の例は、前半が初回の保存、後半が本番での読み取りです。準備時と本番時で実行アカウントをそろえます。

# --- 準備(1回だけ、タスクの実行アカウントで実行する) ---
$cred = Get-Credential -Message '連携用アカウント'
$cred | Export-Clixml -Path 'D:\Jobs\secrets\transfer.credential'

# --- 本番スクリプト(無人実行) ---
$cred = Import-Clixml -Path 'D:\Jobs\secrets\transfer.credential'
# 例: 別資格情報が必要な共有への接続やリモート実行に使う
Invoke-Command -ComputerName FILESV01 -Credential $cred -ScriptBlock { Get-ChildItem D:\Export }

「そのアカウントでPowerShellを起動して保存する」の具体的な手順も書いておきます。上の準備部分(Get-CredentialとExport-Clixmlの2行)をD:\Jobs\save-credential.ps1として保存しておき、そのアカウントとしてPowerShellを起動して実行します。

# 準備をタスクの実行アカウントで行う: runasでそのアカウントとしてPowerShellを起動する
# パスワードはrunasが対話で聞くため、コマンドライン・履歴・トランスクリプトに平文が残らない
runas /user:CONTOSO\svc-transfer "pwsh.exe -NoProfile -NoExit -File D:\Jobs\save-credential.ps1"

runasは対話ログオン相当の権限を使うため、そのアカウントに「ローカル ログオン」を許可していない環境では失敗します。その場合は、準備作業の間だけログオンを許可し、保存が終わったら元の設定に戻すのが現実的です。Get-Credentialの入力自体に対話セッションが必要なので、「無人のタスクを1本作って準備する」方法は使えません。なお、実行アカウントのプロファイルが一度も作られていないマシンでは、初回のログオンでプロファイルが作られてからDPAPIのマスターキーが置かれる点も、この手順を踏む理由のひとつです。

無人実行の前に必要な対話準備実行アカウントで対話的に資格情報を保存する準備と、本番でファイルから読み込む処理を分けます。タスクの実行アカウントで起動対話で資格情報を入力Export-Clixmlで保存本番ではImport-Clixml入力を求めずジョブを実行

図12: 実行アカウントで対話的に資格情報を保存する準備と、本番でファイルから読み込む処理を分けます。

5.3 なぜ同じ環境に結び付くのか

なぜそうなるのかは、仕組みを一段だけ覗くと腑に落ちます。DPAPIはユーザーのログオン資格情報(通常はパスワードのハッシュ)から導いた鍵で「マスターキー」を暗号化し、それをそのユーザーのプロファイル内に置きます。データ本体は、そのマスターキーから作られるセッション鍵で暗号化されます。つまり鍵を握っているのは「そのユーザーとしてログオンできること」そのもので、鍵ファイルを別途どこかに保管する必要がない代わりに、鍵がユーザーとマシンに縛られます。公式ドキュメントも「通常、暗号化したユーザーと同じログオン資格情報を持つユーザーだけが復号でき、暗号化と復号は同じコンピューター上で行う必要がある」と説明しています。18 これが「同一ユーザー・同一マシン限定」の中身です。

DPAPIの鍵とデータの関係DPAPIはログオン資格情報に結び付いた鍵の階層でデータを保護するため、別に保管する鍵ファイルを用意せずに済みます。保護する生成に使う暗号化するログオン資格情報から導く鍵プロファイル内のマスターキーデータ用のセッション鍵保存するデータ

図13: DPAPIはログオン資格情報に結び付いた鍵の階層でデータを保護するため、別に保管する鍵ファイルを用意せずに済みます。

なお、ConvertFrom-SecureStringに-Keyを指定してAES暗号化する方法もありますが、こんどは「鍵ファイルをどう守るか」という同じ問題が一段ずれて残るだけになりがちです。16 マシンをまたぐ必要が出た時点で、6章のボールトか、3章の「持たない設計」に進むべきです。

6. 複数の秘密を管理する ── SecretManagementとSecretStore

接続先が増え、APIキーやトークンも混ざってくると、.credentialファイルの散在が管理限界を迎えます。そこで使うのがSecretManagementモジュールです。これは秘密の保管場所(ボールト)への統一インターフェースで、実際の保存は拡張ボールトが担います。ローカル保存のSecretStore(Microsoft製)のほか、Azure Key Vault、KeePassなどの拡張ボールトを同じコマンドで扱えます。719

6.1 インストール前に、実行アカウントと保守方針を確認する

gMSAなどの管理されたアカウントでは動きません。SecretManagementはプロファイル($env:LOCALAPPDATA)とDPAPIに依存しており、プロファイルを持たないWindowsの管理されたアカウントを現在サポートしていないと明記されています。19 gMSAで動くジョブに秘密を持たせたい場合は、この組み合わせは選べません(そもそもgMSAなら秘密を持たない設計に寄せられることが多い ── 3章)。

SecretManagement/SecretStoreは機能完成(feature complete)扱いで、積極的な新機能開発は終了しています。セキュリティ修正と重大バグの対応は継続されるため使うこと自体は問題ありませんが、公式も「秘密の性質はパスワードレスや連携資格情報へ変わりつつある」としており、長期の設計では認証方式そのものの見直し(Entra IDのマネージドID等)も視野に入れるべきです。7

採用前に実行アカウントを確認するSecretManagementとSecretStoreを選ぶ前にアカウントの制約を確認し、gMSAで動くジョブと混同しないようにします。gMSAなどそれ以外ジョブの実行アカウントを確認Windowsの管理されたアカウントかこの組み合わせは選ばないプロファイルと利用条件を確認保存先と解錠方法を選ぶ

図14: SecretManagementとSecretStoreを選ぶ前にアカウントの制約を確認し、gMSAで動くジョブと混同しないようにします。

6.2 インターフェースと保存先を分ける

SecretManagementは秘密を扱う共通の窓口で、実際に保存するのは登録した拡張ボールトです。コードでは秘密の名前を使い、保存先の選択はボールト登録で行います。719

# 初回のみ: モジュール導入とボールト登録
Install-Module Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore
Register-SecretVault -Name SecretStore -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault

# 秘密の登録(初回アクセス時にボールトのパスワードを設定する)
Set-Secret -Name TransferJobCred -Secret (Get-Credential)

# スクリプト側: 名前で取り出す。保存先が変わってもこの行は変わらない
$cred = Get-Secret -Name TransferJobCred

秘密の値にはPSCredentialやSecureStringのほか、文字列・バイト配列・ハッシュテーブルも保存でき、初回アクセス時にボールト自体を保護するパスワードの設定を求められます。20 利点は、スクリプトから「どこに保存されているか」の知識が消えることです。開発機ではSecretStore、本番ではAzure Key Vault(Az.KeyVaultモジュールが拡張ボールトを提供)という差し替えが、Register-SecretVaultの構成変更だけで済みます。67

秘密の名前とボールトを分離するスクリプトは共通コマンドで秘密を指定し、登録した拡張ボールトが実際の保存と取得を担います。秘密を名前で指定するスクリプトSet-Secret・Get-Secret登録した拡張ボールトローカルやクラウドの保存先

図15: スクリプトは共通コマンドで秘密を指定し、登録した拡張ボールトが実際の保存と取得を担います。

6.3 無人実行は、プロンプト停止と解錠を別々に設計する

SecretStoreは既定で対話的にボールトパスワードを要求します。そのままタスクスケジューラに載せるとプロンプト待ちで止まります。公式ドキュメントは無人実行向けに、InteractionをNoneにし、DPAPI保護ファイル(Export-Clixml)に退避したボールトパスワードをUnlock-SecretStoreで渡す構成を案内しています。5 つまりボールトの鍵は結局DPAPIで守ることになり、前章の同一ユーザー・同一マシン制約を引き継ぎます。パスワード要求自体を無効化(Authentication None)する構成も可能ですが、鍵がファイルシステム権限だけで守られることになるため、強い保護が必要な用途では推奨されていません。21

無人実行でボールトを解錠する対話を出さない設定に加えてDPAPIで保護したパスワードを供給し、SecretStoreを解錠してから秘密を取得します。InteractionをNoneに設定Unlock-SecretStoreで解錠DPAPI保護ファイルから鍵を読むGet-Secretで必要な秘密を取得同一ユーザー・同一端末が前提

図16: 対話を出さない設定に加えてDPAPIで保護したパスワードを供給し、SecretStoreを解錠してから秘密を取得します。

対話を出さないInteraction Noneと、パスワード要求そのものをなくすAuthentication Noneは別の設定です。 無人実行にするために保護まで外してしまわないよう、上記の供給方法と合わせて選びます。521

6.4 別のボールトでも、利用するユーザーを確認する

Windowsの資格情報マネージャー(Credential Manager)をボールトとして使うコミュニティ拡張も存在します。7 cmdkeyで登録した資格情報が自動で使われる仕組みなど、資格情報マネージャー自体の挙動は「ネットワークドライブとUNCパスの落とし穴」で触れたとおり、こちらもユーザープロファイル単位である点は同じです。

7. ログ・トランスクリプトに秘密を残さない

保存を固めても、実行時の記録から漏れては意味がありません。記録する仕組みの保護と、秘密を出力しない書き方の両方を確認します。

7.1 イベントログとトランスクリプトの露出を把握する

第一に、PowerShellは実行内容を記録する機能を複数持っており、組織設定で有効になっていることがあります。スクリプトブロックログを有効にすると、処理したすべてのスクリプトブロックの内容がイベントログに記録されます。公式ドキュメントは「スクリプトログを有効にすると、スクリプトで使われた資格情報や機微データがイベントログに書かれうる」と明示的に警告し、診断以外の用途では保護されたイベントログ(Protected Event Logging)の併用を推奨しています。10 保護されたイベントログは、対象のイベントログの内容をCMS(暗号メッセージ構文)の公開鍵で暗号化して書き込み、秘密鍵を持つ別の安全な場所でだけ復号できるようにする仕組みです。10 トランスクリプション(操作の書き起こし)も同様で、コンソールに流れた内容はファイルに残ります。

保存先とは別に記録も保護する保護されたイベントログは対象イベントを公開鍵で暗号化し、秘密鍵を置いた安全な場所で復号します。記録対象のイベント公開鍵で暗号化暗号化した内容をログへ保存安全な場所の秘密鍵で復号コンソールへ秘密を出さないトランスクリプトへの露出も防ぐ

図17: 保護されたイベントログは対象イベントを公開鍵で暗号化し、秘密鍵を置いた安全な場所で復号します。

7.2 保護されたイベントログと、5.1・7それぞれの設定を確認する

自分の環境がどうなっているかは、ポリシーが書き込むレジストリ値で確認できます。保護されたイベントログはグループポリシーの「コンピューターの構成 > 管理用テンプレート > Windows コンポーネント > イベント ログ記録(Event Logging)」にある「保護されたイベント ログを有効にする(Enable Protected Event Logging)」で設定され、対応するレジストリは次のとおりです。22

# 保護されたイベント ログが有効か(ポリシー未構成ならキー自体が存在しない)
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\EventLog\ProtectedEventLogging' `
    -Name EnableProtectedEventLogging -ErrorAction SilentlyContinue

# スクリプトブロックログ側のポリシー(PowerShell 7系)
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\ScriptBlockLogging' `
    -Name EnableScriptBlockLogging -ErrorAction SilentlyContinue

# Windows PowerShell 5.1側は別のキー
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' `
    -Name EnableScriptBlockLogging -ErrorAction SilentlyContinue

スクリプトブロックログのレジストリ値は、PowerShell 7系がPowerShellCore配下10、Windows PowerShell 5.1がWindows\PowerShell配下23と分かれています。5.1と7が同居しているサーバーでは、両方を確認してください。

確認の勘所は、スクリプトブロックログだけが有効で保護されたイベントログが無効という組み合わせです。これは「記録は取るが平文で貯める」状態なので、資格情報を扱うスクリプトを載せる前に把握しておいてください。

ログを取る設定と保護する設定を照合するPowerShellの各バージョンのログ設定と保護されたイベントログの設定を照合し、記録だけが有効な状態を把握します。5.1と7のログポリシーを確認有効な設定の組み合わせを照合保護されたイベントログを確認資格情報を扱う前に状態を把握

図18: PowerShellの各バージョンのログ設定と保護されたイベントログの設定を照合し、記録だけが有効な状態を把握します。

7.3 引数・画面・例外メッセージに秘密を出さない

記録の保護と同時に、「平文がコマンドラインや画面を通過しない」書き方を徹底します。

  • パスワードを引数で受け取る設計にしない。.\job.ps1 -Password "P@ssw0rd"と打った時点で、ログ・履歴・プロセス一覧に残る前提で考えます。受けるならPSCredentialかSecureStringで受けます。14

受け口のコード例は4.2節にまとめました。対話取得と無人での取得を呼び出し側で選び、PSCredentialとして渡します。

  • 秘密を含む変数をWrite-HostやWrite-Verboseでデバッグ出力しない。トラブルシューティング時の「一時的なつもり」の出力が、トランスクリプトに永続化します。
  • 例外メッセージへの混入に注意する。接続文字列を組み立ててからエラーを投げると、catchしたログに接続文字列ごと記録されがちです。エラー処理でログに何を書くかの設計は「PowerShellのエラー処理と再実行設計」も併せてどうぞ。

8. 実務の定石 ── 実行条件から最終確認する

3〜7章を確認したうえで、実際のジョブに合う方法を選びます。

状況 推奨 判断の目安
人がその場で実行する Get-Credential 保存しないのが一番安全。PSCredentialで受けて-Credentialへ渡す1
ドメイン内の無人ジョブ(接続先がWindows認証) 実行アカウント/gMSAへの権限付与 資格情報を持たない設計を最優先。gMSAはタスクスケジューラでも使える212
無人ジョブで秘密が1〜2個 Export-ClixmlのDPAPI保存 タスクの実行アカウント自身で保存。マシン・アカウント変更で作り直し4
無人ジョブで秘密が多数・保存先を将来差し替えたい SecretManagement+SecretStore ボールトパスワードはDPAPI保存+Unlock-SecretStoreで供給。gMSAでは使えない519
クラウド資源・複数サーバーで共有する秘密 Azure Key Vault(+SecretManagement) マシン局所のDPAPIでは共有できない。集中管理と監査が必要になったら6
スクリプト内の平文+ConvertTo-SecureString 書き換え対象 PSScriptAnalyzerがエラー判定する筋の悪い形。上のいずれかへ移行する8

迷ったら「持たない > マシンに固定して持つ(DPAPI) > ボールトで持つ」の順で、上から検討してください。

無人ジョブの保存方法を最終確認する保存方法だけでなく実行アカウント、解錠、移行時の再準備とログへの露出まで確認して運用へ進めます。必要な秘密だけを保存実行アカウントを確認無人での取得・解錠を確認移行時の再準備を決めるログへ秘密を出さない

図19: 保存方法だけでなく実行アカウント、解錠、移行時の再準備とログへの露出まで確認して運用へ進めます。

9. まとめ

最初に検討するのは、スクリプトからパスワードをなくすことです。 Windows認証と実行アカウントへの権限付与、gMSA、マネージドIDを先に考え、別の秘密が必要な場合だけ保存方法を選びます。

対話実行はGet-CredentialとPSCredentialを基本にします。SecureStringはPowerShellの標準的な受け渡し形式として使い、自作の保護機構の土台にはしません。平文を変換しても、Git履歴・共有フォルダ・ログに残った元の平文は守れないからです。

Windowsの無人実行でDPAPIを使うなら、タスクの実行アカウント自身で保存し、同じコンピューターで使うことが要点です。移行時の作り直しと、非Windowsでは暗号化されない制約を含めて運用を決めます。秘密が増えたらSecretManagementと拡張ボールトで一元管理しますが、SecretStoreの解錠用パスワード、gMSAでは使えない条件、機能完成扱いの保守方針も確認します。

最後に、保存先を保護して終わりにしないでください。平文を引数・画面・例外メッセージへ出さず、スクリプトブロックログとトランスクリプトへの漏えいも設計に含める。 ここまでをそろえて、無人でも安全に扱える構成にします。

保存と実行と記録を一緒に確認する秘密の保存をなくせないかを検討したうえで、必要な秘密の受け渡し・保存・無人取得・記録を順に見直します。秘密を持つ必要を減らす標準の形式で受け渡す必要な秘密だけ保護して保存実行条件と解錠を確認ログへの露出も防ぐ

図20: 秘密の保存をなくせないかを検討したうえで、必要な秘密の受け渡し・保存・無人取得・記録を順に見直します。

関連記事

関連する相談領域

合同会社小村ソフトでは、運用スクリプト・定期バッチに埋め込まれた資格情報の棚卸しと安全な保管への移行、gMSAを含む実行アカウント設計、無人実行ジョブのセキュリティレビューを扱っています。「動いているから触れない」まま放置された平文パスワードの整理からご相談ください。

参考リンク

  1. Microsoft Learn, Get-Credential. Get-Credentialがユーザー名とパスワードの入力を促してPSCredentialオブジェクトを返すこと、Windows PowerShell 5.1ではダイアログ・PowerShell 6以降ではコンソールで入力を求めること、-Credentialパラメーターを持つコマンドに渡して使うことについて。 ↩ ↩2 ↩3

  2. Microsoft Learn, Group Managed Service Accounts overview. gMSAが自動パスワード管理とSPN管理の簡素化を提供する管理されたドメインアカウントであり、パスワード管理を管理者ではなくWindows OSが担うことについて。 ↩ ↩2 ↩3 ↩4

  3. Microsoft Learn, What are managed identities for Azure resources?. マネージドIDがEntra ID上のIDを使って資格情報を管理せずにトークンを取得する仕組みであること、システム割り当てとユーザー割り当ての2種類とその違いについて。 ↩ ↩2

  4. Microsoft Learn, Export-Clixml. Export-Clixmlが資格情報オブジェクトをWindowsのDPAPIで暗号化して保存すること、復号できるのが保存したユーザーアカウント・保存したコンピューター上のみでありエクスポートファイルを他のマシン・他のユーザーで使えないこと、非Windows(macOS/Linux)では暗号化されず実質平文で出力されることについて。 ↩ ↩2 ↩3 ↩4

  5. Microsoft Learn, Use the SecretStore in automation. 無人実行向けにSecretStoreのInteractionをNoneに構成すること、ボールトパスワードをExport-ClixmlでDPAPI暗号化したファイルに保存しUnlock-SecretStoreで解錠する構成、これがWindows限定の解決策であることについて。 ↩ ↩2 ↩3 ↩4

  6. Microsoft Learn, Use Azure Key Vault in automation. Az.KeyVault 3.3.0以降がSecretManagement拡張を含み、Register-SecretVaultでAzure Key VaultをSecretManagementのボールトとして登録してGet-Secret等で操作できることについて。 ↩ ↩2 ↩3 ↩4

  7. Microsoft Learn, Overview of the SecretManagement and SecretStore modules. SecretManagementが拡張ボールトへの統一インターフェースであること、SecretStoreがローカルファイルに暗号化保存するクロスプラットフォームの拡張ボールトであること、Azure Key Vault・KeePass・資格情報マネージャー(CredMan)などの拡張ボールトが存在すること、Secretモジュール群が機能完成扱いで積極開発を終了しセキュリティ修正のみ継続されることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  8. Microsoft Learn, AvoidUsingConvertToSecureStringWithPlainText. PSScriptAnalyzerがConvertTo-SecureStringの-AsPlainText使用をエラー(Severity: Error)として検出すること、暗号化を迂回して機微情報を平文でメモリに晒すこと、代替としてRead-Host -AsSecureStringやSecretStoreモジュールが挙げられていることについて。 ↩ ↩2 ↩3

  9. Microsoft Learn, Advisory Development Guidelines. .NETがSecureStringの新規利用を推奨しない一方で、PowerShellは後方互換性のためSecureStringをサポートし続けていること、平文文字列よりは安全であり、コンソールやログへの偶発的露出を避けるために使われていること、容易に平文へ変換できるため注意して使うべきことについて。 ↩ ↩2

  10. Microsoft Learn, about_Logging_Windows. スクリプトブロックログを有効にするとPowerShellが処理する全スクリプトブロックの内容がイベントログに記録されること、ログレベルを上げるとスクリプトで使われた資格情報などの機微データがログに含まれうること、PowerShell 7系でスクリプトブロックログを有効にするレジストリ値がHKLM:\Software\Policies\Microsoft\PowerShellCore\ScriptBlockLoggingのEnableScriptBlockLoggingであること、Protected Event LoggingがCMS(暗号メッセージ構文)の公開鍵でイベントログの内容を暗号化し、秘密鍵を持つ安全な場所で復号する仕組みであることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  11. Microsoft Learn, Secure group managed service accounts. gMSAのパスワードが240バイトのランダム生成であること、OSが30日ごとに自動変更するためパスワード変更の計画やサービス停止が不要になること、オンプレサービスのアカウント種別としてgMSAの使用が推奨されることについて。 ↩

  12. Microsoft Learn, Manage group Managed Service Accounts. gMSAをサービスコントロールマネージャー構成のサービス、IISアプリケーションプール、タスクスケジューラのタスクで使用できること、前提条件(ドメイン・フォレストの機能レベルがWindows Server 2012以上、Domain Admins/Enterprise Adminsのメンバーシップ、KDSルートキーの存在とKdsSvc運用ログのイベントID 4004での確認、ドメインコントローラー以外での作業にRSATが必要なこと)、New-ADServiceAccountでの作成手順とPrincipalsAllowedToRetrieveManagedPasswordの指定、Install-ADServiceAccount/Test-ADServiceAccountによる導入と確認について。 ↩ ↩2 ↩3 ↩4

  13. Microsoft Learn, Create the Key Distribution Services KDS Root Key. gMSAのパスワード生成にKDSルートキーが必要であること、Add-KdsRootKey -EffectiveImmediatelyでの作成と、全ドメインコントローラーへの複製収束のため最大10時間待つ仕様、検証環境向けにAdd-KdsRootKey -EffectiveTimeで過去日時を指定して待ち時間を回避する手順について。 ↩

  14. Microsoft Learn, Add Credential support to PowerShell functions. 自作関数にPSCredentialパラメーターを追加する方法、平文パスワードがさまざまなログに記録されるためConvertTo-SecureString -AsPlainTextの使用時に警告される理由について。 ↩ ↩2

  15. Microsoft Learn, SecureString Class. .NET(Core)の新規開発でSecureStringを使わないことが推奨されていること、暗号化がWindowsでのみ行われ非Windowsでは内部ストレージが暗号化されないこと、使用時には平文表現への変換が必要なこと、推奨代替がプロセス外に保存された資格情報への不透明なハンドルであることについて。 ↩

  16. Microsoft Learn, ConvertFrom-SecureString. SecureStringを暗号化された標準文字列に変換できること、鍵を指定しない場合はWindowsのDPAPIが、Key/SecureKey指定時はAESが使われること、-AsPlainTextで平文文字列へ直接変換できることについて。 ↩ ↩2

  17. Microsoft Learn, Import-Clixml. Import-ClixmlでExport-Clixmlが保存した資格情報・セキュア文字列を復元できること、これによりスクリプト内に平文パスワードを書くリスクを避けられることについて。 ↩

  18. Microsoft Learn, CryptProtectData function. 通常は暗号化したユーザーと同じログオン資格情報を持つユーザーだけが復号できること、暗号化と復号が通常は同じコンピューター上で行われる必要があること、関数がユーザーのログオン資格情報からセッション鍵を作って暗号化を行うことについて。 ↩

  19. Microsoft Learn, Understanding the SecretManagement module. SecretManagementが登録済み拡張ボールト経由で秘密を保存・取得する仕組みであること、ボールト登録がユーザーコンテキスト単位であること、$env:LOCALAPPDATAとDPAPIへの依存によりWindowsの管理されたアカウント(managed accounts)では現在動作しないことについて。 ↩ ↩2 ↩3 ↩4

  20. Microsoft Learn, Get started with the SecretStore module. Register-SecretVaultでのSecretStore登録、Set-Secret/Get-Secret/Get-SecretInfoの基本操作、初回アクセス時にボールトのパスワード設定を求められることについて。 ↩

  21. Microsoft Learn, Understanding the security features of SecretManagement and SecretStore. パスワード認証を完全に無効化した場合、復号鍵がファイルシステム権限のみで保護され、強いセキュリティ保護が必要なシステムには推奨されないことについて。 ↩ ↩2

  22. Microsoft Learn, ADMX_EventLogging Policy CSP. EnableProtectedEventLoggingポリシーがコンピューターの構成の「Windows コンポーネント > イベント ログ記録」にあること、対応するレジストリキーがSoftware\Policies\Microsoft\Windows\EventLog\ProtectedEventLogging、値名がEnableProtectedEventLoggingであることについて。 ↩

  23. Microsoft Learn, about_Logging (Windows PowerShell 5.1). Windows PowerShell 5.1でスクリプトブロックログを有効にするレジストリ値がHKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLoggingのEnableScriptBlockLoggingであることについて。 ↩

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

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

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

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

PowerShellスクリプトにパスワードを平文で書いてはいけないのはなぜですか?
スクリプトはコピーされ、共有され、履歴に残る前提の成果物だからです。Gitに一度コミットすれば履歴から消すのは困難で、共有フォルダに置けば読み取り権限のある全員に見え、スクリプトブロックログやトランスクリプトにはコマンドの内容がそのまま記録されます。つまり平文パスワードは「ファイルが見えた時点で漏えい確定」の構造を作ってしまいます。まず漏えい経路を締めるのではなく、パスワードをスクリプトの外の保護された場所へ出すことが対策の本筋です。
Export-Clixmlで保存した資格情報はどこまで安全ですか?
WindowsではDPAPI(データ保護API)で暗号化され、保存したユーザーアカウントかつ同じコンピューター上でしか復号できません。ファイルが盗まれても他のマシン・他のユーザーでは開けないため、無人実行用の保存先としては実用的な選択肢です。ただし同一アカウントで動くプロセスからは復号できるので万能ではなく、またmacOSやLinuxでは暗号化されず実質平文で出力される点に注意が必要です。タスクスケジューラから使う場合は、タスクの実行アカウント自身で保存ファイルを作る必要があります。
SecureStringは今でも使うべきですか?
.NETの公式ドキュメントは新規開発でのSecureString使用を推奨しないと明記しています。暗号化はWindowsでしか行われず、非Windowsでは内部が暗号化されません。また使う瞬間には結局平文へ戻す必要があります。一方でPowerShellの世界では、Get-CredentialやSecretManagementなど標準の道具がSecureStringを前提にしており、平文文字列で持ち回るよりは露出が減るため、当面は「PowerShellの標準的な受け渡し形式」として付き合うのが現実的です。自前で暗号化の仕組みを作る材料にはしないでください。
タスクスケジューラの無人実行でSecretStoreを使うとパスワード入力で止まりませんか?
既定構成のままでは止まります。SecretStoreは既定でボールトパスワードを要求し、対話プロンプトを出すためです。無人実行では、Interactionを None にし、ボールトパスワードをDPAPI保護のファイル(Export-Clixml)から読み込んでUnlock-SecretStoreで解錠する構成が公式ドキュメントで案内されています。ただしこれは「ボールトの鍵をDPAPIで守る」構成であり、結局DPAPIの制約(同一ユーザー・同一マシン)を引き継ぎます。その手前で、gMSAや実行アカウントの権限付与で資格情報自体を不要にできないかを先に検討してください。
そもそも資格情報を保存しない方法はありますか?
あります。むしろそれが第一選択です。Windowsのタスクやサービスは実行アカウントの権限で動くため、接続先(共有フォルダ、SQL ServerのWindows認証など)に実行アカウント自身の権限を付与すれば、スクリプトはパスワードを一切持たずに済みます。実行アカウントをgMSA(グループ管理サービスアカウント)にすれば、パスワードは240バイトのランダム値をOSが30日ごとに自動更新し、人間は誰もパスワードを知らない状態を作れます。gMSAはタスクスケジューラのタスクにも使えます。

著者プロフィール

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

小村 豪

合同会社小村ソフト 代表

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

ブログ一覧に戻る