Развивание и каскадирование определенной программы для настольных пк windows

What you’re trying to do requires multiple, nontrivial P/Invoke calls to the WinAPI:

# Helper type for various window-related WinAPI functions.
Add-Type -Namespace Util -Name WinApi -MemberDefinition @'
    // The callback delegate, which receives:
    // - the hWnd being enumerated
    // - a custom LPARAM value passed on invocation to EnumWindows()
    // The delegate must return $true to keep enumerating; in other words: $false stops the enumeration.
    // Note the custom ArrayList parameter in lieu of an IntPtr (LPARAM) (using a generic list is NOT an option, because generic types cannot be marshalled).
    public delegate bool EnumWindowsProc(IntPtr hWnd, System.Collections.ArrayList lParam);

    // The EnumWindows() API function that enumerates *top-level windows*.
    // It too uses a custom ArrayList parameter instead of the IntPtr (LPARAM), which the callback receives.
    [DllImport("user32.dll")]
    public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, System.Collections.ArrayList lParam);
    
    // Get the class name of a window by its handle.
    [DllImport("user32.dll")]
    public static extern int GetClassName(IntPtr hWnd, System.Text.StringBuilder classNameBuf, int nMaxCount);

    // Cascade the specified window(s).
    [DllImport("user32.dll")]
    public static extern ushort CascadeWindows(IntPtr hwndParent, uint wHow, IntPtr lpRect, uint cKids, IntPtr[] lpKids);
    
    // Show the specified window.
    [DllImport("user32.dll")]
    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    // Make the specified window the foreground window, if allowed (see below).
    [DllImport("user32.dll")]
    public static extern bool SetForegroundWindow(IntPtr hWnd);

    // Allow making the windows of other processes the foreground window 
    // for the remainder of the session.
    [DllImport("user32.dll", EntryPoint="SystemParametersInfo")]
    static extern bool SystemParametersInfo_Set_UInt32(uint uiAction, uint uiParam, UInt32 pvParam, uint fWinIni);
    public static void AllowWindowActivation()
    {
      if (! SystemParametersInfo_Set_UInt32(0x2001 /* SPI_SETFOREGROUNDLOCKTIMEOUT */, 0, 0 /* timeout in secs */, 0 /* non-persistent change */)) {
        throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error(), "Unexpected failure calling SystemParametersInfo() with SPI_SETFOREGROUNDLOCKTIMEOUT");
      }
    }
    
'@

# Get all Notepad windows, on the assumption that their window class name is 'Notepad'.
[System.Collections.ArrayList] $matchingHwnds = @()
$null = [Util.WinApi]::EnumWindows(
    {                                                                  # delegate (callback function)
      param([intptr] $hWnd, [System.Collections.ArrayList] $param) 
      $sb = [System.Text.StringBuilder]::new(1024)
      $null = [Util.WinApi]::GetClassName($hWnd, $sb, $sb.Capacity-1)
      if ($sb.ToString() -eq 'Notepad') {
        $param.Add($hWnd)
      }
      return $true # continue enumerating
    }, 
    $matchingHwnds                                                          # the array list to pass as custom data
)

# Reverse the matching window handles (if any).
[IntPtr[]] $matchingHwndsInReverse = [Linq.Enumerable]::Reverse([IntPtr[]] $matchingHwnds)

# Make sure all windows are restored (non-minimized, non-maximized), in reverse order.
foreach ($hwnd in $matchingHwndsInReverse) {
  $null = [Util.WinAPI]::ShowWindow($hwnd, 1)
}

# Cascade them.
$null = [Util.WinAPI]::CascadeWindows([IntPtr]::Zero, 0, [IntPtr]::Zero, $matchingHwnds.Count, $matchingHwnds)

# Activate them in reverse order.
# First, enable cross-process window activation...
[Util.WinAPI]::AllowWindowActivation()
# ... then activate them.
foreach ($hwnd in $matchingHwndsInReverse) {
  $null = [Util.WinAPI]::SetForegroundWindow($hwnd)
}

As for what you tried:

  • Shell.Application.CascadeWindows() indeed by design invariably cascades all (presumably non-minimized only) windows – you cannot constrain it to only a given application’s windows.

    • Additionally, this method seemingly no longer works as of Windows 11; similarly, the cascading feature has been removed from the GUI (you used to be able to right-click on the taskbar and select Cascade windows).
    • What you presumably meant was this – but it wouldn’t work on Windows 11, with the new, UWP implementation of Notepad:

      (Get-Process | Where {$_.MainWindowTitle -and $_.Description -like '*note*' }).MainWindowTitle
      
      # Simpler alternative:
      @((Get-Process notepad).MainWindowTitle) -ne $null
      
    • On Windows 11, with the new UWP implementation of Notepad.exe, only the title of a single Notepad window – namely the currently or most recently active one – is reflected in the .MainWindowTitle property of the Notepad processes, so that there’s no one-to-one relationship between processes and visible windows.

:/>  Bat запросить права администратора

Therefore, a WinAPI-based solution, as shown above, is required, because it:

  • allows you to find top-level windows by class name, independently of examining processes.

  • allows you to cascade only a given set of windows (though you must activate them separately).

Оставьте комментарий