読者です 読者をやめる 読者になる 読者になる

tmori3y2のブログ

主にWindowsのプログラムなど

DISM実行中にスリープやWindows Updateによる再起動をしないようにLinqPadでプロセス監視

tmori3y2.hatenablog.com

で書いたように、LinqPadでAPIを呼び出すと、LinqPadを終了するまで、電源要求を継続することは示せた。

しかし、元々はDismコマンドを実行中だけ電源要求したかったので、目的は達成できていない。

f:id:tmori3y2:20170219110647p:plain

LinqPadのサンプルにWindowを表示して非同期で処理をするサンプルがあったので、System.Diagnostics.Processクラスで実行ファイルリストを取得してDismが起動していたら、起動中に電源要求を行うスクリプトを書いてみた。 (Free版で実行できるため、開発環境のインストールは不要です)

<Query Kind="Program">
  <Reference>&lt;RuntimeDirectory&gt;\WPF\PresentationCore.dll</Reference>
  <Reference>&lt;RuntimeDirectory&gt;\WPF\PresentationFramework.dll</Reference>
  <Reference>&lt;RuntimeDirectory&gt;\WPF\PresentationUI.dll</Reference>
  <Reference>&lt;RuntimeDirectory&gt;\System.Threading.Tasks.dll</Reference>
  <Reference>&lt;RuntimeDirectory&gt;\System.Xaml.dll</Reference>
  <Reference>&lt;RuntimeDirectory&gt;\WPF\WindowsBase.dll</Reference>
  <Namespace>System.ComponentModel</Namespace>
  <Namespace>System.Diagnostics</Namespace>
  <Namespace>System.Runtime.InteropServices</Namespace>
  <Namespace>System.Threading.Tasks</Namespace>
  <Namespace>System.Windows</Namespace>
  <Namespace>System.Windows.Controls</Namespace>
</Query>

[Flags]
enum EXECUTION_STATE : uint
{
    ES_SYSTEM_REQUIRED = 0x00000001,
    ES_DISPLAY_REQUIRED = 0x00000002,
    ES_AWAYMODE_REQUIRED = 0x00000040,
    ES_CONTINUOUS = 0x80000000,
    ES_DONE = ES_CONTINUOUS,
    ES_KEEP_ALIVE = ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED | ES_AWAYMODE_REQUIRED | ES_CONTINUOUS,
}

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern uint SetThreadExecutionState(EXECUTION_STATE esFlags);

public void Main()
{
    new MonitoringWindow().Show();
}

public class MonitoringWindow : Window
{
    Button startButton = new Button { Content = "Start", Margin = new Thickness(5) };
    Button cancelButton = new Button { Content = "Cancel", Margin = new Thickness(5), IsEnabled = false };
    TextBlock resultText = new TextBlock { Margin = new Thickness(5) };
    StackPanel stackPanel = new StackPanel();
    CancellationTokenSource cancelSource = null;

    public MonitoringWindow()
    {
        InitializeComponent();
    }

    async void startButton_Click(object sender, EventArgs args)
    {
        startButton.IsEnabled = false;
        cancelButton.IsEnabled = true;
        SetThreadExecutionState(EXECUTION_STATE.ES_DONE);
        resultText.Text = "Waiting...";
        cancelSource = new CancellationTokenSource();
        var token = cancelSource.Token;

        try
        {
            while (true)
            {
                bool foundDism = false;
                var processes = Process.GetProcesses();
                foreach (var process in processes)
                {
                    if (process.ProcessName.ToLower() == "dism")
                    {
                        foundDism = true;
                        SetThreadExecutionState(EXECUTION_STATE.ES_KEEP_ALIVE);
                        resultText.Text = "Dism found...\n";
                        resultText.Text += $"Id\t\t: {process.Id}\n";
                        resultText.Text += $"StartTime\t: {process.StartTime}\n";
                        resultText.Text += $"Now\t\t: {DateTime.Now}\n";
                        break;
                    }
                    token.ThrowIfCancellationRequested();
                }
                if (!foundDism)
                {
                    resultText.Text = "Finished.";
                    break;
                }
                await Task.Delay(5000, token);
            }
        }
        catch (OperationCanceledException)
        {
            resultText.Text = "Monintoring Canceled";
        }
        catch (Exception ex)
        {
            resultText.Text = "Error: " + ex.Message;
        }
        finally
        {
            startButton.IsEnabled = true;
            cancelButton.IsEnabled = false;
            SetThreadExecutionState(EXECUTION_STATE.ES_DONE);
            cancelSource = null;
        }
    }

