システムエンジニアXの「プログラミング以外」の話 50代会社員SEのお仕事・副業・システム運用の経験談

GPOで実行中の.ps1を置換するとどうなる?ロック衝突の回避策

公開:

※本記事には広告を含みます。

はじめに

タスクスケジューラ等で定期実行させているクライアントローカルのPowerShellスクリプト(.ps1)を、グループポリシー基本設定(GPP)のファイル配布機能で新しいバージョンへ「置換(Replace)」または「更新(Update)」しようとした際、「もしちょうどスクリプトが実行中だったらどうなるのか?」と不安になったことはないでしょうか。

私も過去に、常駐監視に近い形で動くPowerShellスクリプトやバッチファイルをGPPで直接上書き配布したところ、一部の端末でファイルロックによる更新失敗(イベントID 4098)が多発したり、古いバッチの途中から新しい処理に化けて予期せぬエラーを起こしたりして肝を冷やした経験があります。

スクリプト言語の種類(.ps1 と .bat / .cmd)や、PowerShellエンジンがファイルを開く際のファイルハンドル仕様によって、上書き時の挙動は大きく異なります。

この記事では、GPO(GPP)で配布先の .ps1 ファイルを置換・更新する際、実行中プロセスと衝突したときのOS内部の挙動と、ロック衝突を100%防ぐ無停止アップデートの設計パターンを解説します。

読み終えるころには、本番稼働中のスクリプトを安全かつ確実に全端末へ更新配信できるようになります。

この記事の要点

  • PowerShell(powershell.exe)は .ps1 起動時にスクリプト全体をメモリへ一括読み込みしてファイルハンドルを閉じるため、実行中であってもミリ秒単位の読み込み瞬間を除き上書き自体は成功しやすい
  • ただし、読み込みの瞬間とGPPの置換が重なると 共有違反(エラー 0x80070020 / ERROR_SHARING_VIOLATION) となり、イベントID 4098を記録してその回のGPO適用が失敗する
  • 一方で .bat(バッチファイル)やドットソース読み込みを多用する構成では、実行中にファイルが書き換わると 「構文破壊(途中から別コードを実行)」 を起こす危険があるため、ラッパー経由の一時コピー実行またはバージョン別フォルダ管理が必須となる
項目 動作確認・検証環境
検証年月 2026年9月
対象OS Windows 11 Pro 23H2・24H2 / Windows Server 2022
対象エンジン Windows PowerShell 5.1 / PowerShell 7.4
関連イベントログ Applicationログ / ソース: GroupPolicy Files / イベントID: 4098

結論:実行中の「.ps1」にGPPで置換・更新がかかったときの挙動

結論からお伝えすると、.ps1 ファイルの実行中にGPPのファイル更新が走った場合、「①問題なく上書きされ次回実行から新コードになる」か、「②ファイルオープン瞬間と衝突してエラー 0x80070020 でスキップされる」 のいずれかになります。

実行中のPowerShellプロセスが途中で異常終了することはありませんが、タイミングや呼び出し構造によっては無視できないリスクが潜んでいます。

1. PowerShell(.ps1)は「一括メモリ読み込み」のためロック時間は極小

powershell.exe -File "C:\Scripts\monitor.ps1" のようにスクリプトを起動した場合、PowerShellエンジンは以下の順序で動作します。

  1. monitor.ps1 を開き、ファイル内のテキストをすべてメモリ上のAST(抽象構文木)へパース(解析)する
  2. パースが完了した時点で、即座に .ps1 ファイルのハンドルをクローズ(ロック解除)する
  3. メモリ上のコードにもとづいてスクリプト処理を最後まで実行する

つまり、スクリプトが10分間スリープやループ処理を続けていたとしても、ファイル自体が開かれているのは 起動直後の数十ミリ秒間だけ です。そのため、実行中にGPPがファイルを「置換」しても、大半のケースではロック衝突せずにディスク上のファイルが新バージョンへ書き換わり、現在稼働中のプロセスは古いコードのまま正常に完走します。

2. ロック衝突時に発生する「0x80070020(共有違反)」と「ファイル消失」の罠

しかし、タスクスケジューラで1分おきに高頻度実行している場合や、ログオン時にGPO適用とスクリプト起動が同時並行で走った場合、ファイルを開いている瞬間やISE・エディタで開いている状態とGPPの処理が正面衝突することがあります。

このとき、クライアントのイベントビューアー(Windows ログ > Application)には以下のエラーが記録されます。

