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

[ ホーム ] [ 上へ ]

・Excel 2000 - VBA編

<1.Windows APIを使用する>

Excelを起動してAlt + F11を押してVBエディタを開き、 挿入メニューから標準モジュールを追加してください。
新規のモジュールができたら以下を記述してください。

Option Explicit

'現在のプロセスハンドル取得
Private Declare Function GetCurrentProcess Lib "kernel32" () As Long

'アクセストークン オープン
Private Declare Function OpenProcessToken Lib "advapi32.dll" _
    (ByVal ProcessHandle As Long, _
     ByVal DesiredAccess As Long, _
     TokenHandle As Long) As Long

'特権名よりローカル一意識別子取得
Private Declare Function LookupPrivilegeValue Lib "advapi32.dll" _ 
                                               Alias "LookupPrivilegeValueA" _
    (ByVal lpSystemName As String, _
     ByVal lpName As String, _
     dlpLuid As LUID) As Long  '第三引数データ型変更

'アクセストークン内の特権の有効/無効化
Private Declare Function AdjustTokenPrivileges Lib "advapi32.dll" _
    (ByVal TokenHandle As Long, _
     ByVal DisableAllPrivileges As Long, _
     NewState As TOKEN_PRIVILEGES, _
     ByVal BufferLength As Long, _
     PreviousState As Long, _
     ReturnLength As Long) As Long   '第五引数データ型変更

'ハンドルClose
Private Declare Function CloseHandle Lib "kernel32" _
    (ByVal hObject As Long) As Long

'ログオフ/シャットダウン/再起動
Private Declare Function ExitWindowsEx Lib "user32" _
    (ByVal uFlags As Long, _
     ByVal dwReserved As Long) As Long 

'システム中断
Private Declare Function SetSystemPowerState Lib "kernel32.dll" _
    (ByVal fSuspend As Long, _
     ByVal fForce As Long) As Long

'Hibernationサポート確認
Private Declare Function IsPwrHibernateAllowed Lib "Powrprof.dll" () As Byte

'Hibernation有効/無効化
Private Declare Function CallNtPowerInformation Lib "Powrprof.dll" _
    (ByVal InformationLevel As POWER_INFORMATION_LEVEL, _
     lpInputBuffer As Byte, _
     ByVal nInputBufferSize As Long, _
     lpOutputBuffer As Long, _
     ByVal nOutputBufferSize As Long) As Long


'(定数)
Private Const TOKEN_QUERY             As Long = &H8
Private Const TOKEN_ADJUST_PRIVILEGES As Long = &H20

Private Const EWX_LOGOFF      As Long = &H0
Private Const EWX_REBOOT      As Long = &H2
Private Const EWX_POWEROFF    As Long = &H8
Private Const EWX_FORCEIFHUNG As Long = &H10

Private Const SE_SHUTDOWN_NAME As String = "SeShutdownPrivilege"

Private Const SE_PRIVILEGE_ENABLED As Long = &H2


'(構造体)
'ローカル一意識別子
Private Type LUID
    lowpart  As Long
    highpart As Long
End Type

Private Type LUID_AND_ATTRIBUTES
    pLuid      As LUID
    Attributes As Long  '特権を有効にするにはSE_PRIVILEGE_ENABLEDを指定
End Type

'AdjustTokenPrivilegesで使用する変更する特権情報を保持する構造体
Private Type TOKEN_PRIVILEGES
    PrivilegeCount  As Long  '変更する特権数
    Privileges(0)   As LUID_AND_ATTRIBUTES
End Type

'(列挙体)
Private Enum POWER_INFORMATION_LEVEL
    SystemReserveHiberFile = 10
End Enum

Public Enum POWER_STATUS
    POWEROFF = 1
    LOGOFF = 2
    REBOOT = 3
    STANDBY = 4
    HIBER = 5
End Enum
'----------------------------------------------------------------------------------

