Waiting for Out-GridView Windows to Close

This came up on StackOverflow and thought it was worth reposting here.  It shows how fancy you can get with low-level Win32 API calls using the new Add-Type cmdlet.  The following code essentially checks for the existence of *any* window associated with the current process other than the main window.  Any additional window like that created for Out-GridView will cause the script to wait until the last of these extra windows are closed.  Note that Out-GridView windows are created as top level windows and their titles vary depending on the command text used to invoke the Out-GridView cmdlet.

   1: $src = @' 
   2: using System; 
   3: using System.Diagnostics; 
   4: using System.Runtime.InteropServices; 
   5: using System.Threading; 
   6:  
   7: namespace Utils 
   8: { 
   9:     public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam); 
  10:  
  11:     public class WindowHelper  
  12:     { 
  13:         private const int PROCESS_QUERY_LIMITED_INFORMATION = 0x1000; 
  14:         private IntPtr _mainHwnd; 
  15:         private IntPtr _ogvHwnd; 
  16:         private IntPtr _poshProcessHandle; 
  17:         private int _poshPid; 
  18:         private bool _ogvWindowFound; 
  19:  
  20:         public WindowHelper() 
  21:         { 
  22:             Process process = Process.GetCurrentProcess(); 
  23:             _mainHwnd = process.MainWindowHandle; 
  24:             _poshProcessHandle = process.Handle; 
  25:             _poshPid = process.Id; 
  26:         } 
  27:  
  28:         public void WaitForOutGridViewWindowToClose() 
  29:         { 
  30:             do  
  31:             { 
  32:                 _ogvWindowFound = false; 
  33:                 EnumChildWindows(IntPtr.Zero, EnumChildWindowsHandler, 
  34:                                  IntPtr.Zero); 
  35:                 Thread.Sleep(500); 
  36:             } while (_ogvWindowFound); 
  37:         } 
  38:  
  39:         [DllImport("User32.dll")] 
  40:         [return: MarshalAs(UnmanagedType.Bool)] 
  41:         public static extern bool EnumChildWindows( 
  42:             IntPtr parentHandle, Win32Callback callback, IntPtr lParam); 
  43:  
  44:         [DllImport("Oleacc.dll")] 
  45:         public static extern IntPtr GetProcessHandleFromHwnd(IntPtr hwnd); 
  46:  
  47:         [DllImport("Kernel32.dll")] 
  48:         public static extern int GetProcessId(IntPtr handle); 
  49:  
  50:         [DllImport("Kernel32.dll")] 
  51:         [return: MarshalAs(UnmanagedType.Bool)] 
  52:         public static extern bool DuplicateHandle( 
  53:             IntPtr hSourceProcessHandle,  
  54:             IntPtr hSourceHandle,  
  55:             IntPtr hTargetProcessHandle, 
  56:             out IntPtr lpTargetHandle, 
  57:             int dwDesiredAccess, 
  58:             bool bInheritHandle, 
  59:             int dwOptions); 
  60:  
  61:         [DllImport("Kernel32.dll")] 
  62:         [return: MarshalAs(UnmanagedType.Bool)] 
  63:         public static extern bool CloseHandle(IntPtr handle); 
  64:  
  65:         [DllImport("Kernel32.dll")] 
  66:         public static extern int GetLastError(); 
  67:  
  68:         private bool EnumChildWindowsHandler(IntPtr hwnd, IntPtr lParam) 
  69:         { 
  70:             if (_ogvHwnd == IntPtr.Zero) 
  71:             { 
  72:                 IntPtr hProcess = GetProcessHandleFromHwnd(hwnd); 
  73:                 IntPtr hProcessDup; 
  74:                 if (!DuplicateHandle(hProcess, hProcess, _poshProcessHandle, 
  75:                                      out hProcessDup,  
  76:                                      PROCESS_QUERY_LIMITED_INFORMATION, 
  77:                                      false, 0)) 
  78:                 { 
  79:                     Console.WriteLine("Dup process handle {0:X8} error: {1}", 
  80:                                       hProcess.ToInt32(), GetLastError()); 
  81:                     return true; 
  82:                 } 
  83:                 int processId = GetProcessId(hProcessDup); 
  84:                 if (processId == 0) 
  85:                 { 
  86:                     Console.WriteLine("GetProcessId error:{0}", 
  87:                                       GetLastError()); 
  88:                     return true; 
  89:                 } 
  90:                 if (processId == _poshPid) 
  91:                 { 
  92:                     if (hwnd != _mainHwnd) 
  93:                     { 
  94:                         _ogvHwnd = hwnd; 
  95:                         _ogvWindowFound = true; 
  96:                         CloseHandle(hProcessDup); 
  97:                         return false; 
  98:                     } 
  99:                 } 
 100:                 CloseHandle(hProcessDup); 
 101:             } 
 102:             else if (hwnd == _ogvHwnd) 
 103:             { 
 104:                 _ogvWindowFound = true; 
 105:                 return false; 
 106:             } 
 107:             return true; 
 108:         } 
 109:     } 
 110: } 
 111: '@ 
 112:  
 113: Add-Type -TypeDefinition $src 
 114:  
 115: Get-Process | Out-GridView 
 116:  
 117: $helper = new-object Utils.WindowHelper 
 118: $helper.WaitForOutGridViewWindowToClose() 
 119:  
 120: "Done!!!!" 

 

