やり方
入力用ウィンドウのハンドルを取得
PowerShell
# P/Invoke Signature
Add-Type -TypeDefinition @"
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace Win32
{
public static class API
{
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern int GetClassName(
IntPtr hWnd,
StringBuilder lpClassName,
int nMaxCount
);
public delegate bool EnumProc(
IntPtr hWnd,
IntPtr lParam
);
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern bool EnumChildWindows(
IntPtr hwndParent,
EnumProc lpEnumFunc,
IntPtr lParam
);
}
}
"@
# コールバック関数
$callback = {
param(
$hWnd,
$lParam
)
$stringBuilder = New-Object System.Text.StringBuilder 256
[Win32.API]::GetClassName($hWnd, $stringBuilder, $stringBuilder.Capacity)
$resultObject = [PSCustomObject][ordered]@{
HWnd = $hWnd
ClassName = $stringBuilder.ToString()
}
$windowList.Add($resultObject) | Out-Null
return $true
}
$enumChildProc = [Win32.API+EnumProc]$callback
# ウィンドウハンドルの取得
$process = Get-Process WindowsTerminal
$hWnd = $process.MainWindowHandle
# EnumChildWindows を実行
$windowList = [System.Collections.Generic.List[object]]::new()
[Win32.API]::EnumChildWindows($hWnd, $enumChildProc, [IntPtr]::Zero)
# 結果の取得
$windowList.Where{$_.ClassName -match "Windows.UI.Input.InputSite.WindowClass"}
試しに Enter を送ってみる
PowerShell
$inputHWnd = $windowList.Where{$_.ClassName -match "Windows.UI.Input.InputSite.WindowClass"}.HWnd
# PostMessage を使って Enter を送ってみる
Add-Type -Namespace Win32 -Name POST -MemberDefinition @"
public const uint WM_KEYDOWN = 0x0100;
public const uint WM_KEYUP = 0x0101;
public const uint WM_CHAR = 0x0102;
public const int VK_RETURN = 0x0D;
public static UInt32 Repeat = 1; // bits 0..15
public static UInt32 ScanCode = 0x00; // bits 16..23
public static bool Extended = false; // bit 24
public static bool ContextAlt = false; // bit 29
public static bool PreviousKeyState = false; // bit 30
public static bool TransitionState = false; // bit 31 (0=keydown,1=keyup)
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern bool PostMessage(
IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam
);
"@
# PostMessage "Enter"
$msg = [Win32.POST]::WM_KEYDOWN
$wParam = [Win32.POST]::VK_RETURN
[Win32.POST]::Repeat = 1
[Win32.POST]::ScanCode = 0x1C # スキャンコード(キーボード上の配置を表す値)
[Win32.POST]::Extended = $false # ここでは NumPad Enter にするかどうか
[Win32.POST]::TransitionState = $false # キー押下
[UInt32]$contextFlag = 0
$contextFlag = $contextFlag -bor ([Win32.POST]::Repeat -band 0xFFFF)
$contextFlag = $contextFlag -bor (([Win32.POST]::ScanCode -band 0xFF) -shl 16)
if ([Win32.POST]::Extended) { $contextFlag = $contextFlag -bor ([UInt32]1 -shl 24) }
if ([Win32.POST]::ContextAlt) { $contextFlag = $contextFlag -bor ([UInt32]1 -shl 29) }
if ([Win32.POST]::PreviousKeyState) { $contextFlag = $contextFlag -bor ([UInt32]1 -shl 30) }
if ([Win32.POST]::TransitionState) { $contextFlag = $contextFlag -bor ([UInt32]1 -shl 31) }
# "0x{0:X8}" -f $contextFlag
$lParam = [IntPtr]::new([long]$contextFlag)
[Win32.POST]::PostMessage($inputHWnd, $msg, $wParam, $lParam)
※注意※
foreach を使って char[] を送ると PowerShell がクラッシュします。
※補遺※
この状況で PowerShell に文字列を送るには、
1. SendInput
2. AutoHotkey なんかできそう (URL: https://www.reddit.com/r/AutoHotkey/comments/139o980/controlsend_works_with_command_prompt_but_not/?tl=ja )
3. UI Automation もしかしたらできるかも (URL: https://accessibilityinsights.io/docs/windows/overview/ )
4. MessageOnlyWindow をセルフホスト(PowerShell と同一プロセス内)
5. WriteConsoleInput を使った C source を dll にコンパイルして、PowerShell プロセス内に直接配置する
などの方法が必要になってくるかもしれません。
探し方
P/Invoke Signature
Add-Type -TypeDefinition @"
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace Win32
{
/* AttachThreadInput */
public static class FOCUS
{
[DllImport("user32.dll")]
public static extern IntPtr SetFocus(
IntPtr hWnd
);
[DllImport("user32.dll")]
public static extern IntPtr GetFocus();
[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(
IntPtr hWnd,
out uint lpdwProcessId
);
[DllImport("kernel32.dll")]
public static extern uint GetCurrentThreadId();
[DllImport("user32.dll")]
public static extern bool AttachThreadInput(
uint idAttach,
uint idAttachTo,
bool fAttach
);
}
/* EnumChildWindows */
public static class ENUMc
{
public delegate bool EnumProc(
IntPtr hWnd,
IntPtr lParam
);
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern bool EnumChildWindows(
IntPtr hwndParent,
EnumProc lpEnumFunc,
IntPtr lParam
);
}
/* EnumThreadWindows */
public static class ENUMt
{
[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(
IntPtr hWnd,
out uint lpdwProcessId
);
public delegate bool EnumThreadProc(
IntPtr hWnd,
IntPtr lParam
);
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern bool EnumThreadWindows(
uint dwThreadId,
EnumThreadProc lpfn,
IntPtr lParam
);
}
/* FindWindowEx */
public static class FIND
{
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern IntPtr FindWindowExW(
IntPtr hwndParent,
IntPtr hwndChildAfter,
string lpszClass,
string lpszWindow
);
}
/* Extra information */
public static class INFO
{
public const int bufferSize = 256;
public static string WrapGetClassName(IntPtr hWnd)
{
StringBuilder strBuf = new System.Text.StringBuilder(bufferSize);
int length = GetClassName(hWnd, strBuf, strBuf.Capacity);
if (length == 0)
{
return string.Empty;
}
return strBuf.ToString();
}
public static string WrapGetWindowText(IntPtr hWnd)
{
StringBuilder strBuf = new System.Text.StringBuilder(bufferSize);
int length = GetWindowText(hWnd, strBuf, strBuf.Capacity);
if (length == 0)
{
return string.Empty;
}
return strBuf.ToString();
}
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern int GetClassName(
IntPtr hWnd,
StringBuilder lpClassName,
int nMaxCount
);
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern int GetWindowText(
IntPtr hWnd,
StringBuilder lpString,
int nMaxCount
);
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(
IntPtr hWnd
);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetParent(
IntPtr hWnd
);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsChild(
IntPtr hWndParent,
IntPtr hWnd
);
}
}
"@
PowerShell の「親プロセスのウィンドウハンドル」を取得する関数
function Get-PSParentHWnd
{
try
{
$currentPid = $PID
$targetProcesses = @('WindowsTerminal.exe', 'conhost.exe')
while ($currentPid)
{
$process = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $currentPid"
if (!$process)
{
Write-Host "プロセス情報取得失敗 (PID: $currentPid)" -ForegroundColor Red
break
}
$processName = $process.Name
if ($processName -in $targetProcesses)
{
if ($processName -eq 'WindowsTerminal.exe')
{
return (Get-Process -Pid $currentPid).MainWindowHandle
}
Write-Host "conhost.exe が見つかりました" -ForegroundColor Blue
return $false
}
$currentPid = $process.ParentProcessId
}
Write-Host "Windows Terminal/conhost.exe が見つかりませんでした" -ForegroundColor Red
return $false
}
catch
{
Write-Host "エラー: $($_.Exception.Message)" -ForegroundColor Red
return $false
}
}
「キーボード フォーカス」があるウィンドウのハンドルを取得する関数
function Find-InputWindow
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[IntPtr] $TopHWnd
)
if ($TopHWnd -eq [IntPtr]::Zero) {throw "TopHWnd に 0 は指定できません。"}
# AttachThreadInput
$pPid = 0
$currentThreadId = [Win32.FOCUS]::GetCurrentThreadId()
$targetThreadId = [Win32.FOCUS]::GetWindowThreadProcessId($TopHWnd, [ref]$pPid)
$attached = [Win32.FOCUS]::AttachThreadInput($currentThreadId, $targetThreadId, $true)
if (!$attached) {throw "AttachThreadInput が失敗しました。"}
# SetFocus → GetFocus → 以前のフォーカスを復元 → 切り離す
try
{
$prev = [Win32.FOCUS]::SetFocus($TopHWnd)
Start-Sleep -Milliseconds 40
$focus = [Win32.FOCUS]::GetFocus()
Start-Sleep -Milliseconds 40
[void][Win32.FOCUS]::SetFocus($prev)
}
finally
{
[Win32.FOCUS]::AttachThreadInput($currentThreadId, $targetThreadId, $false) | Out-Null
}
if ($focus -eq [IntPtr]::Zero) {throw "SetFocus failed"}
return $focus
}
「入力用ウィンドウ」を検出
PowerShell
# コールバック関数の定義
$callback = {
param(
$hWnd,
$lParam
)
$TopHWnd = [IntPtr]::new([long]${script:TopHWnd})
$local:stringBuilder = New-Object System.Text.StringBuilder 256
[Win32.INFO]::GetClassName($hWnd, $stringBuilder, $stringBuilder.Capacity)
$local:resultObject = [PSCustomObject][ordered]@{
HWnd = $hWnd
ClassName = $stringBuilder.ToString()
IsVisible = [Win32.INFO]::IsWindowVisible($hWnd)
IsProgeny = [Win32.INFO]::IsChild($TopHWnd, $hWnd)
Parent = [Win32.INFO]::GetParent($hWnd)
}
$local:resultArray = [System.Runtime.InteropServices.GCHandle]::FromIntPtr($lParam)
$local:resultArray.Target.Add($resultObject) | Out-Null
return $true
}
$enumChildProc = [Win32.ENUMc+EnumProc]$callback
$enumThreadProc = [Win32.ENUMt+EnumThreadProc]$callback
# PowerShell が実行されているウィンドウのハンドルを取得
$topHWnd = Get-PSParentHWnd
# 標準入力を持つウィンドウのハンドルを取得
$kFocus = Find-InputWindow $topHWnd
# EnumChildWindows を実行
$gcHandleWL = [System.Runtime.InteropServices.GCHandle]::new()
# ⇧ `if ($gcHandleWL.IsAllocated)` の例外を防ぐため。⇧
try
{
$windowList = [System.Collections.Generic.List[object]]::new()
$gcHandleWL = [System.Runtime.InteropServices.GCHandle]::Alloc($windowList)
$pReference = [System.Runtime.InteropServices.GCHandle]::ToIntPtr($gcHandleWL)
[void][Win32.ENUMc]::EnumChildWindows($topHWnd, $enumChildProc, $pReference)
$resultArray = $windowList -as [PSCustomObject[]]
}
finally
{
if ($gcHandleWL.IsAllocated)
{
$gcHandleWL.Free()
}
}
# EnumThreadWindows を実行
$processId = [Activator]::CreateInstance([UInt32])
$threadId = [Win32.ENUMt]::GetWindowThreadProcessId($topHWnd, [ref]$processId)
$gcHandleWL = [System.Runtime.InteropServices.GCHandle]::new()
try
{
$windowList = [System.Collections.Generic.List[object]]::new()
$gcHandleWL = [System.Runtime.InteropServices.GCHandle]::Alloc($windowList)
$pReference = [System.Runtime.InteropServices.GCHandle]::ToIntPtr($gcHandleWL)
[void][Win32.ENUMt]::EnumThreadWindows($threadId, $enumThreadProc, $pReference)
$resultArray += $windowList -as [PSCustomObject[]]
}
finally
{
if ($gcHandleWL.IsAllocated)
{
$gcHandleWL.Free()
}
}
# 検出
$resultArray.Where{$_.HWnd -match $kFocus}
Windows Terminal に連なるウィンドウのできる限りの列挙を試みる
UWPSpy
※「UWPSpy」に関しては参考までに URL を紹介しておきます。
公式サイト
GitHub
窓の社
@EndOfData
FindWindowEx
※以下のコードだと情報は特に増えないかもしれません。
※一応 FindWindowEx バージョンも、ということで......。
PowerShell
# FindWindowEx を実行
$hashSet = [System.Collections.Generic.HashSet[object]]::new(@($topHWnd) + @($resultArray.HWnd))
Add-Type -AssemblyName System.Management.Automation
$pNull = [System.Management.Automation.Language.NullString]::Value
$wndList = [System.Collections.Generic.List[IntPtr]]::new()
foreach ($hWnd in $hashSet)
{
$immediate = [IntPtr]::Zero
:LOOP do
{
$value = [Win32.FIND]::FindWindowExW([IntPtr]$hWnd, $immediate, $pNull, $pNull)
if ($value -ne [IntPtr]::Zero)
{
$wndList.Add($value)
$immediate = $value # hWndChildAfter を更新
}
} while ($value -ne [IntPtr]::Zero)
}
$uniqueArray = [System.Collections.Generic.HashSet[object]]::new(@($wndList))
# 重複があったか確認したい場合
<#
$COMP = [System.Collections.StructuralComparisons]::StructuralEqualityComparer
# True なら重複なし
$COMP.Equals(@($uniqueArray), @($wndList))
# 出力がないなら重複なし
diff @($uniqueArray) @($wndList)
#>
# ハンドルとクラス名を紐づけ
$exhaustiveSet =
$uniqueArray |
Select-Object `
@{
Name="HWnd";
Expression={$_}
},
@{
Name="ClassName";
Expression={
[Win32.INFO]::WrapGetClassName($_)
}
},
@{
Name="WindowText";
Expression={
[Win32.INFO]::WrapGetWindowText($_)
}
}<#
#
#
#>
# 結果の表示
$exhaustiveSet.Where{$_.ClassName -match "Input"}