'Win32APIによる電源管理(引数に指定した状態に移行)
'引数 argeStatus : 列挙体 POWEROFF, LOGOFF, REBOOT, STANDBY, HIBERのいずれか
'戻値 成功 0以外, 失敗 0
Public Function GFnControlPowerByAPI(argeStatus As POWER_STATUS) As Long
    Dim vlRet           As Long '戻値フラグ 
    Dim vlFlag(3)       As Long 'Poweroff, Logoff, Reboot用配列
    Dim vcIn            As Byte 'Hibernation有効フラグ

    'SE_SHUTDOWN_NAME特権の有効化
    If FnControlPrivilege(SE_SHUTDOWN_NAME, True) = 0 Then Exit Function

    vlFlag(0) = &H10000: _
    vlFlag(1) = EWX_POWEROFF: _
    vlFlag(2) = EWX_LOGOFF: _
    vlFlag(3) = EWX_REBOOT

    Do
        If argeStatus <= REBOOT Then
            'シャットダウン/ログオフ/再起動
            vlRet = ExitWindowsEx(vlFlag(argeStatus) Or EWX_FORCEIFHUNG, 0)

        Else
            'Hibernationの場合
            If argeStatus = HIBER Then
                If IsPwrHibernateAllowed() = 0 Then 'Hibernation無効の時
                    'Hibernation有効化
                    vcIn = 1
                    If CallNtPowerInformation(SystemReserveHiberFile, _
                                              vcIn, LenB(vcIn), ByVal 0, 0) <> 0 Then
                        vlRet = 0: Exit Do
                    End If
                End If
            End If

            'サスペンド/休止
            vlRet = SetSystemPowerState((argeStatus = STANDBY), 1)
        End If

        Exit Do
    Loop

    '特権無効化
    FnControlPrivilege SE_SHUTDOWN_NAME, False

    GFnControlPowerByAPI = vlRet
End Function  
'----------------------------------------------------------------------------------

'特権の有効化/無効化
'引数 argsPrivilege
'戻値 成功 0以外  失敗 0
Private Function FnControlPrivilege(argsPrivilege As String, _
                                    argyEnable As Boolean) As Long
    Dim vlRet        As Long
    Dim vlHToken     As Long
    Dim vtLuid       As LUID
    Dim vtTp         As TOKEN_PRIVILEGES

    'アクセストークン オープン
    vlRet = OpenProcessToken(GetCurrentProcess(), _
                             TOKEN_QUERY Or TOKEN_ADJUST_PRIVILEGES, _
                             vlHToken)

    If vlRet = 0 Then Exit Function

    Do
        '特権名(argsPrivilege)からローカル一意識別子(vtLuid)の取得
        vlRet = LookupPrivilegeValue(vbNullString, argsPrivilege, vtLuid)

        If vlRet = 0 Then Exit Do

        'TOKEN_PRIVILEGES構造体の作成
        vtTp.PrivilegeCount = 1
        vtTp.Privileges(0).pLuid = vtLuid
        vtTp.Privileges(0).Attributes = IIf(argyEnable, SE_PRIVILEGE_ENABLED, 0)

        '特権の有効化/無効化
        vlRet = AdjustTokenPrivileges(vlHToken, 0, vtTp, 0, ByVal 0, ByVal 0)

        If vlRet = 0 Then Exit Do

        Exit Do
    Loop

    'ハンドルClose
    CloseHandle vlHToken

    FnControlPrivilege = vlRet
End Function

記述できたらブックを保存して、イミディエイトウィンドウやワークシートから GFnControlPowerByAPIを任意の引数を渡して実行してください。

APIの宣言に関しては Win32Api.txt にありますが、LookupPrivilegeValueの第三引数はLARGE_INTEGERとして定義されていますが、 C言語ではLUIDへのポインタで宣言されているので 型名を変更しています。または、 この関数の定義を変えないで、LUID構造体名をLARGE_INTEGERとしてもいいかもしれません。

また、AdjustTokenPrivilegesの第五引数についても Win32Api.txt はTOKEN_PRIVILEGE構造体を参照渡しするようになっていますが、今回はこの引数は使用せずNULL(0)を 指定するためにLong型に変更しています。

IsPwrHibernateAllowed、CallNtPowerInformationの両関数については Win32Api.txt にないので、C言語での宣言を参考に作成しています。

