相対パスを指定して、UAC が働く「管理者として」、バッチを起動するショートカットを作る

相対パスを指定して、コマンドやアプリケーションを管理者権限で実行するような、ショートカットを作成したい。

管理者としてでなければ、ショートカットのプロパティで「作業フォルダ」を空白にしておくと、エクスプローラからショートカットを起動したときに、ショートカットの場所がカレントディレクトリ(作業フォルダ)になることを利用して、

"%ComSpec%" /C START "" "<相対パス>" [引数...]

としたショートカットを作成することで実現できる。
(一瞬コマンドプロンプトが立ち上がる問題は、「実行時の大きさ」を最小化にしておくことである程度回避可能)

ところが、ショートカットの詳細設定から「管理者として実行」を選んでしまうと、カレントディレクトリが %SystemRoot%\system32 へ勝手に変更されてしまう。
こうなると、上記のショートカットでは %SystemRoot%\system32 からの相対パスを開こうとするため、意図した動作にならない。

このため、 UAC を起動させる ものと、カレントディレクトリの情報を渡す ものの 2つのショートカットを作成することで実現させる。

START コマンドを使うので、内容を理解する場合はこのコマンドのヘルプをよく読み込んでおくと良いかも知れない。

START ["タイトル"] [/D パス] (中略) [コマンド/プログラム] [パラメーター]

    "タイトル"  ウィンドウのタイトル バーに表示するタイトル。
    パス        開始するディレクトリ。

    コマンド/プログラム
                内部コマンドまたはバッチ ファイルの場合、コマンド プロセッサ
                は cmd.exe の /K オプションを使用して実行されます。
                これは コマンドの後でもウィンドウが残ることを意味
                します。

                内部コマンドまたはバッチ ファイルではない場合、そのプログラム
                はウィンドウ モードのアプリケーションまたはコンソール
                アプリケーションとして動作します。

   パラメーター
               コマンド/プログラムに渡すパラメーターです。

UAC を起動させるショートカット

まずは、指定した プログラム/コマンド を パラメータ付きで 管理者として実行するショートカットを作成する。

以下の様にプロパティを設定したショートカットを作成しよう。

続きを読む

リモートデスクトップ時に、バッチでサーバ側のPCをスリープさせる

リモートデスクトップとかやっていると、WoL で遠隔起動はできるのに、コマンドプロンプトにも PowerShell にも、(休止じゃなくて)スリープができるコマンドがなくて困る。
そんなときに、Windows7 以降で標準搭載している PowerShwll を使って、Win32API を直呼び出しして、休止する方法をご紹介。

使い方はとっても簡単。
下記の一行を hoge.bat fuga.cmd などと、適当なバッチファイルにして保存するだけ。
後はダブルクリックすれば、その PC をスリープ状態にできる。

@echo (Add-Type TempClass -Member '[DllImport("powrprof.dll")]public static extern bool SetSuspendState(bool Hibernate,bool ForceCritical,bool DisableWakeEvent);' -PassThru)::SetSuspendState($false,$false,$false) | powershell -c -

呼び出している Win32API については、MSDN の記事を参照: SetSuspendState function (Windows)
「SetSuspendState を呼び出す C# の関数を、PowerShell でコンパイルし、PowerShell から呼び出すようになっているコード」を、コマンドプロンプトで echo し、実行コマンドとして PowerShell にパイプで渡しているだけだ。

やってることはめんどくさいが、Windows7 以降なら追加のツールのインストールとかも必要なくすぐに作れるのがミソ。
PowerShell + Add-Type は本当に何でもできるから困る。

ちなみに、ちまたで見かける

shellrundll32.exe PowrProf.dll,SetSuspendState 0,1,0

とか言うのは間違った使い方だ。
Rundll32.exe の仕様 を見ればわかるとおり、このアプリケーションは、特定のプロトタイプに従った関数を呼び出すためのもので、任意の関数を呼ぶためのものではない。
上記のような呼び出し方をすると、実際には

   hwnd = (parent window handle)
   hinst = HINSTANCE of PowrProf.dll
   lpszCmdLine = "0,1,0"
   nCmdShow = (whatever the nCmdShow was passed to CreateProcess)

としたうえで、

   SetSuspendState(hwnd, hinst, lpszCmdLine, nCmdShow)

