再起動に失敗した Windows をリモートから強制的に再起動する方法

Windows を強制的に再起動したい場合

Windows PC をネットワークに接続して複数台使用していると、時々、ドライバの調子やシステムサービスの調子が悪くなり、リモートから再起動することができない症状が発生することがあります。原因としては色々なことが考えられると思います (たとえば、システムサービスが終了命令に応答せず、かつ「生きています」通知を Windows にずっと発行し続けるなどの異常状態に入ると、再起動できなくなってしまいます)。
まず、通常リモートから Windows PC を再起動するには、以下のいずれかの方法を実施すると思います。

  1. shutdown.exe コマンドを用いてリモート PC を再起動する
  2. リモート PC にリモートデスクトップ等で接続し、スタートメニュー等から再起動する


しかし、これらの方法を行ってもときどき再起動に失敗し、再起動処理中のところで Windows が半分フリーズした感じになり、リモートからのログオンも、リモートからのシャットダウンコマンドの受付も実行できなくなってしまいます。
このような場合に telnet サーバーや ssh サーバーが Windows に入っていれば、それにログインして shutdown -r -f などを実行することはできますが、それでも再起動することができないことがあります。


上記のような現象が発生した場合は、通常は、現地へ行って電源ボタンを長押しして再起動するか、または電話などをかけてそこにいる人に頼んで再起動してもらうしかありません。

Windows のシャットダウン処理を省略して PC を強制的に再起動するコード

しかし、以下のようなプログラムを書き、これを対象の Windows PC 上にコピーして実行することで、強制的にコンピュータを再起動することができます。この場合の再起動はハードウェアレベルで強制的に行うため、Windows が半分フリーズしてしまったような状態でも、マシンのリセットボタンを押したのと同様に再起動することができます。遠隔地の再起動に失敗したサーバーや PC を再起動するためだけに現地へ行く必要がなく大変便利です。

// Shutdown Tool  by Daiyuu Nobori
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int do_shutdown()
{
	HINSTANCE hDll = LoadLibrary("ntdll.dll");
	NTSTATUS (NTAPI *NtShutdownSystem)(int action);
	int ret;
	HANDLE hToken;

	if (hDll == NULL)
	{
		printf("LoadLibrary(ntdll.dll) Error.\n");
		return -1;
	}

	NtShutdownSystem =
		(NTSTATUS (__stdcall *)(int))
		GetProcAddress(hDll, "NtShutdownSystem");

	if (NtShutdownSystem == NULL)
	{
		printf("GetProcAddress(NtShutdownSystem) Error.\n");
		return -1;
	}

	if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, &hToken))
	{
		TOKEN_PRIVILEGES tkp;

		LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);

		tkp.PrivilegeCount = 1;
		tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 

		AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
		CloseHandle(hToken);
	}
	ret = (int)NtShutdownSystem(1);

	printf("%u %u\n", ret, NtShutdownSystem);

	if (ret != 0)
	{
		printf("NtShutdownSystem Error: 0x%x\n", ret);
	}

	FreeLibrary(hDll);

	return ret;
}

int main(int argc, char **argv)
{
	printf("NT Shutdown Tool\n\n");

	if (argc <= 1 || _strcmpi(argv[1], "y"))
	{
		char tmp[64];

		printf("Are you sure? (y/n) :");
		memset(tmp, 0, sizeof(tmp));
		fgets(tmp, sizeof(tmp) - 1, stdin);

		if (_strcmpi(tmp, "y"))
		{
			return -1;
		}
	}

	return do_shutdown();
}

上記のコードは、自分が上記のような状況になったときに急いで調べて走り書きしたもので、いいかげんで間違っているかも知れませんが、ご了承ください。


試しに上記のコードをコンパイルして NtReboot.exe 等の名前でリンクし、Windows PC 上で実行してみてください (引数として y を付けるか、プロンプトで y を指定してください)。すると、その PC が Windows によるシャットダウン処理をせずに、いきなり再起動すると思います。(実験する前に、重要なデータは保存し、念のためディスクキャッシュをフラッシュしてから実施してください。また、コンピュータや OS 等が壊れたりデータが破損したりする可能性もありますのでご注意ください。)

リモートマシン上で EXE ファイルを実行する方法

上記のプログラムをコンパイルして NtReboot.exe を作成したとしても、それをリモートマシン上で実行できなければ意味はありません。
プログラムのコピーは、可能であれば管理共有 (C$ など) を用いるのが良いでしょう。不可能な場合は、リモートマシンに入って ftp 等の基本的なコマンドで TCP/IP 経由でプログラムを受け取ると良いでしょう。


実行する方法は以下の 3 種類くらいがあると思います。

  1. telnetssh 等があらかじめ対象の PC 上で動作していれば、それでログインして実行する。
  2. telnetssh 等がない場合は、対象の PC に mmc でリモート接続し、サービスとして telnet を有効にして起動した上で、1 の方法で実行する。
  3. WMI の RPC を用いてリモートプロセスを起動する。


3 の方法は http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=11401&forum=7 に載っている方法などを利用することができます。


これで、何百台もクライアント PC がある環境で、シャットダウンツール等で一括再起動しても調子が悪く Windows のシャットダウン処理中に固まってしまった PC を遠隔地から救済することができるかも知れません。


なお、上記の方法は、新たにユーザーモードプロセスを起動する必要があるため、Windows のシャットダウン処理がかなり進行してしまい、再起動直前のところで停止してしまったような場合は、利用できません。
そこで、上記の方法と似たようなことをカーネルモードドライバを活用して実現し、かつそれをリモートから実行する方法 というかなり危険ですがほとんどの場合は利用可能な方法を後日この日記に掲載する予定です。