構造体は Win32Api.txt にあるのでそのまま使えますが、POWER_INFORMATION_LEVEL列挙体はないので CallNtPowerInformationのドキュメントで確認してください。

ロジックはVC++の場合と同じなので 各APIのドキュメントとともに確認してください。

<2.WMIを使用する・・・対象OS Windows2000, WindowsXP>

VBAの場合は VC++の時と大違いで楽にできます。 ただやはりスタンバイと休止は自分で実装しないといけないです。ということで あんまり使えないですが長くないので書いておきます。
前項で使用したブックに新しく標準モジュールを挿入して以下のように記述してください。

Option Explicit

'Public Enum POWER_STATUS
'   POWEROFF = 1
'   LOGOFF = 2
'   REBOOT = 3
'   STANDBY = 4
'   HIBER = 5
'End Enum


'WMIによる電源管理(シャットダウン, ログオフ, 再起動のみ)
'(スタンバイと休止は不可)
'引数 status: POWER_STATUS列挙体の POWEROFF, LOGOFF, REBOOTのいずれか
'戻値         成功時 1, 失敗時 0
'参照設定     Microsoft WMI Scripting V1.x Library
Public Function GFnControlPowerByWMI(status As POWER_STATUS) As Long
    Dim vlRet       As Long
    Dim voLocator   As WbemScripting.SWbemLocator
    Dim voServices  As WbemScripting.SWbemServices  '名前空間
    Dim voObjSet    As WbemScripting.SWbemObjectSet 'インスタンス群
    Dim voObj       As WbemScripting.SWbemObject    'インスタンス
    Dim voInst      As WbemScripting.SWbemObject    '同上
    Dim voInParam   As WbemScripting.SWbemObject    'メソッド入力パラメータ
    Dim voOutParam  As WbemScripting.SWbemObject    'メソッド出力パラメータ

    On Error GoTo ErRoutine

    Set voLocator = CreateObject("WbemScripting.SWbemLocator")

    '名前空間に接続
    Set voServices = voLocator.ConnectServer(strServer:=".", _
                                             strNamespace:="root\CIMV2")
    'シャットダウン特権の追加
    voServices.Security_.Privileges.Add wbemPrivilegeShutdown, True

    'WIN32_OperatingSystemクラスのインスタンス群を取得
    Set voObjSet = voServices.ExecQuery("SELECT * FROM Win32_OperatingSystem")

    If voObjSet.Count < 1 Then
        Set voLocator = Nothing: Exit Function
    End If

    'インスタンス取得
    For Each voObj In voObjSet
        Set voInst = voObj: Exit For
    Next voObj

    'Win32Shutdownメソッド用入力パラメータ作成
    Set voInParam = voInst.Methods_("Win32Shutdown").InParameters.SpawnInstance_()
    voInParam.Properties_.Item("Flags").Value = Choose(status, 8, 0, 2) '(A)

    'Win32Shutdownメソッド実行
    Set voOutParam = voInst.ExecMethod_("Win32Shutdown", voInParam)

    '出力パラメータvoOutParamの評価
    GFnControlPowerByWMI = _
                        IIf(voOutParam.Properties_("ReturnValue").Value = 0, 1, 0)

    Set voLocator = Nothing
Exit Function

ErRoutine:
    GFnControlPowerByWMI = 0
    Set voLocator = Nothing
End Function

保存してGFnControlPowerByWMIを実行してください。

ロジックについてはこちら と同じなので省略します。
※注(A)

<3.WSH(Windows Script Host)を使用する・・・対象OS Windows XP

多分、VBAの場合はこれが一番楽そうですが、電源OFF等につかうコマンド shutdown.exeや休止(ハイバーネーション)時使うかもしれないpowercfg.exeWindows XP以降でしか有効ではないです。

上記二項と同じブックに新しい標準モジュールを追加して以下を記述してください。

Option Explicit

'Public Enum POWER_STATUS
'   POWEROFF = 1
'   LOGOFF = 2
'   REBOOT = 3
'   STANDBY = 4
'   HIBER = 5
'End Enum