という呼び出しをしていることになってしまう。
これはもちろん SetSuspendState の本来の引数である (BOOLEAN Hibernate, BOOLEAN ForceCritical, BOOLEAN DisableWakeEvent) の bool 値3つに換算すると、(true, true, true) となるため (hwnd, hinst, lpszCmdLine いずれも 0 にならないj)、意図したスリープ状態にならない。
それどころか、イベントやタイマ、WoL から起動できなくなったりする為、Rundll32.exe を使うのは避けた方がいいだろう。

SetSuspendState に限っては、呼ばれる関数に対して呼び出す引数の数(正確には引数の数ではなく、引数の合計サイズ)が多いので問題にはならないが、場合によってはメモリ破壊を引き起こすこともあり得るので、 Rundll32.exe の扱いには注意が必要だ。

Windows コマンドプロンプトで、パイプでつないだコマンドの終了コードを取得

パイプにつないだとあるコマンドの終了コードを読みたい。

しかし、パイプ後の %errorlevel% の中身は、パイプ先の最後のコマンドの終了コードになる。
また、パイプの中身は独立したプロセスになっているようで、この中でいくら環境変数の設定を行っても、消えてしまうのだ。

…となっては仕方ない、いったん一時ファイルに書き出した後、その値を読み出すしかない。

set tmplog=%TEMP%\errlv_%DATE:/=%%RANDOM%%RANDOM%.log
(cd d:\nonexist & call echo %^^errorlevel% ^>"%tmplog%") | sort
for /f "usebackq" %A in ("%tmplog%") do echo result %A
del "%tmplog%"

パイプの呼び出し時と、all echo 時、そして実行時の 合計 3回 環境変数の展開が行われるので、最初の2回分を キャレットで回避しているのがミソ。

ちなみに、cd d:\nonexist は、フォルダが存在しない場合に errorlevel が返されるのを利用したもので、 sort はパイプで文字を受け取るデモになっている。(一行だけなので意味は無いけど…)

ただし、バッチファイルに書くときは、以下の様に % を重ねて書かなくてはならないので注意。

set tmplog=%TEMP%\errlv_%DATE:/=%%RANDOM%%RANDOM%.log
(cd d:\nonexist & call echo %%^^errorlevel%% ^>"%tmplog%") | sort
for /f "usebackq" %%A in ("%tmplog%") do echo result %%A
del "%tmplog%"

続きを読む

非同期でプログラムを実行するバッチを、パイプする

start コマンドを使い、非同期でプログラムを実行するバッチを作成し、そのバッチの出力をパイプすると、実行したプログラムが終了するまでパイプの処理が返らない。

pause.cmd

@echo pause
@exit /b

process.cmd

@echo off
Echo 処理1開始
Rem 処理1
Echo 処理2開始
start "" cmd /s /c "pause.cmd"
exit /b

と作成し、コマンドプロンプトで単に

process.cmd

とすれば、pause.cmd を閉じなくても次の処理に進めるのだが、

process.cmd | sort

などとパイプしたとたん、処理が返らなくなってしまう。

これの対処法はいくつかある。 続きを読む

[Powershell] UTF-8 でエンコードされた標準出力を受け取る

2バイト目に ASCII コードがくることを想定していないコンソールアプリなど、標準出力を UTF-8 として使用するコンソールアプリは少なくない。
しかし、PowerShell は標準では UTF-8 の標準出力をそのまま読み取ることができない。
(後述するが、単純な方法はある)

そういった場合でも、.NET Framework を駆使すれば、ちゃんと文字列として取得できる。

続きを読む

コマンドプロンプトのcompコマンドが、いちいち「ほかのファイルを比較しますか」と聞いてきてウザい

2019-06-12 追記:

いつからか不明だが、少なくとも Windows 10 の最近のバージョンでは、 comp.exe/M その他のファイルを比較するメッセージを表示しない オプションが追加されている。

以下は、 Windows 7 当時の話。


コマンドプロンプトで、2つのファイルまたはファイルセットの内容を比較するコマンド、comp.exe

しかしこのコマンド、終了時にいちいち
「ほかのファイルを比較しますか」
と聞いてきてかなりウザい。

これを表示させないオプションがなぜか存在しないのだ。

特にバッチでループさせ対するときに、毎回毎回停止してしまうので、
すこぶる迷惑なのだが…

で、これを回避する方法だが、 "N" を自動で入力させるほか無い。
つまるところ、

echo n | comp Data1 Data2

という風に、echoを使って "n" と改行をパイプしてやれば、めでたくcompでループすることができる。

仕組みがわかればなんてことはないのだが、気づかないとちょっと悩んでしまうところだ。