1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Windows Terminal が構成する「入力用ウィンドウ」のハンドルを取得する

Last updated at Posted at 2025-09-23

やり方

入力用ウィンドウのハンドルを取得

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"}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?