Note the use of DuplicateHandle(). This was required because initially we don’t have permissions to ask for the process ID of the process handle returned from GetProcessHandleFromHwnd.  During the handle duplication we can ask for the permission we need to get the process id.

Advertisements
This entry was posted in PowerShell 2.0. Bookmark the permalink.

One Response to Waiting for Out-GridView Windows to Close

  1. Tim says:

    I tried the suggested solution here but it didn’t work and I kept getting errors reported by the GetProcessID() and the DuplicateHandle() processes. Output looked like this:

    ...
    Dup process handle 00000134 error: 6
    GetProcessId error:6
    GetProcessId error:6
    Dup process handle 00000120 error: 6
    GetProcessId error:6
    GetProcessId error:6
    ...

    I don’t know enough about the API’s to properly diagnose the problem, but the errors make me this it’s permission-related. However, I did find there’s a similar function call GetWindowThreadProcessId() that makes for a simpler solution and doesn’t produce an error.

    Modifying the solution provided here, this is what I came up with:

    $src = @'
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Threading;
    
    namespace Utils
    {
        public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);
    
        public class WindowHelper
        {
            private const int PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
            private IntPtr _mainHwnd;
            private IntPtr _ogvHwnd;
            private IntPtr _poshProcessHandle;
            private int _poshPid;
            private bool _ogvWindowFound;
    
            public WindowHelper()
            {
                Process process = Process.GetCurrentProcess();
                _mainHwnd = process.MainWindowHandle;
                _poshProcessHandle = process.Handle;
                _poshPid = process.Id;
            }
    
            public void WaitForOutGridViewWindowToClose()
            {
                do
                {
                    _ogvWindowFound = false;
                    EnumChildWindows(IntPtr.Zero, EnumChildWindowsHandler,
                                     IntPtr.Zero);
                    Thread.Sleep(500);
                } while (_ogvWindowFound);
            }
    
            [DllImport("User32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool EnumChildWindows(
                IntPtr parentHandle, Win32Callback callback, IntPtr lParam);
    
            [DllImport("User32.dll")]
            public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int processId);
    
            [DllImport("Kernel32.dll")]
            public static extern int GetLastError();
    
            private bool EnumChildWindowsHandler(IntPtr hwnd, IntPtr lParam)
            {
                if (_ogvHwnd == IntPtr.Zero)
                {
                    int processId;
                    int thread = GetWindowThreadProcessId(hwnd, out processId);
    
                    if (processId == 0)
                    {
                        Console.WriteLine("GetWindowThreadProcessId error:{0}",
                                          GetLastError());
                        return true;
                    }
                    if (processId == _poshPid)
                    {
                        if (hwnd != _mainHwnd)
                        {
                            _ogvHwnd = hwnd;
                            _ogvWindowFound = true;
                            return false;
                        }
                    }
                }
                else if (hwnd == _ogvHwnd)
                {
                    _ogvWindowFound = true;
                    return false;
                }
                return true;
            }
        }
    }
    '@
    
    Add-Type -TypeDefinition $src
    Get-Process | Out-GridView               
    $helper = new-object Utils.WindowHelper  
    $helper.WaitForOutGridViewWindowToClose()
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s