ログの名前: Application
ソース:     GroupPolicy Files
イベント ID: 4098
レベル:     警告
説明:       グループ ポリシー ファイル の基本設定により、'C:\Scripts\monitor.ps1' にある 
            グループ ポリシー オブジェクト 'Deploy-MonitoringScript {GUID}' の設定を適用できませんでした。
            エラー コード: '0x80070020 プロセスはファイルにアクセスできません。別のプロセスが使用中です。'

ここで特に注意すべきなのが、GPPのアクションを 「置換(Replace)」 にしている場合です。
「置換」は「既存ファイルの削除 → 新規コピー」という2段階で動くため、削除直後やウイルス対策ソフト(Defender等)のスキャンロックと競合した際、まれに 既存ファイルだけ消えて新ファイルの配置に失敗する(または次回のGPO更新周期まで古いまま残る) という中途半端な状態を招きます。

3. 【比較表】スクリプト種別ごとの実行中上書きリスク

実は .ps1 以上に危険なのが、ランチャーとして併用されがちなバッチファイル(.bat / .cmd)です。ファイル種別ごとの上書き時の挙動を比較します。

ファイル種別 実行中のファイルロック挙動 実行中にGPOで中身が書き換わった場合の結果 危険度
PowerShell (.ps1) 起動時のみ一瞬オープンし、すぐ閉じる メモリ上の旧コードで完走する(起動瞬間のみ 0x80070020 エラー) ★★☆(中)
複数分割 .ps1(ドットソース) サブ .ps1 を呼び出す瞬間にその都度オープン メインが新・サブが旧(または逆)の バージョン不整合エラー が起きる ★★★(高)
バッチファイル (.bat / .cmd) 1行実行するごとにオープン→シーク→クローズを繰り返す 書き換わった後のバイト位置から別コマンドを読み込み、構文崩壊・暴走する ★★★(極高)
実行ファイル (.exe / .dll) プロセス終了までOSが排他ロックを維持する 常に 0x80070020 で上書き失敗する(実行中プロセスへの影響はなし) ★★☆(中)

バッチファイル(cmd.exe)は1行実行するたびに「前回読み込んだバイトオフセット位置」からファイルを読み直す仕様のため、実行途中にGPPで文字数の異なるファイルへ上書きされると、行の途中から文字列をコマンドとして解釈し、予期せぬ誤動作を引き起こします。


ロック衝突と不整合を100%防ぐ3つの実践アーキテクチャ

現場でPowerShellスクリプトや関連バッチをGPO(GPP)で安全に配布・更新するための3つの解決策を紹介します。

対策1:アクションは「置換」ではなく「更新(Update)」を使う

まず基本設定として、GPPの「ファイル」アクションは「置換(Replace)」ではなく 「更新(Update)」 を選択してください。

  • 「置換」は変更がなくても90分ごとのGPO更新のたびにファイルを削除・再作成するため、稼働中のタスクと衝突する確率が跳ね上がります。
  • 「更新」にしておけば、管理者がSYSVOL上の .ps1 ファイルを差し替えたとき(タイムスタンプが変わったとき)の1回だけしかファイル書き込みが走らないため、衝突リスクを劇的に減らせます。

対策2:実行時に %TEMP% へ退避コピーして動かす(シャドウ実行パターン)

高頻度で動くスクリプトや、長時間稼働するスクリプトにおいて最も確実な回避策が、「GPOで配布されたマスターファイル(C:\Scripts\master\main.ps1)を直接実行せず、起動直後に一時フォルダへコピーしてから実行する」 方式です。

タスクスケジューラからは以下のラッパーコマンド(または数行のランチャー .ps1)を呼び出すように設計します。

# ラッパースクリプト例(C:\Scripts\launcher.ps1)
# ※このランチャー自体は変更しないため、ロックや書き換え事故が起きない

$MasterScript = "C:\Scripts\master\worker.ps1"
$TempDir      = Join-Path -Path $env:TEMP -ChildPath "ScriptRunner"

if (-not (Test-Path -Path $TempDir)) {
    New-Item -Path $TempDir -ItemType Directory -Force | Out-Null
}

# プロセスID($PID)付きのファイル名で一時コピーを作成(多重起動やロックを完全回避)
$RunScript = Join-Path -Path $TempDir -ChildPath "worker_$PID.ps1"

try {
    # GPPが更新するマスターファイルを一瞬だけ読み取ってローカルTEMPへ複製
    Copy-Item -Path $MasterScript -Destination $RunScript -Force -ErrorAction Stop

    # 複製した独立ファイルを実行(実行中にマスターがGPOで更新されても一切影響を受けない)
    & $RunScript
}
catch {
    Write-EventLog -LogName Application -Source "Windows Error Reporting" -EventId 1000 -EntryType Warning -Message "スクリプト起動失敗: $($_.Exception.Message)"
}
finally {
    # 実行完了後に一時ファイルをクリーンアップ
    if (Test-Path -Path $RunScript) {
        Remove-Item -Path $RunScript -Force -ErrorAction SilentlyContinue
    }
}

