💥

Copilotと一緒に誤爆を防ぐBAT作ってみた

に公開

経緯

常駐先で作業者がサーバーをシャットダウンするBATを誤爆してしまい、システムが落ちてしまいました。本来は作業者の端末からシャットダウンされてしまったサーバーにはアクセスできないよう制御されているのですが、たまたま使用していた端末の設定が漏れており今回のシステムダウンが発生してしまいました。
このことがきっかけになりBATの誤爆を防ぐために、誤爆対策をしたBATを作ることになりました。

実現したい処理

まず、BATはタスクスケジューラーの操作として設定されています。
理想の動きとしては
タスクスケジューラ上でBATを実行した場合→実行
BATをエクスプローラーから直で実行した場合→実行していいか確認をしてから実行する
上記になります。

実現方法

今回はタスクスケジューラ上で実行された場合、BATに引数を持たせることで、
タスクスケジューラ上でBATを実行した場合とBATをエクスプローラーから直で実行した場合を区別することにしました。
また、実際のコードではsampleA.batからsampleB.batを呼び出しています。
実際に現場で使用されているBATを見ると上記のようにBATから別のBATを呼び出していることが多々あったので、誤爆によって複数のBATが連鎖しないよう1つ1つのBATを実行する前に確認を行うようにしています。
作業者からすると煩わしいかもしれませんが,,,

実際のコード

コード上では
タスクスケジューラ上でBATを実行した場合→自動
BATをエクスプローラーから直で実行した場合→手動
上記のように表記してあります。
また、
タスクスケジューラ上でBATを実行した場合→タスクスケジューラ上で引数を渡す
BATをエクスプローラーから直で実行した場合→引数なしで実行
とすることで自動or手動の判別を行っています。

sampleA.bat
@echo off

:: 引数取得(タスクスケジューラ上で引数を設定する)
set MODE=%1

setlocal
:: 自動or手動実行判定ロジック
if /i "%MODE%"=="auto" (
        echo [INFO] 自動実行と判定 → taskA.batの本処理実行、taskB.bat を呼び出します
        call :MainProcess
        goto :End
)

:: 手動実行と判定された場合
echo [WARN] 手動実行と判定されました
set /p USERCONFIRM=taskA.batの本処理、taskB.bat を実行しますか? (Y/N): 
if /i "%USERCONFIRM%"=="Y" (
    echo [INFO] ユーザーが実行を許可 → taskA.batの本処理、taskB.bat を呼び出します
    call :MainProcess
    goto :End
) else (
    echo [INFO] ユーザーがキャンセル → 処理終了
    goto :End
)

::taskA本処理
:MainProcess
echo [INFO] taskA.bat の本処理を実行中...
:: ここに本処理を書く
call "c:\sampleB.bat" MODE
goto :eof

::終了処理
:End
if /i not "%MODE%"=="auto" (
    pause
)
endlocal
goto :eof
sampleB.bat
@echo off
setlocal

:: 自動or手動実行判定ロジック
if /i "%MODE%"=="auto" (
        echo [INFO] 自動実行と判定 → taskB.batの本処理を呼び出します
        ::taskB.batの本処理を記述
        call :MainProcess
        goto :End
)

:: 手動実行と判定された場合
echo [WARN] 手動実行と判定されました
set /p USERCONFIRM=taskB.batの本処理を実行しますか? (Y/N): 
if /i "%USERCONFIRM%"=="Y" (
    echo [INFO] ユーザーが実行を許可 → taskB.batの本処理を実行
    ::taskB.batの本処理を記述
    call :MainProcess
    goto :End
) else (
    echo [INFO] ユーザーがキャンセル → 処理終了
    goto :End
)

::taskB本処理
:MainProcess
echo [INFO] taskB.bat の本処理を実行中...
:: ここに本処理を書く
goto :eof

::終了処理
:End
echo [INFO] taskB.bat 処理完了
endlocal
goto :eof

Copilotと開発するときの過程

BATを開発しているときは下記のようにCopilotを使っていました。
BATの基本的な書き方を勉強している時間がない中で開発を行っていたので、コードの記述はほぼCopilotを頼っていました。

フェーズ1

