Search notes:

Powershell module: WinGUI

WinGUI is a PowerShell module that allows to manipulate the MS Windows GUI by wrapping the relevant Win32 API functions (P/Invoke).

Functions

get-childWindows Wrapper for EnumChildWindows. An alias of this function is enum-childWindows.
get-childWindowsFiltered An alias of this function is enum-childWindowsFiltered
get-windowText
get-windowClassName
set-windowPos Compare with move-window
set-windowTopMost
move-window Compare with set-windowPos
get-windowRect
find-window, find-windowEx
send-windowMessage

Examples

Move and resize notepad

The following example makes sure that exactly one process of notepad.exe is running and the calls find-window to get this process' main window window handle ($hWnd).
It then calls get-windowRect to determine the size and position of the window and finally calls set-windowPos to move and resize the window:
get-process   notepad -errorAction ignore | stop-process
start-process notepad

start-sleep 1

$hwnd = find-window notepad

$rect = get-windowRect $hwnd

start-sleep 1

set-windowPos $hwnd 0   `
  ($rect.left   - 10)   `
  ($rect.top    - 10)   `
  ($rect.right  - $rect.left + 20) `
  ($rect.bottom - $rect.top  + 20)

get-windowText

get-windowText returns a window's title:
get-process   notepad -errorAction ignore | stop-process
start-process notepad

start-sleep 1

$hwnd = find-window notepad

get-windowText $hWnd

Move window to the top left corner

This examples tries to find a windows whose title maches xyz with enum-childWindowsFiltered. If it finds exactly one window, it uses move-window to move the window into the top left corner.
$win_xyz = enum-childWindowsFiltered {
   param (
      [int] $hWnd
   )

   $title = get-windowText $hWnd

   return $title -match 'xyz'
}

if ($win_xyz.count -ne 1) {
  'expected ONE window'
   return
}

$rect = get-windowRect $win_xyz[0].hwnd

move-window $win_xyz[0].hwnd 0 0 ($rect.right - $rect.left) ($rect.bottom - $rect.top) $false

enum-childWindows

This example demonstrates the function get-childWindows for which enum-childWindows is an alias.
$callback is a script block which is called for each (top level) window.
The script block adds all windows to a (script) variable ($foundWindows) whose title is neither IME nor MSCTFIME UI:
$foundWindows = @()

$callback = {

   param (
      [IntPtr] $hWnd,
      [IntPtr] $unused_in_this_example
   )

   $winTxt    = get-windowText      $hWnd
   $className = get-windowClassName $hWnd

   if ( ($className -notIn 'IME', 'MSCTFIME UI') -and
        ($winTxt                               )       ) {

       $script:foundWindows += new-object psCustomObject -property ([ordered] @{ hWnd = $hWnd; windowText = $winTxt; className = $className })

       return $true
   }

   return $true
}


enum-childWindows $callback

$foundWindows

enum-childWindowsFiltered

This example demonstrates the function get-childWindowsFiltered for which enum-childWindowsFiltered is an alias.
The function takes a script block which is invoked for each top level Window. If the script block returns $true, the window is added to the list of windows returned by enum-childWindowsFiltered.
This simple script uses the regular expressions \d to find all windows that have a number in their title:
$wins = enum-childWindowsFiltered {

   param ([int] $hWnd)

   $title = get-windowText $hWnd

   return $title -match '\d'
}

$wins

set-windowTopMost

The following example finds a Snipping Tool (SnippingTool.exe) window (whose class name is Windows/dirs/Windows/System32/SnippingTool_exe) and makes sure that it is always on visible:
$hwnd = find-window  Microsoft-Windows-SnipperEditor
set-windowTopmost $hwnd

send-windowMessage

This example sends a WM_SETTEXT message to a notepad's Edit window:
#
#  make sure exactly one process of notepad is running
#
get-process   notepad -errorAction ignore | stop-process
start-process notepad

start-sleep 1

$hwnd_notepad     = find-window  'notepad'
$hwnd_notepadEdit = find-windowEx $hwnd_notepad 0 'Edit'

$WM_SETTEXT = 0x000C

$ret = send-windowMessage $hwnd_notepadEdit $WM_SETTEXT 0 "This text was`ninserted with`nsend-windowMessage."
"send message returned $ret"

Source code

WinGUI.psm1

set-strictMode -version 3

$memberDefinition = @'

using System;
using System.Text;
using System.Runtime.InteropServices;

public class WinGUI {


  [StructLayout(LayoutKind.Sequential)]
   public struct RECT {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
   }

   public delegate bool enumChildWindowsProc(
      IntPtr               hWnd,
      IntPtr               lParam
   );

