Search code examples
excelpowershellwinapiuser32

Bring Window To Foreground When MainWindowHandle Is 0


The following code brings the window to the foreground, if the MainWindowHandle is not 0.

How can I bring a window to the front that has MainWindowHandle = 0?

This is for the Microsoft Excel - Compatibility Checker window that shows a GUI but does not have an icon in the task bar and has MainWindowHandle = 0.

I have no other instances of Excel running.

Add-Type @"
  using System;
  using System.Runtime.InteropServices;
  public class Tricks {
     [DllImport("user32.dll")]
     [return: MarshalAs(UnmanagedType.Bool)]
     public static extern bool SetForegroundWindow(IntPtr hWnd);
  }
"@

$excel = (Get-Process | Where-Object { $_.ProcessName -eq 'EXCEL' }).MainWindowHandle
[void] [Tricks]::SetForegroundWindow($excel)

In Windows Task Manager, I can right click on 'Microsoft Excel - Compatibility Checker' and click on "Bring To Front" and that works. How can I mimic this functionality in Powershell?


Solution

  • Thanks to IInspectable for pointing me in the right direction.

    This code gets the real MainWindowHandle value:

    $TypeDef2 = @"
    
    using System;
    using System.Text;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    
    namespace Api
    {
    
    public class WinStruct
    {
       public string WinTitle {get; set; }
       public int MainWindowHandle { get; set; }
    }
    
    public class ApiDef
    {
       private delegate bool CallBackPtr(int hwnd, int lParam);
       private static CallBackPtr callBackPtr = Callback;
       private static List<WinStruct> _WinStructList = new List<WinStruct>();
    
       [DllImport("User32.dll")]
       [return: MarshalAs(UnmanagedType.Bool)]
       private static extern bool EnumWindows(CallBackPtr lpEnumFunc, IntPtr lParam);
    
       [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
       static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    
       private static bool Callback(int hWnd, int lparam)
       {
           StringBuilder sb = new StringBuilder(256);
           int res = GetWindowText((IntPtr)hWnd, sb, 256);
          _WinStructList.Add(new WinStruct { MainWindowHandle = hWnd, WinTitle = sb.ToString() });
           return true;
       }  
    
       public static List<WinStruct> GetWindows()
       {
          _WinStructList = new List<WinStruct>();
          EnumWindows(callBackPtr, IntPtr.Zero);
          return _WinStructList;
       }
    
    }
    }
    "@
    
    Add-Type -TypeDefinition $TypeDef2 -Language CSharpVersion3
    
    $excelInstance = [Api.Apidef]::GetWindows() | Where-Object { $_.WinTitle.ToUpper() -eq "Microsoft Excel - Compatibility Checker".ToUpper() }
    

    So now using this correct value, I can call the SetForegroundWindow() function:

    Add-Type @"
      using System;
      using System.Runtime.InteropServices;
      public class Tricks {
         [DllImport("user32.dll")]
         [return: MarshalAs(UnmanagedType.Bool)]
         public static extern bool SetForegroundWindow(IntPtr hWnd);
      }
    "@
    [void] [Tricks]::SetForegroundWindow($excelInstance.MainWindowHandle)
    

    I wrote a detailed blog about this on my website.

    I've put up a full example on GitHub of how to create an Excel file, edit it and run the above code in a different thread which you have to do because the Excel popup blocks the main thread.