常駐先の方から指示された内容から、Copilotと対話しながら実現したいことを整理する

フェーズ2

整理した実現したいことを基にしてCopilotにベースプログラムを作ってもらう。

フェーズ3

ベースプログラムを基にプログラムを作成する。上手くいかない場合はCopilotと相談して改善する。

Copilotと開発をしてみて

今回Copilotと開発をしてみて、まったく知らない言語の開発でも意外となんとかなってしまうことに驚きました。(今回の開発内容が簡単だったからかもしれませんが,,,)
未知の言語を学習する際に、簡単なコードをCopilotに記述してもらいそれをベースにして自分で開発をしてみるというような使い方をすると効率よく勉強できるのではないかと感じました。
私の勤務している会社ではまだAIを利用した開発が盛んではなく、今回の開発はAIと開発するとはどういうことなのかを知るいいきっかけになりました。

余談

今回は
タスクスケジューラ上でBATを実行した場合→実行
BATをエクスプローラーから直で実行した場合→実行していいか確認をしてから実行する
上記の処理を実装しましたが、
もともとは
タスクスケジューラ上でスケジュール通りにBATを実行した場合→実行
タスクスケジューラ上で手動でBATを実行した場合→実行していいか確認をしてから実行する
BATをエクスプローラーから直で実行した場合→実行していいか確認をしてから実行する
という処理を目指していました。
しかし、
タスクスケジューラ上でスケジュール通りにBATを実行した場合→実行
タスクスケジューラ上で手動でBATを実行した場合→実行していいか確認をしてから実行する
上記2つの区別をつけられず断念しました。

自動実行判定ロジックの部分でタスクスケジューラ上でタスクを実行する場合、
タスクスケジューラ上でスケジュール通りにBATを実行した場合とタスクスケジューラ上で手動でBATを実行した場合はどちらも親プロセスが taskeng.exeになってしまうらしく結局没になりました。

@echo off
setlocal

:: 引数取得
set MODE=%1

:: PowerShellで自身の親プロセスIDを取得 → 一時ファイルに保存
powershell "Get-WmiObject win32_process -filter processid=$pid | ForEach-Object{$_.parentprocessid;}" > PID.txt
set /p VALUE=<PID.txt
del PID.txt
echo [DEBUG] 親プロセスID: %VALUE%

:: PowerShellで親プロセス名を取得 → 一時ファイルに保存
powershell -Command ^
    "$ppid = (Get-CimInstance Win32_Process -Filter \"ProcessId=$PID\").ParentProcessId; ^
     $parent = Get-CimInstance Win32_Process -Filter \"ProcessId=$ppid\"; ^
     $parent.Name" > PPNAME.txt

:: 一時ファイルから親プロセス名を読み込み
set /p PPNAME=<PPNAME.txt
del PPNAME.txt

:: 親プロセス名を表示
echo [DEBUG] 親プロセス名: %PPNAME%
echo [INFO] 実行モード: %MODE%

:: 自動実行判定ロジック(引数が "auto" かつ親プロセスが taskeng.exe)
if /i "%MODE%"=="auto" (
    if /i "%PPNAME%"=="taskeng.exe" (
        echo [INFO] 自動実行と判定 → taskA.bat の本処理を実行、taskB.bat を呼び出します
        call :MainProcess
        goto :End
    )
)

:: 手動実行と判定された場合 → ユーザー確認を挟む
echo [WARN] 手動実行と判定されました
set /p USERCONFIRM=taskA.bat の本処理、taskB.bat を実行しますか? (Y/N): 
if /i "%USERCONFIRM%"=="Y" (
    echo [INFO] ユーザーが実行を許可 → taskA.bat の本処理、taskB.bat を呼び出します
    call :MainProcess
    goto :End
) else (
    echo [INFO] ユーザーがキャンセル → 処理終了
    goto :End
)

:: taskA の本処理
:MainProcess
echo [INFO] taskA.bat の本処理を実行中...
:: ここに本処理を書く(例:taskB.bat を呼び出す)
:: call "C:\Path\To\taskB.bat"
goto :eof

:: 終了処理
:End
endlocal
goto :eof

Discussion