C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

[PowerShell]ウィンドウを持たないアプリの正常終了

[トピック内 8 記事 (1 - 8 表示)]  << 0 >>

■86294 / inTopicNo.1)  [PowerShell]ウィンドウを持たないアプリの正常終了
  
□投稿者/ tacos (1回)-(2018/01/14(Sun) 18:56:58)

分類:[.NET 全般] 

PowerShellにてPCの利便性を高めるため簡易的なスクリプトを作成しています。

メインウィンドウを持たないアプリを正常終了させる方法があれば教えてください。
以下自分なりに試したところ、意図した挙動が得られなかったものです。


$ps = Get-Process | Where-Object {$_.Name -eq "(対象アプリ名)"}
→$psには上記の値が入っているものとして説明します。
 対象アプリはメインウィンドウを持たず、バックグラウンドで起動しているものとします。


Stop-Process -id $ps.Id
→強制終了してしまいました。

$ps.CloseMainWindow()
→メインウィンドウを持たないため、アプリが終了しませんでした。

taskkill.exe /pid $ps.Id
→$ps.CloseMainWindow()とほぼ同じ挙動でしょうか?
 強制終了シグナル送信成功のメッセージは返されましたが、アプリは終了しませんでした。
 /f オプションは強制終了してしまうので、意図した動作ではありません。


というところでつまずいております。
対象アプリはバックグランドで常駐するもので、タスクマネージャーにはプロセスとして表示されますが
タスクトレイには見えません。Windowsログオフ時に終了処理を行うアプリなのですが、
目的としてはその際の処理をWindowsをログオフせずに再現したいというものになります。
あるいはそのアプリのみに、Windowsの終了処理が始まった事を通知するという
トリッキーな方法があればそれで済むのですが。

ご教示よろしくお願い致します。


Windows 7 Professionnal
PowerShellバージョン
 CLRVersion 2.0.50727.5485
 BuildVersion 6.1.7601.17514
 PSVersion 2.0
引用返信 編集キー/
■86296 / inTopicNo.2)  Re[1]: [PowerShell]ウィンドウを持たないアプリの正常終了
□投稿者/ Hongliang (596回)-(2018/01/15(Mon) 10:33:42)
対象のアプリがどういう手法でログオフを検出しているか、などに依存する話なので、一般論で語ることはできないでしょう。
// PowerShellの既存のコマンドレットでお手軽に実装ってわけにはいかなさそうというのだけは察せますが。
引用返信 編集キー/
■86306 / inTopicNo.3)  Re[2]: [PowerShell]ウィンドウを持たないアプリの正常終了
□投稿者/ tacos (2回)-(2018/01/16(Tue) 06:07:55)
No86296 (Hongliang さん) に返信

ご連絡ありがとうございます。
ある程度の期間に渡って自分なりに調べておりましたが、
方策が見つからず悩んでおりました。
やはりちょっと特殊な処理を要求していたようですね。
違う角度から何か出来ないか検討してみます。
引用返信 編集キー/
■86308 / inTopicNo.4)  Re[1]: [PowerShell]ウィンドウを持たないアプリの正常終了
□投稿者/ 魔界の仮面弁士 (1534回)-(2018/01/16(Tue) 10:53:09)
No86294 (tacos さん) に返信
> メインウィンドウを持たないアプリを正常終了させる方法があれば教えてください。
> Windowsログオフ時に終了処理を行うアプリなのですが、

該当アプリが、ログオフに反応して処理が行われるつくりになっているのなら、
WM_QUERYENDSESSION あるいは WM_ENDSESSION を受信するための
不可視ウィンドウが存在しているのではないでしょうか。
https://msdn.microsoft.com/en-us/library/windows/desktop/aa376889.aspx