    void InitializeComponent()
    {
        Title = "Dism keeper";
        startButton.Click += startButton_Click;
        cancelButton.Click += (sender, e) => cancelSource.Cancel();
        Width = Height = 400;
        Padding = new Thickness(15);
        stackPanel.Children.Add(startButton);
        stackPanel.Children.Add(cancelButton);
        stackPanel.Children.Add(resultText);
        Content = stackPanel;
    }
}

変更したのは、

  • Task.Delay()にCancellationTokenSource.Tokenを渡して、待機中にCancellationTokenSource.Cancel()が呼ばれたら例外が発生して中断するようにする
  • プロセスリストの処理中にCancellationTokenSource.Cancel()が呼ばれたら例外が発生させるように、CancellationTokenSource.Token.ThrowIfCancellationRequested()を呼び出す

使い方は簡単で、

  • Dismコマンドを実行する
  • LinqPadを管理者モードで起動して、Startボタンを押すとDismコマンドの監視を開始
    • Dismコマンドが起動していなかったらすぐに監視を終了する
    • Cancelボタンを押したら監視を終了する

f:id:tmori3y2:20170219112113p:plain

D:\1607>Dism /Export-Image /SourceImageFile:E:\sources\install.esd /SourceIndex:2 /DestinationImageFile:install2.wim /Compress:max /CheckIntegrity

展開イメージのサービスと管理ツール
バージョン: 10.0.14393.0

イメージをエクスポートしています
[===========================64.0%=====                     ]

電源要求の確認は、管理者モードのコマンドプロンプトでpowercfg /requestsを実行すると行える。

C:\WINDOWS\system32>powercfg /requests
DISPLAY:
[PROCESS] \Device\HarddiskVolume3\Users\xxxx\AppData\Local\LINQPad\ProcessServer5X86B\LINQPad.UserQuery.exe

SYSTEM:
[DRIVER] \FileSystem\srvnet
アクティブなリモート クライアントが、このコンピューターに最近要求を送信しました。
[PROCESS] \Device\HarddiskVolume3\Users\xxxx\AppData\Local\LINQPad\ProcessServer5X86B\LINQPad.UserQuery.exe

AWAYMODE:
[PROCESS] \Device\HarddiskVolume3\Users\xxxx\AppData\Local\LINQPad\ProcessServer5X86B\LINQPad.UserQuery.exe

実行:
なし。

PERFBOOST:
なし。

ACTIVELOCKSCREEN:
なし。

Dismコマンドの実行が終了したら監視も終了する。

D:\1607>Dism /Export-Image /SourceImageFile:E:\sources\install.esd /SourceIndex:2 /DestinationImageFile:install2.wim /Compress:max /CheckIntegrity

展開イメージのサービスと管理ツール
バージョン: 10.0.14393.0

イメージをエクスポートしています
[==========================100.0%==========================]
操作は正常に完了しました。

f:id:tmori3y2:20170219112618p:plain

C:\WINDOWS\system32>powercfg /requests
DISPLAY:
なし。

SYSTEM:
[DRIVER] \FileSystem\srvnet
アクティブなリモート クライアントが、このコンピューターに最近要求を送信しました。

AWAYMODE:
なし。

実行:
なし。

PERFBOOST:
なし。

ACTIVELOCKSCREEN:
なし。

ほんとは、TextBoxで監視対象のプロセス名を変更したかったのだが、IMEで変換した文字じゃないとなぜか入力できなかったので、dism固定にしてしまった。