none
PowerShellでスリープ抑止(Win32APIの利用) RRS feed

  • 質問

  • PowerShellスクリプトで、アプリを実行させその実行中はスリープをさせないようにしたいと思っています。
    Web検索で Win32APIを使ってスリープをコントロールする例が載っていたので試しています。
    しかし、エラーが出て実行できません。
    例えば ISEから以下のコードを実行すると
    ***********************************

    [int] $SystemRequired = 1
    [int] $Continuous = 0x80000000
    Write-Host "Sleep 抑止"
    $signature = @"
    [DllImport("kernel32.dll")]
    extern static ExecutionState SetThreadExecutionState(ExecutionState esFlags);
    "@
    $func = Add-Type -memberDefinition $signature -namespace "Win32Functions" -name "SetThreadExecutionState" -passThru
    $func::SetThreadExecutionState($SystemRequired -bor $Continuous);
    Read-Host -Prompt "Can you sleep?"

    Write-Host "Sleep 許可"
    $signature = @"
    [DllImport("kernel32.dll")]
    extern static ExecutionState SetThreadExecutionState(ExecutionState esFlags);
    "@
    $func = Add-Type -memberDefinition $signature -namespace "Win32Functions" -name "SetThreadExecutionState" -passThru
    $func::SetThreadExecutionState($Continuous);
    Read-Host -Prompt "Can you sleep?"

    Write-Host "Execute Seep !"
    Read-Host -Prompt "Good-bye"
    $signature = @"
    [DllImport("powrprof.dll")]
    public static extern bool SetSuspendState(bool Hibernate,bool ForceCritical,bool DisableWakeEvent);
    "@
    $func = Add-Type -memberDefinition $signature -namespace "Win32Functions" -name "SetSuspendStateFunction" -passThru
    $func::SetSuspendState($false,$false,$false)

    ***********************************

    最後のスリープの実行だけならOKですが、スリープの抑止と許可の部分で
            型または名前空間名 'ExecutionState' が見つかりませんでした。using
            ディレクティブまたはアセンブリ参照が不足しています。
    というようなエラーが返されてしまいます。
    具体的にどこをどのようにすればスリープを抑止できるのか分かる方教えていただけるとありがたいです。
     (Win10 x64)

    2016年12月2日 14:14

回答

  • 以下のような感じになるかと思います。

    $SystemRequired = [uint32]1 #①
    $Continuous = [uint32]"0x80000000" #②
    $signature = @"
    [DllImport("kernel32.dll")]
    public extern static uint SetThreadExecutionState(uint esFlags);
    "@ #③
    $func = Add-Type -memberDefinition $signature -namespace "Win32Functions" -name "SetThreadExecutionStateFunction" -passThru #④
    
    Write-Host "Sleep 抑止"
    $func::SetThreadExecutionState($SystemRequired -bor $Continuous); #⑤
    Read-Host -Prompt "Can you sleep?"
    
    Write-Host "Sleep 許可"
    $func::SetThreadExecutionState($Continuous); #⑥
    Read-Host -Prompt "Can you sleep?"

    以下、解説です。

    ①SetThredExecutionState関数はintではなくuint(符号なし整数値)を取るので、変数もuint型にしておきます。PowerShellには[uint]という型エイリアスの定義はないので、.NET Frameworkでの正式名System.UInt32を用います。(System.は省略可)

    ②0x80000000と表記するとint型の-2147483648と等価になるのですが、符号が付いているのでこのint→uintキャストは失敗します。C#だとuncheckedキーワードを用いて回避したりするのですが、PowerShellでは同等の構文が無いと思いますので、文字列を経由して変換します。

    ③外部からアクセスする必要があるのでpublicキーワードを付けます。
    今回のエラーの直接の原因となっている、ExecutionStateは、uintのenumとして別途定義が必要になりますが、その場合-MemberDefinitionではなく、-TypeDefinitionを用いてenumの定義と関数の定義の両方を含むフルのC#コードを記述する必要がでてきます。今回はuintの値をそのまま使えば回避できるので、引数と戻り値の型を、ExecutionStateではなく、uintに変更します。

    ④Add-Type -Nameで指定したものがクラス名となりますが、クラス内で同名のメソッドが定義されているとコンパイルエラーになりますので、別名にします。(ここではSetThreadExecutionStateFunctionというクラス名にした)

    ⑤⑥スリープ抑止する場合と、許可する場合では、同じ関数を呼び出すので、それぞれを関数定義する必要はなく、単に引数を変えればOKです。

    • 回答としてマーク pai314 2016年12月3日 7:17
    2016年12月2日 21:57
    モデレータ

