トップ回答者
PowerShellでスリープ抑止(Win32APIの利用)

質問
-
PowerShellスクリプトで、アプリを実行させその実行中はスリープをさせないようにしたいと思っています。
[int] $SystemRequired = 1
Web検索で Win32APIを使ってスリープをコントロールする例が載っていたので試しています。
しかし、エラーが出て実行できません。
例えば ISEから以下のコードを実行すると
***********************************
[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)
回答
-
以下のような感じになるかと思います。
$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
すべての返信
-
以下のような感じになるかと思います。
$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
-
早速ありがとうございました。
詳細な説明をつけていただいたので、曖昧な理解が明快になりました。
但し、スリープ抑止を確認するために元コードの最後にあるスリープ移行命令を、スリープ抑止とスリープ許可の間に実行すると
[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?"
⑦が ④をコピペした行です。
⑧は、実際の動作でスリープ移行命令やスタートボタンからのスリープも抑止するために付加し、⑤の行の論理和に加えています。