$TypeDef = @"
using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Win32
{
 public class WinInfo
 {
  public int ProcessId { get; set; }
  public IntPtr Handle { get; set; }
  public bool Visible { get; set; }
  public string Title { get; set; }
 }
 public class Wankuma86294
 {
  public static List<WinInfo> Execute()
  {
   var proc = new Wankuma86294();
   EnumWindows(proc.EnumFunc, IntPtr.Zero);
   return proc.WinList;
  }
  private Wankuma86294() { }
  private delegate bool WNDENUMPROC(IntPtr hwnd, IntPtr lParam);
  private List<WinInfo> WinList = new List<WinInfo>();
  [DllImport("User32.dll")][return: MarshalAs(UnmanagedType.Bool)]
  private static extern bool EnumWindows(WNDENUMPROC lpEnumFunc, IntPtr lParam);
  [DllImport("User32.dll")][return: MarshalAs(UnmanagedType.Bool)]
  private static extern bool IsWindowVisible(IntPtr hWnd);
  [DllImport("User32.dll")]
  private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
  [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
  private bool EnumFunc(IntPtr hWnd, IntPtr lParam)
  {
   var sb = new StringBuilder(512);
   int len = GetWindowText((IntPtr)hWnd, sb, 512);
   int id = 0;
   if (hWnd != IntPtr.Zero) { GetWindowThreadProcessId(hWnd, out id); }
   WinList.Add(new WinInfo() { Handle = hWnd, Title = sb.ToString(0, len), ProcessId = id, Visible = IsWindowVisible(hWnd) });
   return true;
  }
 }
}
"@
Add-Type -TypeDefinition $TypeDef -Language CSharpVersion3
$TopLevelWindows = [Win32.Wankuma86294]::Execute() | Sort-Object -Property ProcessId, Handle
$TopLevelWindows | Select-Object ProcessId, @{Name="HWND"; Expression={"{0} : 0x{1:X16}" -f $(if($_.Visible){'Show'}else{'Hide'}),($_.Handle -as [long])}}, Title
引用返信 編集キー/
■86309 / inTopicNo.5)  Re[2]: [PowerShell]ウィンドウを持たないアプリの正常終了
□投稿者/ 魔界の仮面弁士 (1535回)-(2018/01/16(Tue) 11:43:14)
No86308 (魔界の仮面弁士) に追記
> 該当アプリが、ログオフに反応して処理が行われるつくりになっているのなら、
> WM_QUERYENDSESSION あるいは WM_ENDSESSION を受信するための
> 不可視ウィンドウが存在しているのではないでしょうか。

> Add-Type -TypeDefinition $TypeDef -Language CSharpVersion3
> $TopLevelWindows = [Win32.Wankuma86294]::Execute() | Sort-Object -Property ProcessId, Handle
> $TopLevelWindows | Select-Object ProcessId, @{Name="HWND"; Expression={"{0} : 0x{1:X16}" -f $(if($_.Visible){'Show'}else{'Hide'}),($_.Handle -as [long])}}, Title


もし、該当する ProcessId のウィンドウが存在しているようであれば、
それら全てに対して、WM_CLOSE を SendMessageTimeout / PostMessage してみるとか。
https://support.microsoft.com/en-us/help/178893/
引用返信 編集キー/
■86310 / inTopicNo.6)  Re[3]: [PowerShell]ウィンドウを持たないアプリの正常終了
□投稿者/ tacos (3回)-(2018/01/16(Tue) 14:53:50)
No86309 (魔界の仮面弁士 さん) に返信

ご返信ありがとうございます。


>>該当アプリが、ログオフに反応して処理が行われるつくりになっているのなら、
>>WM_QUERYENDSESSION あるいは WM_ENDSESSION を受信するための
>>不可視ウィンドウが存在しているのではないでしょうか。

っ!そんな気がしてきました!
該当アプリを逆コンパイルしてみて分からないなりに眺めてみたところ、
確かにウィンドウらしきモノが確認出来たので、
逆コンパイルが上手く行っていないだけなのか疑問でした。

コードまでありがとうございます。とても期待出来そうです。
当方知識が貧弱なため解決まで時間が掛かりますが、じっくり検証させて頂きます。
引用返信 編集キー/
■86311 / inTopicNo.7)  Re[4]: [PowerShell]ウィンドウを持たないアプリの正常終了
□投稿者/ 魔界の仮面弁士 (1536回)-(2018/01/16(Tue) 19:12:40)
No86310 (tacos さん) に返信
> 該当アプリを逆コンパイルしてみて分からないなりに眺めてみたところ、
> 確かにウィンドウらしきモノが確認出来たので、
> 逆コンパイルが上手く行っていないだけなのか疑問でした。

もしかして、対象のアプリは .NET Framework 向けのものでしょうか。


>> WM_QUERYENDSESSION あるいは WM_ENDSESSION を受信するための
>> 不可視ウィンドウが存在しているのではないでしょうか。

.NET のマネージアプリの場合、それらをカプセル化した
[Microsoft.Win32.SystemEvents] クラスの SessionEnding / SessionEnded イベントが
使われている可能性があります。

この場合、『.NET-BroadcastEventWindow.』で始まる名前のクラス名を持った
不可視ウィンドウが用意され、そこでメッセージが受信されるようになっています。
コンソールアプリの場合は、SetConsoleCtrlHandler API 経由ですけれども。

Windows のシャットダウン時やログオフ時に、稀に
 「プログラムの終了 - .NET-BroadcastEventWindow…」
と表示されることがありますが、それの正体がコレのようです。
https://support.microsoft.com/ja-jp/help/841073


