◆電源OFF、再起動、サスペンド・・・・電源管理 その1

[ ホーム ] [ 上へ ] [ 電源管理 その2 ]

唐突ですが冬場私の部屋はとても寒いです。なので大抵は布団にもぐって PCを使用してます。それはいいのですが、いい感じで温まってくるとそのまま電源つけっぱなしで 朝を迎えることがちょくちょくありました。もったいない、環境に悪い。そこで自動で電源を 切るためにちょっと調べました。
いろいろあるかとは思いますが、今回は以下の方法で電源OFF、ログオフ、再起動、サスペンド、 休止(ハイバーネーション)をやってみます。

  1. Windows APIを使用する
  2. WMI(Windows Management Instrumentation)を使用する
  3. WSH(Windows Script Host)を使用する

<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メソッドで行います。
このメソッドを実行するには

  1. 名前空間に接続するためのISWbemLocatorインターフェイスの生成(CoCreateInstance)
  2. ISWbemLocatorから目的のクラスがある名前空間(root\CIMV2)に接続して、その中にあるクラスの 各サービスの提供をあらわすISWbemServicesインターフェイスの取得。(ISWbemLocator::ConnectServer)
  3. ISWbemServicesから接続した名前空間内の目的のクラス(Win32_OperatingSystem)のインスタンス群を 取得(ISWbemServices::ExecQuery)。マルチOSでない場合はインスタンスは1つです。
  4. ISWbemServices::ExecQueryで取得したインスタンス群のインターフェイスISWbemObjectSetから Nextメソッドでインスタンスを取得。
  5. 取得したWin32_OperatingSystemクラスのインスタンス(ISWbemObject)から、メソッド情報を表す インスタンス群(ISWbemMethodSet)をISWbemObject::get_Methodsで取得。Win32_OperatingSystemに   あるメソッド群にはReboot、Shutdown、Win32Shutdownがあります。
  6. ISWbemMethodSet::ItemでWin32Shutdownメソッドを表すインスタンス(ISWbemMethod)を取得。
  7. ISWbemMethod::get_InParametersでWin32Shutdown引数に指定するパラメータを表すISWbemOjbect インスタンスを取得して、それを元にSpawnInstanceで新しいインスタンスを作成。
  8. 7.で作成した入力パラメータを表すISWbemObjectからget_Properties_で、引数群を表す ISWbemPropetySetを取得します。Win32ShutdownメソッドはFlagsとReservedの2つの受け取ります。
  9. ISWbemPropertySet::ItemでFlag引数を表すISWbemPropertyを取得。
  10. ISWbemProperty::put_Valueで目的の状態へ移行する値をVARIANT型で渡す。
  11. 4.で取得したWin32_OperatingSystemのインスタンスのメソッドExecuteMethodの引数に、メソッド名 「Win32Shutdown」、10.で設定した7.のISWbemObjectへのポインタを指定して実行。
  12. ExecuteMethodの最後の引数で指定した出力パラメータに結果を受け取っているので、それを評価。

という流れになります。
ただ、これだけでは権限がないので、(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で扱います。