PowerShellを埋め込んだバッチファイルをネットワーク上から管理者権限で実行する

会社でちょっとしたPowerShellスクリプトを作ったのだが、ユーザにはネットワーク上に置いたものをそのままダブルクリックで実行できるようにしたかった。

バッチファイルにPowerShellを埋め込んで1ファイルにした上で管理者権限で起動するものは既存のものが見つかったのだが、 管理者権限でネットワークの資格情報が引き継がれない場合に管理者権限での起動に失敗してしまうため、自分自身をローカルドライブにコピーしてから起動するようにするスニペットを作成した。

参考にさせてもらったサイトをもとに、

  • 管理者権限無しでバッチファイルを起動したときに、一時フォルダ(%TEMP%)に自分自身をコピーしてからバッチファイルを管理者権限で起動する
  • 管理者権限ありで起動したときは、自分自身の10行目以降をPowerShellコードとして実行した後、一時フォルダにある場合は最後に自分自身を削除する

という動作するコードが以下。

@ECHO OFF
net session > nul 2>&1
if %errorlevel% neq 0 (
 COPY /Y "%~0" "%TEMP%\%~nx0" > nul
 powershell start-process "\"%TEMP%\%~nx0\"" -verb runas
 exit
)
@powershell -NoProfile -ExecutionPolicy Unrestricted "$s=[scriptblock]::create((gc \"%~f0\"|?{$_.readcount -gt 9})-join\"`n\");&$s;if(\"%~f0\" -ilike \"$env:TEMP\*\"){rm \"%~f0\"}" %*&goto:eof
# この下からPowerShellコードを記述する
Write-Host "Hello"

Pause

バッチファイルではなく、VBScriptやJScriptを記述可能なwsfファイルにPowerShellスクリプトを埋め込む方法も見つけたので、同様にネットワークと管理者権限での起動に対応してみた。

コマンドプロンプトのウィンドウや「UNC パスはサポートされません。Windows ディレクトリを既定で使用します。」というメッセージを見せたくない場合にはこっち。

<job>
  <script type="text/jscript">//<![CDATA[
    var objShell = new ActiveXObject("WScript.Shell");
    var objFSO = new ActiveXObject("Scripting.FileSystemObject");
    var target = objFSO.GetSpecialFolder(2) + "\\" + WScript.ScriptName;
    if(objShell.Run("net session", 0, true) !== 0){
      objFSO.CopyFile(WScript.ScriptFullName, target, true);
      objShell.Run("powershell start-process wscript '\\\"" + target + "\\\"' -Verb RunAs", 0);
      WScript.Quit();
    }
    objShell.Run("powershell -NoProfile -ExecutionPolicy Unrestricted \"[xml]$x = gc '" + WScript.ScriptFullName + "'; iex $x.job.powershell.'#cdata-section'\"", 1, true);
    if(WScript.ScriptFullName.toLowerCase() === target.toLowerCase()){
      objFSO.DeleteFile(WScript.ScriptFullName);
    }
  //]]></script>
  <powershell><![CDATA[
Write-Host "Hello"

Pause
  ]]></powershell>
</job>

もとのスクリプトではVBScriptだったが、今後廃止するという方向が発表されているのでJScript9Legacyが発表されて少しは延命できそうなJScriptに書き直した。

なお、上記のどちらも引数には非対応。

参考