  [DllImport("user32.dll")]
   public static extern bool EnumChildWindows(
      IntPtr               hWnd,
      enumChildWindowsProc callback,
      IntPtr               lParam
   );

  [DllImport("user32.dll", SetLastError=true)]
   public static extern Int32 GetWindowTextLength(
      IntPtr               hWnd
   );

  [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
   public static extern int GetWindowText(
      IntPtr               hwnd,
      StringBuilder        lpString,
      int                  cch
   );

  [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
   public static extern int GetClassName(
      IntPtr               hwnd,
      StringBuilder        lpString,
      int                  cch
   );

  [DllImport("user32.dll", SetLastError = true)]
   public static extern IntPtr FindWindow(
      string               className,
      string               windowTitle
   );

  [DllImport("user32.dll", SetLastError = true)]
   public static extern IntPtr FindWindowEx(
      IntPtr               hWndParent,
      IntPtr               hWndChildAfter,
      string               className,
      string               windowTitle
   );

  [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
   public static extern IntPtr SendMessageString(
       IntPtr                                   hWnd,
       int                                      msg,
       IntPtr                                   wParam,
      [MarshalAs(UnmanagedType.LPWStr)] string  lParam
   );

  [DllImport("user32.dll", SetLastError=true)]
   public static extern IntPtr SetActiveWindow(
        IntPtr                                  hWnd
   );

  [DllImport("user32.dll", SetLastError=true)]
   public static extern bool SetForegroundWindow(
        IntPtr                                  hWnd
   );

  [DllImport("user32.dll")]
   public static extern bool SetWindowPos(
        IntPtr                                  hWnd,
        IntPtr                                  hWndInsertAfter,
        Int32                                   x,
        Int32                                   y,
        Int32                                   cx,
        Int32                                   cy,
        UInt32                                  uFlags
   );

  [DllImport("user32.dll")]
   public static extern bool MoveWindow(
        IntPtr                                  hWnd,
        Int32                                   x,
        Int32                                   y,
        Int32                                   w,
        Int32                                   h,
        Boolean                                 repaint
   );

  [DllImport("user32.dll")]
  [return: MarshalAs(UnmanagedType.Bool)]
   public static extern bool GetWindowRect(
        IntPtr                                   hWnd,
    out RECT                                     lpRect
   );


// public static extern bool EnumChildWindows(
//   EnumWindowsProc enumProc,
//   IntPtr lParam);

}
'@

add-type -typeDef $memberDefinition # -name WinGUI # -namespace WinGUI


function get-childWindows {

   param (
      [scriptblock] $callBack,
      [IntPtr     ] $hWnd      = [IntPtr]::Zero,
      [IntPtr     ] $param     = [IntPtr]::Zero
   )

   $null = [WinGUI]::EnumChildWindows(
       $hWnd,
       $callBack,
       $param
   )
}

function get-childWindowsFiltered {
 #
 # Apply $filter on each window and
 # return windows for which $filter
 # returns true.
 #
 # It is assumed that $filter takes a
 # $hWnd parameter.
 #
 # get-childWindowsFiltered returns
 # a System.Collections.Generic.List[...win]
 #

   param (
      [scriptBlock] $filter
   )

   class win {

      [int   ] $hWnd
      [string] $title
      [string] $class

   }

   class prm {
      [System.Collections.Generic.List[win]]  $wins
      [scriptBlock]                           $flt
   }


   $callbackEnum = {

      param (
         [IntPtr] $hWnd,
         [IntPtr] $psObjPtr  # param
      )

      $gch  = [System.Runtime.InteropServices.GCHandle]::FromIntPtr($psObjPtr);
      $pp   = $gch.Target

      if (invoke-command $pp.flt -argumentList $hWnd) {

           $win       = new-object win
           $win.hWnd  = $hWnd
           $win.title = get-windowText      $hWnd
           $win.class = get-windowClassName $hWnd

           $pp.wins.Add($win)
       }

       return $true
   }

   $p      = new-object prm
   $p.flt  = $filter
   $p.wins = new-object 'System.Collections.Generic.List[win]'

   $g   = [System.Runtime.InteropServices.GCHandle]::Alloc($p);
   $ptr = [System.Runtime.InteropServices.GCHandle]::ToIntPtr($g)

   enum-childWindows $callbackEnum -param $ptr

   $g.Free()

  ,$p.wins
}

function get-windowText {

   param (
      [IntPtr] $hWnd
   )

   $len  = [WinGUI]::GetWindowTextLength($hWnd)
   $sb   = new-object Text.Stringbuilder ($len+1)
   $null = [WinGUI]::GetWindowText($hWnd, $sb, $sb.Capacity)

   $sb.ToString()

}

function get-windowClassName {

   param (
      [IntPtr] $hWnd
   )

   $sb   = new-object Text.Stringbuilder 256
   $null = [WinGUI]::GetClassName($hWnd, $sb, $sb.Capacity)

   $sb.ToString()
}

function set-windowPos {
   param (
      [IntPtr] $hWnd,
      [IntPtr] $hWndInsertAfter,
      [Int32 ] $x,
      [Int32 ] $y,
      [Int32 ] $cx,
      [Int32 ] $cy,
      [UInt32] $uFlags
   )

   [WinGUI]::SetWindowPos($hWnd, $hWndInsertAfter, $x, $y, $cx, $cy, $uFlags)
}

function move-window {
   param (
      [IntPtr ] $hWnd,
      [Int32  ] $x,
      [Int32  ] $y,
      [Int32  ] $w,
      [Int32  ] $h,
      [Boolean] $repaint
   )

   [WinGUI]::MoveWindow($hWnd, $x, $y, $w, $h, $repaint)
}

function get-windowRect {
   param (
      [IntPtr] $hWnd
   )

   $rect = new-object WinGUI+RECT

   if ([WinGUI]::GetWindowRect($hWnd, [ref] $rect)) {
      return $rect
   }

   return $null

}

function find-window {

   param (
      [string] $className    = '',
      [string] $windowTitle  = ''
   )

   $className_   = if ($className   -eq '') { [NullString]::Value} else { $className  }
   $windowTitle_ = if ($windowTitle -eq '') { [NullString]::Value} else { $windowTitle}

   return [WinGUI]::FindWindow($className_, $windowTitle_)
}

function find-windowEx {

   param (
      [IntPtr] $hWndParent,
      [IntPtr] $hWndChildAfter,
      [string] $className       = '',
      [string] $windowTitle     = ''
   )

   $className_   = if ($className   -eq '') { [NullString]::Value } else { $className   }
   $windowTitle_ = if ($windowTitle -eq '') { [NullString]::Value } else { $windowTitle }

   return [WinGUI]::FindWindowEx($hWndParent, $hWndChildAfter, $className_, $windowTitle_)
}

function send-windowMessage {

   param (
      [IntPtr] $hWnd       ,
      [int   ] $msg        ,
      [IntPtr] $wParam     ,
      [object] $lParam
   )

   if ($lParam -is [string]) {
      return [WinGUI]::SendMessageString($hWnd, $msg, $wParam, $lParam)
   }
   else {
      write-textinConsoleWarningColor "todo in send-windowMessage: implement me lParam is not string."
   }
}

function set-activeWindow {
   param (
     [IntPtr]   $hWnd
   )

   return [WinGUI]::SetActiveWindow($hWnd)
}

function set-foregroundWindow {
   param (
     [IntPtr]   $hWnd
   )

   return [WinGUI]::SetForegroundWindow($hWnd)
}

function set-windowTopmost {
 #
 # The following code was helpful:
 #    https://github.com/bkfarnsworth/Always-On-Top-PS-Script/blob/master/Always_On_Top.ps1
 #
   param (
     [IntPtr]   $hWnd,
     [bool]     $topMost = $true
   )

   if ($topMost) {
      $hWndInserAfter = ([IntPtr]::new(-1)) # HWND_TOPMOST
   }
   else {
      $hWndInserAfter = ([IntPtr]::new(-2)) # HWND_NOTOPMOST
   }

 #
 # 0x03 = 0x01 (SWP_NOSIZE) + 0x02 (SWP_NOMOVE)
 #
   set-windowPos $hWnd  $hWndInserAfter  0  0  0  0  0x03
}

new-alias enum-childWindows         get-childWindows
new-alias enum-childWindowsFiltered get-childWindowsFiltered
Github repository ps-modules-WinGUI, path: /WinGUI.psm1

WinGUI.psd1

@{
   RootModule         = 'WinGUI'
   ModuleVersion      = '0.1'

   RequiredAssemblies = @(
   )

   RequiredModules    = @(
   )

   FunctionsToExport  = @(
    #
    # WinAPI functions
    #
     'get-childWindows'         ,
     'get-windowRect'           ,
     'get-windowText'           ,
     'get-windowClassName'      ,
     'set-windowPos'            ,
     'find-window'              ,
     'find-windowEx'            ,
     'move-window'              ,
     'send-windowMessage'       ,
     'set-foregroundWindow'     ,
     'set-activeWindow'         ,
    #
    # Non-WinAPI functions
    #
     'get-childWindowsFiltered' ,
     'set-windowTopMost'
   )

   AliasesToExport    = @(
     'enum-childWindows'        ,
     'enum-childWindowsFiltered'
   )

   FormatsToProcess   = @(
   )
}
Github repository ps-modules-WinGUI, path: /WinGUI.psd1

See also

René's simple PowerShell modules

Index