すべての返信

  • 以下のような感じになるかと思います。

    $SystemRequired = [uint32]1 #①
    $Continuous = [uint32]"0x80000000" #②
    $signature = @"
    [DllImport("kernel32.dll")]
    public extern static uint SetThreadExecutionState(uint esFlags);
    "@ #③
    $func = Add-Type -memberDefinition $signature -namespace "Win32Functions" -name "SetThreadExecutionStateFunction" -passThru #④
    
    Write-Host "Sleep 抑止"
    $func::SetThreadExecutionState($SystemRequired -bor $Continuous); #⑤
    Read-Host -Prompt "Can you sleep?"
    
    Write-Host "Sleep 許可"
    $func::SetThreadExecutionState($Continuous); #⑥
    Read-Host -Prompt "Can you sleep?"

    以下、解説です。

    ①SetThredExecutionState関数はintではなくuint(符号なし整数値)を取るので、変数もuint型にしておきます。PowerShellには[uint]という型エイリアスの定義はないので、.NET Frameworkでの正式名System.UInt32を用います。(System.は省略可)

    ②0x80000000と表記するとint型の-2147483648と等価になるのですが、符号が付いているのでこのint→uintキャストは失敗します。C#だとuncheckedキーワードを用いて回避したりするのですが、PowerShellでは同等の構文が無いと思いますので、文字列を経由して変換します。

    ③外部からアクセスする必要があるのでpublicキーワードを付けます。
    今回のエラーの直接の原因となっている、ExecutionStateは、uintのenumとして別途定義が必要になりますが、その場合-MemberDefinitionではなく、-TypeDefinitionを用いてenumの定義と関数の定義の両方を含むフルのC#コードを記述する必要がでてきます。今回はuintの値をそのまま使えば回避できるので、引数と戻り値の型を、ExecutionStateではなく、uintに変更します。

    ④Add-Type -Nameで指定したものがクラス名となりますが、クラス内で同名のメソッドが定義されているとコンパイルエラーになりますので、別名にします。(ここではSetThreadExecutionStateFunctionというクラス名にした)

    ⑤⑥スリープ抑止する場合と、許可する場合では、同じ関数を呼び出すので、それぞれを関数定義する必要はなく、単に引数を変えればOKです。

    • 回答としてマーク pai314 2016年12月3日 7:17
    2016年12月2日 21:57
    モデレータ
  • 早速ありがとうございました。
    詳細な説明をつけていただいたので、曖昧な理解が明快になりました。

    但し、スリープ抑止を確認するために元コードの最後にあるスリープ移行命令を、スリープ抑止とスリープ許可の間に実行すると

    [Win32Functions.SetSuspendStateFunction] に 'SetThreadExecutionState' という名前のメソッドが含まれないため、メソッドの呼び出しに失敗しました。

    というエラーが出てしまいました。 
    私の環境ですと ④の行がスリープ移行命令(以下のコート中の⑨) と ⑥ の間にないとエラーになるようです。

    以下、動作を確認した最終コードをペーストしておきます。

    $SystemRequired = [uint32]1 #①
    $Continuous = [uint32]"0x80000000" #②
    $AwayMode = [uint32]"0x00000040" #⑧
    $signature = @"
    [DllImport("kernel32.dll")]
    public extern static uint SetThreadExecutionState(uint esFlags);
    "@ #③
    $func = Add-Type -memberDefinition $signature -namespace "Win32Functions" -name "SetThreadExecutionStateFunction" -passThru #④
    
    Write-Host "Sleep 抑止"
    $func::SetThreadExecutionState($SystemRequired -bor $Continuous -bor $AwayMode); #⑤
    Read-Host -Prompt "Can you sleep?"
    
    Write-Host "Execute Seep !"
    Read-Host -Prompt "Good-bye"
    $signature1 = @"
    [DllImport("powrprof.dll")]
    public static extern bool SetSuspendState(bool Hibernate,bool ForceCritical,bool DisableWakeEvent);
    "@
    $func = Add-Type -memberDefinition $signature1 -namespace "Win32Functions" -name "SetSuspendStateFunction" -passThru #⑨
    $func::SetSuspendState($false,$false,$false)
    
    $func = Add-Type -memberDefinition $signature -namespace "Win32Functions" -name "SetThreadExecutionStateFunction" -passThru #⑦
    Write-Host "Sleep 許可"
    $func::SetThreadExecutionState($Continuous); #⑥
    Read-Host -Prompt "Can you sleep?"

    ⑦が ④をコピペした行です。
    ⑧は、実際の動作でスリープ移行命令やスタートボタンからのスリープも抑止するために付加し、⑤の行の論理和に加えています。

    2016年12月3日 6:58
  • そのエラーが出るのは、$func変数を使いまわしているのが原因であるように思います。

    SetThreadExecutionStateをよぶクラスと、SetSuspendStateをよぶクラスをそれぞれ別変数に代入すれば大丈夫かと思います。

    2016年12月3日 8:37
    モデレータ