この構成にしておけば、GPPが配布・更新する C:\Scripts\master\worker.ps1 はどのプロセスからも占有されないため、いつGPO更新が走ってもエラー 0x80070020 を出さずに無停止でアップデートが完了します。

対策3:複数ファイル構成なら「バージョン番号付きフォルダ」で配る

メインの .ps1 から設定ファイル(.json)や共通関数(.psm1)を読み込む構成の場合、ファイルを1つずつ上書きすると「スクリプトだけ新しくなり、設定ファイルがまだ古い」というタイムラグが発生します。

このようなケースでは、上書き更新ではなく以下のように フォルダ名にバージョンを含めて配布する のが安全です。

  1. 新バージョンを C:\Scripts\v1.2\ フォルダとしてGPPで新規配布する
  2. 同時にタスクスケジューラのGPP設定(「更新」)で、実行パスを C:\Scripts\v1.2\main.ps1 に切り替える
  3. 旧バージョンの C:\Scripts\v1.1\ は、次回以降のGPO更新で不要になってから削除する

これにより、実行中の v1.1 プロセスを一切邪魔することなく、次回起動分からクリーンに v1.2 へスイッチできます。


よくある質問(FAQ)

Q: GPOの「スタートアップスクリプト」や「ログオンスクリプト」でSYSVOL上の.ps1を直接指定している場合はどうなりますか?

A: スタートアップ/ログオンスクリプトはPC起動時やログオン時に1回だけSYSVOLからメモリへ読み込まれて実行されるため、ローカル配布のような上書きロック問題はほとんど起きません。ただし、管理者がSYSVOL上のファイルを編集保存している最中にユーザーがログオンすると読み込みエラーになるため、編集時は別名で作業してから一瞬でファイルを差し替えるのが鉄則です。

Q: GPPのファイル配布がロックエラー(0x80070020)で失敗した場合、いつ再試行されますか?

A: 次回のグループポリシー背景更新サイクル(既定では90分+0〜30分のランダムオフセット後)、または端末の再起動時・gpupdate /force の手動実行時に自動的に再試行されます。即座にリトライされるわけではありません。

Q: どのプロセスが .ps1 ファイルをロックしているか調べる方法はありますか?

A: PowerShellまたはSysinternalsの handle.exe、もしくはWindows標準の「リソースモニター(resmon.exe)」で特定できます。resmon.exe を管理者として起動し、[CPU]タブ > [関連付けられたハンドル] の検索窓に .ps1 のファイル名を入力すると、ファイルハンドルを掴んでいるプロセスのPIDと実行ファイル名が即座に判明します。


まとめ

GPO(GPP)でPowerShellスクリプト(.ps1)を配布・置換する際のポイントは以下の通りです。

  • .ps1 単体であれば起動直後にメモリへ一括読み込みされてファイルハンドルが閉じるため、実行中でも上書き自体は通りやすい
  • ただし、高頻度実行タスクやファイルオープン瞬間と重なると 0x80070020(共有違反)でイベントID 4098 が発生し更新がスキップされる
  • 呼び出し元に .bat(バッチファイル)を使っている場合、実行中の上書きは構文破壊を招くため絶対に避ける
  • 90分ごとの無駄な再作成を防ぐためアクションは 「更新(Update)」 とし、常駐・高頻度スクリプトは %TEMP% への退避コピー実行 または バージョン別フォルダ配布 を採用する

スクリプトの実行頻度とファイル構成に合わせて配布方式を設計し、ロック衝突のない安定した自動化基盤を運用しましょう。

関連記事

免責規定

この記事で提供される情報は、一般的なガイダンスを目的としており、すべての環境やシステムでの動作を保証するものではありません。

OSのバージョンやリリースによっては、記載されている事が実行できない、または異なる結果をもたらす可能性があります。 また、会社所有のパソコン、スマホ、タブレットなどでは、ポリシーや権限によって実行できない場合があります。

この記事の情報を使用することによって生じる問題や結果について、筆者およびサイト管理者は責任を負いません。 すべての操作は自己責任で行ってください。

最後までお読みいただきありがとうございました。
またお会いしましょう!

著者のおすすめ教材 & note実践記事

公式教材 豪華4大特典付き

PowerShell実務スクリプト50選

AD・Windows運用・障害対応をコマンド一発で自動化する現役SEの実務スクリプト集。

最新note 毎日定時で帰る現場ハック

ググっても出ない実務テク20選

社内SE現場の地味で細かすぎる面倒作業を一撃で解消する実践テクニック集。