'電源管理(引数に指定した状態に移行) - <WindowsXP以降のみ有効>
'引数 argeStatus : 列挙体 POWEROFF, LOGOFF, REBOOT, STANDBY, HIBERのいずれか
'戻値              成功 1, 失敗 0
'参照設定 Windows Script Host Object Model(C:\Windows\system32\wshom.ocx)
Public Function GFnControlPowerByWSH(argeStatus As POWER_STATUS) As Long
    Dim voWshShell    As IWshRuntimeLibrary.wshshell         'WSH Shellオブジェクト
    Dim voFso         As IWshRuntimeLibrary.FileSystemObject 'FileSystemObjectオブジェクト
    Dim vsSysFolder   As String                              'システムフォルダーパス
    Dim vsCmd         As String                              'コマンド文字列

    On Error GoTo ErRoutine

    'OSのバージョン確認
    If Val(Mid(Application.OperatingSystem, _
                  InStrRev(Application.OperatingSystem, " ", -1) + 1)) < 5.01 Then
        Exit Function
    End If

    Set voWshShell = CreateObject("WScript.Shell")
    Set voFso = CreateObject("Scripting.FileSystemObject")

    'システムフォルダパス取得
    vsSysFolder = voFso.GetSpecialFolder(SystemFolder).Path

    If argeStatus <= REBOOT Then 'Poweroff, Logoff, Reboot
        'コマンド文字列作成
        vsCmd = vsSysFolder & "\shutdown.exe -t 0 " & Choose(argeStatus, "-s", "-l", "-r")

    Else 'Suspend, Hibernation

        'ハイバーネーション有効/無効
        vsCmd = "powercfg.exe /H " & IIf((argeStatus = STANDBY), "OFF", "ON")
        voWshShell.Run vsCmd, 0

        'コマンド文字列作成
        vsCmd = vsSysFolder & "\Rundll32.exe Powrprof.dll, SetSuspendState"

    End If

    'コマンド実行
    voWshShell.Run vsCmd, 0

    Set voWshShell = Nothing
    Set voFso = Nothing

    GFnControlPowerByWSH = 1
    Exit Function

ErRoutine:
    Set voWshShell = Nothing
    Set voFso = Nothing

    GFnControlPowerByWSH = 0
End Function

Windows XP以降であれば、電源OFF、再起動、ログオフはshutdown.exeという ユーティリティがあるのでコンソールから呼び出せます。また、サスペンド、休止に関しては SetSuspenStateというAPIが、Powrprof.dllにあるのですが、Rundll32.exeというユーティリティを 使用すれば呼び出すことができます。これらのコマンドを実行してくれるのがシェルであり、 今回利用しようとしているWSH(Windows Script Host)です。

WSHのWshShellオブジェクトのRunメソッドを使用するとコンソールからコマンドを 実行するのと同様にプログラムを実行できます。

  1. WshShellオブジェクトを作成する(CreateObject)
  2. shutdown.exeやRundll32.exeはシステムフォルダにあるのでFileSystemObjectのSpecialFolderメソッドを 使ってそのパスを取得する。
  3. WshShellのRunメソッドで実行するコマンド文字列を作成する。
    1. 電源OFF、再起動、ログオフの場合はshutdown.exeを使用します。さらに、shutdown.exeで 指定するスイッチでそれぞれを区分する(電源OFF: -s、ログオフ: -l、再起動: -r)
    2. サスペンド、休止の場合はRundll32.exeを通してSetSuspendStateを呼び出す。その前に、 ここで説明していますが、休止を サポートしている場合(ハイバーネーションファイルがある場合)は、無条件でSetSuspendStateは 休止状態になり、そうでない場合はサスペンドになってしまうので、休止のサポート/非サポートを 切り替えてくれるようにpowercfg.exeを使用します。これはAPIで書いたIsPwrHibernateAllowed、 CallNtPowerInformationと同じ機能を提供してくれます。休止の場合はpowercfg.exeに /H ONスイッチを付けて実行すると休止がサポートされます。
  4. WshShellオブジェクトのRunメソッドで3.で作成されたコマンド文字列を実行する。

上記のような流れになります。