唐突ですが冬場私の部屋はとても寒いです。なので大抵は布団にもぐって
PCを使用してます。それはいいのですが、いい感じで温まってくるとそのまま電源つけっぱなしで
朝を迎えることがちょくちょくありました。もったいない、環境に悪い。そこで自動で電源を
切るためにちょっと調べました。
いろいろあるかとは思いますが、今回は以下の方法で電源OFF、ログオフ、再起動、サスペンド、
休止(ハイバーネーション)をやってみます。
<1.Windows APIを使用する>
電源OFF、ログオフ、再起動はExitWindowsExを、
他の2つについてはSetSystemPowerStateの各関数を使います。しかし、Windows2000以降
ではただこの関数を呼んだだけでは実行できません。
MSDNで両関数のドキュメントをご覧になるとわかりますが、SE_SHUTDOWN_NAMEという
電源管理ができる権限を取得しなければなりません。
さらに、休止(ハイバーネーション)にしたい場合は、イメージを保持するディスク領域を作成してこれを
実行できるようにサポートしなければなりません。
<2.WMIを使用する>
なにかやろうとするとWMIは非常に
便利ですが今回は苦労しました。というより諦めました。電源OFF、再起動、サスペンドに関しては
Win32_OperatingSystemクラスにWin32Shutdownメソッドが実装されているので
問題ないですが、残り2つに関しては有効なメソッドを見つけられませんでした。もし、ご存知の方は
ご連絡いただけると有り難いです。実はらしいメソッドは定義されています。
Win32_BatteryなどのスーパークラスCIM_LogicalDeviceクラスにそれはあります。メソッド名は
SetPowerStateですが、ドキュメントのRemarksをみるとこう書かれています。
This method is currently not implemented by WMI.
To use this method, you must implement it in your own provider.
実装はしてへんで。使いたかったら自分でやんなはれということです。おいおい。
<3.WSHを使用する>
WSHのシェル機能を使って電源管理をおこなうコマンドを 実行します。電源OFF、ログオフ、再起動はshutdown.exe、他はRundll32.exeを使って Powrprof.dll内のSetSuspendStateを使用します。さらに補助的にpowercfg.exeも使います。
これら3つの方法をVC++2005、Excel 2000 - VBA で実装します。(ただし、3の方法はVC++では省略します。)
・VC++2005編
<1.APIを使用・・・対象OS Windows2000, WindowsXP>
VSのファイルメニューから新規プロジェクトを作成します。 Win32コンソールアプリケーションを選択してアプリケーションウィザードを起動します。
プロジェクト名 :ControlPower(任意) アプリケーションの種類:コンソールアプリケーション 追加のオプション :プリコンパイル済みヘッダ
を選択して完了ボタンをクリックしてください。 ウィザードが終了したらControlPower.cppに以下を記述してください。
// ControlPower.cpp : コンソールアプリケーションのエントリポイントを定義します。 // #include "stdafx.h" #include <windows.h> #include <Powrprof.h> #pragma comment(lib, "PowrProf.lib") #define POWEROFF 1 //パワーオフ #define LOGOFF 2 //ログオフ #define REBOOT 3 //再起動 #define STANDBY 4 //スタンバイ #define HIBER 5 //休止(ハイバーネーション) //特権変更プロトタイプ宣言 BOOL ControlPrivilege(LPCTSTR pszPrivilege, BOOL bEnable); //電源管理(引数に指定した状態へ移行) //引数 POWEROFF, LOGOFF, REBOOT, STANDBY HIBERのいずれか //戻値 成功0, 失敗0以外 int _tmain(int argc, _TCHAR* argv[]) { if (argc < 2) return 1; int status = _tstoi(argv[1]); if (! status) return 1; UINT uFlags[] = {0x1000, EWX_POWEROFF, EWX_LOGOFF, EWX_REBOOT}; BOOL bRet = FALSE; //特権有効化 if (! ControlPrivilege(SE_SHUTDOWN_NAME, TRUE)) return 1; switch (status) { case POWEROFF: case LOGOFF: case REBOOT: //Poweroff, Logoff, Reboot実行 bRet = ::ExitWindowsEx(uFlags[status] | EWX_FORCEIFHUNG, 0); break; case HIBER: if (! ::IsPwrHibernateAllowed()) //ハイバネーションがサポートされていない時 { BOOLEAN bIn = TRUE; //ハイバネーションを有効にする if (::CallNtPowerInformation(SystemReserveHiberFile, &bIn, sizeof(bIn), NULL, NULL)) { bRet = FALSE; break; } } case STANDBY: //Standby, Hibernation実行 bRet = ::SetSystemPowerState(status == STANDBY, TRUE); break; default: bRet = FALSE; } //特権無効化 ControlPrivilege(SE_SHUTDOWN_NAME, FALSE); return ! bRet; } //権限変更 //引数 pszPrivilege : 変更する権限名 // bEnable : 第一引数の権限を有効にする時TRUE //戻値 成功 TRUE, 失敗 FALSE BOOL ControlPrivilege(LPCTSTR pszPrivilege, BOOL bEnable) { HANDLE hToken = NULL; BOOL bRet = TRUE; LUID luid; TOKEN_PRIVILEGES tp; //アクセストークンopen bRet = ::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken); if (! bRet) return FALSE; while (TRUE) { //権限名(pszPrivilege)に対する一意識別子をluidに取得 bRet = ::LookupPrivilegeValue(NULL, pszPrivilege , &luid); if (! bRet) break; //権限変更用TOKEN_PRIVILEGES構造体設定 tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0; //権限を有効/無効化 bRet = ::AdjustTokenPrivileges(hToken, NULL, &tp, 0, NULL, NULL); if (! bRet) break; break; } ::CloseHandle(hToken); return bRet; } |
こちらで書いたように 目的のExitWindowsEx又はSetSystemPowerStateを実行するにはそのプロセスのアクセストークンで SE_SHUTDOWN_NAME権限が必要です。
その権限をControlPrivilegeで取得しています。実際、権限を変更しているのは AdjustTokenPrivilegeですがどのように変更するか(権限を有効又は無効にする)は第三引数に 指定するTOKEN_PRIVILEGES構造体へのポインタで指示することになります。
この構造体は変更する権限の数(PrivilegeCountメンバ)と、その権限を一意に 特定するLUIDと有効・無効を指示するフラグをメンバに持つLUID_AND_ATTRIBUTES構造体の配列で 構成されています。ところが、今変更しようとしている権限はSE_SHUTDOWN_NAMEという 「権限名」しかわからずLUIDはわかりません。(SE_SHUTDOWN_NAMEは実際はwinnt.hで 「SeShutdownPrivilege」という文字列で定義されています) 権限名からLUIDを取得しているのがLookupPrivilegeValueになります。
これでLUIDが得られたのでAdjustTokenPrivilegeを実行すればいいのですが、 AdjustTokenPrivilegeのドキュメントにこの関数の実行にはTOKEN_ADJUST_PRIVILEGES権限が 必要とあります。なので、OpenProcessTokenでアクセストークンのハンドルを取得するときに 第二引数でこの権限を指定しています。
SE_SHUTDOWN_NAME権限が有効になったらExitWindowsEx、 SetSystemPowerStateを実行します。ここで注意しているのが 休止(ハイバーネーション)の場合です。現在の状況をHDDに書き込むことになりますが、 それ用のファイルがメモリサイズ分だけ用意されてないといけません。休止をサポートしているか どうかは次で確認できます。(Windows XPの場合)
コントロールパネル → 電源オプション → 休止状態タブを選択。
ここで休止状態を有効にするチェックボックスが選択されていれば問題ないですが、 そうでない場合に休止を選択して上記プログラムを実行しても失敗します。そこで IsPwrHibernateAllowedで休止が有効かどうか確認して、有効になってなかったら CallNtPowerInformationでプログラム的にチェックをいれて有効にしています。
VC++の場合はこれがいちばんしっくりきます。 WindowsサービスではWMIでの方法を省略しましたので 次はWMIでの電源管理を書いてみます。
<2.WMIを使用する・・・対象OS Windows2000, WindowsXP>
冒頭でも書きましたがスタンバイと休止用にSetPowerState?が 定義されているが実装はされてないので現段階では電源OFF、再起動、ログオフの機能しか 実装できていません。しかも、コードも無駄に?長いです。 ではなぜ書くかというと単なる好奇心それだけです。
APIのときと同様にWin32コンソールプロジェクトを
任意の名前で作成して同様のオプションを選択して完了してください。
xxxx.cppファイルに以下を記述してください。
// ControlPowserByWMI.cpp : コンソールアプリケーションのエントリポイントを定義します。 // #define _WIN32_DCOM #include "stdafx.h" #include <comdef.h> #include <Wbemidl.h> #pragma comment(lib, "Wbemuuid.lib") //POWEROFF 1 //パワーオフ //LOGOFF 2 //ログオフ //REBOOT 3 //再起動 //STANDBY 4 //スタンバイ(不可) //HIBER 5 //休止 (不可) //WMIによる電源管理(シャットダウン, ログオフ, 再起動のみ) //(スタンバイと休止は不可) //引数 POWEROFF=1, LOGOFF=2, REBOOT=3のいずれか //戻値 成功0, 失敗1 int _tmain(int argc, _TCHAR* argv[]) { if (argc < 2) return 1; int status = _tstoi(argv[1]); if (status < 1 && status > 3) return 1; //int status = 1; HRESULT hr; int iFlag[] = {0x1000, 8, 0, 2}; //Win32Shutdownメソッド引数用 ISWbemLocator *pLocator = NULL; //ISWbemLocatorインターフェイス ISWbemServices *pServices = NULL; //名前空間 //Win32_OperatingSystemインスタンス取得用 ISWbemObjectSet *pObjSet = NULL; IUnknown *pUnk = NULL; IEnumVARIANT *pEnumObj = NULL; VARIANT vItem; ISWbemObject *pObject = NULL; //Win32Shutdownメソッドインスタンス取得用 BSTR strMethodName = ::SysAllocString(_T("Win32Shutdown")); ISWbemMethodSet *pObjMethodSet = NULL; ISWbemMethod *pObjMethod = NULL; //入力パラメータ設定用 ISWbemObject *pObjInParams = NULL; ISWbemObject *pInParams = NULL; ISWbemPropertySet *pInPropSet = NULL; ISWbemProperty *pInProp = NULL; VARIANT vInParam; //出力パラメータ ISWbemObject *pOutParams = NULL; ISWbemPropertySet *pOutPropSet = NULL; ISWbemProperty *pOutProp = NULL; VARIANT vOutParam; //COM初期化 hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) return 1; while (TRUE) { //セキュリティ初期化 hr = ::CoInitializeSecurity(NULL, -1, NULL,NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); if (FAILED(hr)) break; //ISWbemLocatorインターフェイス取得 hr = ::CoCreateInstance(CLSID_SWbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_ISWbemLocator, (LPVOID *)&pLocator); if (FAILED(hr)) break; //名前空間接続 hr = pLocator->ConnectServer(bstr_t(_T(".")), bstr_t(_T("root\\CIMV2")), NULL, NULL, NULL, NULL, 0, NULL, &pServices); if (FAILED(hr)) break; //認証情報設定 hr = ::CoSetProxyBlanket(pServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE); if (FAILED(hr)) break; //シャットダウン権限設定 //(A) ISWbemSecurity *pSecurity = NULL; ISWbemPrivilegeSet *pPrivilegeSet = NULL; ISWbemPrivilege *pPrivilege = NULL; while (TRUE) { hr = pServices->get_Security_(&pSecurity); if (FAILED(hr)) break; hr = pSecurity->get_Privileges(&pPrivilegeSet); if (FAILED(hr)) break; hr = pPrivilegeSet->Add(wbemPrivilegeShutdown, TRUE, &pPrivilege); if (FAILED(hr)) break; break; } if (pSecurity) pSecurity->Release(); if (pPrivilegeSet) pPrivilegeSet->Release(); if (pPrivilege) pPrivilege->Release(); if (FAILED(hr)) break; //(A') //Win32_OperatingSystemインスタンス取得 hr = pServices->ExecQuery(bstr_t(_T("SELECT * FROM Win32_OperatingSystem")), bstr_t(_T("WQL")), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pObjSet); if (FAILED(hr)) break; hr = pObjSet->get__NewEnum(&pUnk); if (FAILED(hr)) break; hr = pUnk->QueryInterface(IID_IEnumVARIANT, (void **)&pEnumObj); if (FAILED(hr)) break; VariantInit(&vItem); hr = pEnumObj->Next(1, &vItem, NULL); if (FAILED(hr)) break; hr = VariantChangeType(&vItem, &vItem, 0, VT_UNKNOWN); if (FAILED(hr)) break; hr = vItem.punkVal->QueryInterface(IID_ISWbemObject,(void **)&pObject); if (FAILED(hr)) break; //Win32Shutdownメソッド取得 hr = pObject->get_Methods_(&pObjMethodSet); if (FAILED(hr)) break; hr = pObjMethodSet->Item(strMethodName, 0, &pObjMethod); if (FAILED(hr)) break; //入力パラメータ設定 hr = pObjMethod->get_InParameters(&pObjInParams); if (FAILED(hr)) break; hr = pObjInParams->SpawnInstance_(0, &pInParams); if (FAILED(hr)) break; hr = pInParams->get_Properties_(&pInPropSet); if (FAILED(hr)) break; hr = pInPropSet->Item(bstr_t(_T("Flags")), 0, &pInProp); if (FAILED(hr)) break; VariantInit(&vInParam); vInParam.vt = VT_I4; vInParam.intVal = iFlag[status]; //引数の値を代入 hr = pInProp->put_Value(&vInParam); if (FAILED(hr)) break; //メソッド実行 hr = pObject->ExecMethod_(strMethodName, pInParams, 0, NULL, &pOutParams); if (FAILED(hr)) break; //出力パラメータ値取得 hr = pOutParams->get_Properties_(&pOutPropSet); if (FAILED(hr)) break; hr = pOutPropSet->Item(bstr_t(_T("ReturnValue")), 0, &pOutProp); if (FAILED(hr)) break; VariantInit(&vOutParam); hr = pOutProp->get_Value(&vOutParam); if (FAILED(hr)) break; VariantChangeType(&vOutParam, &vOutParam, 0, VT_UINT); break; } //戻値設定 int ret = (FAILED(hr) || vOutParam.uintVal) ? 1 : 0; //後始末 if (pOutParams) pOutParams->Release(); if (pOutPropSet) pOutPropSet->Release(); if (pOutProp) pOutProp->Release(); VariantClear(&vOutParam); if (pObjInParams) pObjInParams->Release(); if (pInParams) pInParams->Release(); if (pInPropSet) pInPropSet->Release(); if (pInProp) pInProp->Release(); VariantClear(&vInParam); ::SysFreeString(strMethodName); if (pObjMethodSet) pObjMethodSet->Release(); if (pObjMethod) pObjMethod->Release(); if (pObjSet) pObjSet->Release(); if (pUnk) pUnk->Release(); if (pEnumObj) pEnumObj->Release(); ::VariantClear(&vItem); if (pObject) pObject->Release(); if (pServices) pServices->Release(); if (pLocator) pLocator->Release(); ::CoUninitialize(); return ret; } |
まず、上記プログラムを理解するにはWMIについて概略を理解しておく必要があるので こちらを含めたMSのサイトも一読してください。
長いので流れを書きます。電源OFF、ログオフ、
再起動を行うメソッドはWMIのWin32_OperatingクラスのWin32Shutdownメソッドで行います。
このメソッドを実行するには
という流れになります。
ただ、これだけでは権限がないので、(A)〜(A')でwbemPrivilegeShutdown権限を
名前空間で付与しています。
※注
MSDNをご覧になると分かりますが、Win32_OperatingSystemのWin32Shutdownメソッドで第一引数に
電源OFFは8、シャットダウンは1となっています(上記プログラムではiFlag[]で定義されています)。
当方の環境ではWindows2000の場合8で正常にシャットダウン→電源OFFとなりますが、XPの場合、
シャットダウンまでいき電源が落ちません。
これを8の代わりに1を渡してやると電源OFFまでいくので、うまくいかない場合は
このことも参考にして下さい。
<3.WSH(Windows Script Host)を使用する>
ですが、VC++の場合、わざわざCOMでこのコンポーネントを使わなくても ShellExecuteをつかったりすればできそうなのでやりません。次のExcel2000 - VBAで扱います。