ちなみに VB6 アプリの場合は "ThunderRT6Main" クラスというウィンドウが使われていました。
こちらは不可視ではなく、サイズ 0 の可視ウィンドウでした。
(開発環境で実行した場合は "ThunderMain" というクラス名です)


参考までに、先のサンプルでクラス名も拾うように改修してみました。
また、今回は対象アプリのプロセス ID も事前に分かっているようなので、
呼び出し時に検索対象のプロセスID を指定できるようにしています。

# 全部のプロセスを列挙する場合
[Win32.Wankuma86294]::Execute() | Format-Table

# 列挙対象の Process.Id を指定する場合
[Win32.Wankuma86294]::Execute($ps.Id) | Format-Table


$TypeDef = @"
using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Win32
{
 public class WinInfo
 {
  public int ProcessId { get; set; }
  public IntPtr Handle { get; set; }
  public bool Visible { get; set; }
  public string Class { get; set; }
  public string Title { get; set; }
 }
 public class Wankuma86294
 {
  public static List<WinInfo> Execute() { return Execute(0); }
  public static List<WinInfo> Execute(int targetProcessId)
  {
   var proc = new Wankuma86294() { TargetProcessId = targetProcessId };
   EnumWindows(proc.EnumFunc, IntPtr.Zero);
   return proc.WinList;
  }
  private Wankuma86294() { }
  private int TargetProcessId;
  private delegate bool WNDENUMPROC(IntPtr hwnd, IntPtr lParam);
  private List<WinInfo> WinList = new List<WinInfo>();
  [DllImport("User32.dll")][return: MarshalAs(UnmanagedType.Bool)]
  private static extern bool EnumWindows(WNDENUMPROC lpEnumFunc, IntPtr lParam);
  [DllImport("User32.dll")][return: MarshalAs(UnmanagedType.Bool)]
  private static extern bool IsWindowVisible(IntPtr hWnd);
  [DllImport("User32.dll")]
  private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
  [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
  [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
  private bool EnumFunc(IntPtr hWnd, IntPtr lParam)
  {
   int processId = 0;
   if (hWnd != IntPtr.Zero) { GetWindowThreadProcessId(hWnd, out processId); }
   if (TargetProcessId == 0 || processId == TargetProcessId)
   {
    var bufText = new StringBuilder(512);
    int lenText = GetWindowText((IntPtr)hWnd, bufText , 512);
    var bufClass = new StringBuilder(512);
    int lenClass = GetClassName((IntPtr)hWnd, bufClass, 512);
    WinList.Add(new WinInfo()
    {
     ProcessId = processId,
     Handle = hWnd,
     Visible = IsWindowVisible(hWnd),
     Class = bufClass.ToString(0, lenClass),
     Title = bufText.ToString(0, lenText),
    });
   }
   return true;
  }
 }
}
"@
Add-Type -TypeDefinition $TypeDef -Language CSharpVersion3
引用返信 編集キー/
■86312 / inTopicNo.8)  Re[5]: [PowerShell]ウィンドウを持たないアプリの正常終了
□投稿者/ tacos (4回)-(2018/01/17(Wed) 10:31:26)
No86311 (魔界の仮面弁士 さん) に返信

大変詳細にご説明頂き本当にありがとうございます。
ざっくりですが、動かしてみた結果をご報告いたします。

まず No86308 にて頂いたコードにて、予想された通り該当アプリに非表示のウィンドウが存在することを確認できました。


No86311
> もしかして、対象のアプリは .NET Framework 向けのものでしょうか。
> この場合、『.NET-BroadcastEventWindow.』で始まる名前のクラス名を持った
> 不可視ウィンドウが用意され、そこでメッセージが受信されるようになっています。

該当アプリはDelphiで書かれているようで、DeDeという逆コンパイラーで(恐らく)正常な逆コンパイルが出来ました。
もう少し詳細を先にお伝えしておけば良かったと恐縮な思いですが、改修頂いたコードのおかげで
該当アプリのメインウィンドウのハンドルおよびクラスを取得できました。
さらにそのウィンドウに対して WM_CLOSE を SendMessage することで、無事該当アプリが目的としていた動作(ログ生成)をしてくれました!


この度は迅速かつ的確なアドバイスを頂きありがとうございました。
正直こんなに早く解決できるとは思っていませんでした。

現状では SendMessage の実装が、ウェブであちこち検索してコピペしたツギハギの状態のままだったり
該当アプリが目的のログ生成まではするものの終了せずに残っている or 異常終了のメッセージが出るなど
まだまだ精査していく余地がありますが、PostMessage も試してみるなど引き続き試してみます。
もうここまで来ておりますので、この記事は一旦解決済みとさせて頂きます。
重ねて御礼申し上げます。
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