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 Click buttons, set text in edit controles etc.

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 example 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 visible (on top):
$hwnd = find-window  Microsoft-Windows-SnipperEditor
set-windowTopmost $hwnd

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", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
   public static extern IntPtr SendMessageInt(
       IntPtr                                   hWnd,
       int                                      msg,
       IntPtr                                   wParam,
       int                                      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  =  0,
      [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)
   }
   elseif ($lParam -is [int]) {
      return [WinGUI]::SendMessageInt(   $hWnd, $msg, $wParam, $lParam)
   }
   else {
      write-textinConsoleWarningColor "todo in send-windowMessage: implement me lParam is neither string nor int"
   }
}

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.3'

   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

History

V.2 Default parameter for $hwndChildAfter in find-WindowEx
V.3 Allow for int parameter to be passed to send-windowMessage. Added BM_CLICK` example.

See also

René's simple PowerShell modules
connect-SonicWallTunnel.ps1 is a script that uses the WinGUI module to establish a VPN tunnel with SonicWall without manual intervention.
The Perl module Win32::GuiTest.

Index

Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database in /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php:78 Stack trace: #0 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(78): PDOStatement->execute(Array) #1 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(30): insert_webrequest_('/notes/Windows/...', 1759390852, '216.73.216.42', 'Mozilla/5.0 App...', NULL) #2 /home/httpd/vhosts/renenyffenegger.ch/httpsdocs/notes/Windows/PowerShell/modules/personal/WinGUI/index(626): insert_webrequest() #3 {main} thrown in /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php on line 78