Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing [System.Windows.Forms.Application]::EnableVisualStyles() # ========== Native SendInput (Keyboard / Scancode) — mit Union & Fehlerprüfung ========== Add-Type -Language CSharp @' using System; using System.Runtime.InteropServices; public static class ScanInput { // ================================ // Structs // ================================ [StructLayout(LayoutKind.Sequential)] public struct INPUT { public uint type; public InputUnion U; } [StructLayout(LayoutKind.Explicit)] public struct InputUnion { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; [FieldOffset(0)] public HARDWAREINPUT hi; } [StructLayout(LayoutKind.Sequential)] public struct MOUSEINPUT { public int dx, dy; public uint mouseData, dwFlags, time; public IntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] public struct KEYBDINPUT { public ushort wVk, wScan; public uint dwFlags, time; public IntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] public struct HARDWAREINPUT { public uint uMsg; public ushort wParamL, wParamH; } // ================================ // Constants // ================================ private const uint INPUT_KEYBOARD = 1; private const uint KEYEVENTF_SCANCODE = 0x0008; private const uint KEYEVENTF_KEYUP = 0x0002; private const uint KEYEVENTF_EXTENDEDKEY = 0x0001; private const uint MAPVK_VK_TO_VSC_EX = 0x04; // ================================ // P/Invoke // ================================ [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); [DllImport("user32.dll")] private static extern short VkKeyScanExW(char ch, IntPtr dwhkl); [DllImport("user32.dll")] private static extern uint MapVirtualKeyEx(uint code, uint mapType, IntPtr dwhkl); [DllImport("user32.dll")] private static extern IntPtr GetKeyboardLayout(uint threadId); // ================================ // Resolve Char → Keys & Modifiers // ================================ public static bool TryGetScanAndMods( char ch, out ushort scan, out bool needShift, out bool needLCtrl, out bool needRAltExt, out bool ext, out bool isDeadKey ) { scan = 0; needShift = false; needLCtrl = false; needRAltExt = false; ext = false; isDeadKey = false; IntPtr hkl = GetKeyboardLayout(0); short result = VkKeyScanExW(ch, hkl); if (result == -1) return false; byte vk = (byte)(result & 0xFF); byte sh = (byte)((result >> 8) & 0xFF); needShift = (sh & 1) != 0; bool ctrl = (sh & 2) != 0; bool alt = (sh & 4) != 0; // DE: AltGr = Ctrl + Alt (→ RAlt extended und LCtrl) needLCtrl = ctrl || alt; needRAltExt = alt; uint scEx = MapVirtualKeyEx(vk, MAPVK_VK_TO_VSC_EX, hkl); if (scEx == 0) return false; ext = (scEx & 0x100) != 0; scan = (ushort)(scEx & 0xFF); // Dead-Key heuristics if (ch == '^' || ch == '`') isDeadKey = true; return true; } // ================================ // Low-level Send Scancode // ================================ private static void SendKey(ushort scan, bool ext, bool up) { INPUT[] inp = new INPUT[1]; inp[0].type = INPUT_KEYBOARD; inp[0].U.ki.wVk = 0; inp[0].U.ki.wScan = scan; uint flags = KEYEVENTF_SCANCODE; if (ext) flags |= KEYEVENTF_EXTENDEDKEY; if (up) flags |= KEYEVENTF_KEYUP; inp[0].U.ki.dwFlags = flags; inp[0].U.ki.time = 0; inp[0].U.ki.dwExtraInfo = IntPtr.Zero; uint sent = SendInput(1, inp, Marshal.SizeOf(typeof(INPUT))); if (sent == 0) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } // Modifier helpers public static void ModDown_LCtrl() { SendKey(0x1D, false, false); } public static void ModUp_LCtrl() { SendKey(0x1D, false, true); } public static void ModDown_LShift(){ SendKey(0x2A, false, false); } public static void ModUp_LShift() { SendKey(0x2A, false, true); } public static void ModDown_RAlt() { SendKey(0x38, true, false); } public static void ModUp_RAlt() { SendKey(0x38, true, true); } // ================================ // MAIN: Send char via scancode // ================================ public static void SendCharByScan(char ch, int delayMs) { ushort scan; bool needShift, needLCtrl, needRAltExt, ext, isDeadKey; if (!TryGetScanAndMods(ch, out scan, out needShift, out needLCtrl, out needRAltExt, out ext, out isDeadKey)) throw new InvalidOperationException("Char not representable in current layout: " + ch); if (needLCtrl) ModDown_LCtrl(); if (needRAltExt) ModDown_RAlt(); if (needShift) ModDown_LShift(); if (isDeadKey) { SendKey(scan, ext, false); SendKey(scan, ext, true); // SPACE to "commit" SendKey(0x39, false, false); SendKey(0x39, false, true); } else { SendKey(scan, ext, false); SendKey(scan, ext, true); } if (needShift) ModUp_LShift(); if (needRAltExt) ModUp_RAlt(); if (needLCtrl) ModUp_LCtrl(); } } '@ # --------------------------------------------------------- # OPTION A — Clean Scancode-only API (recommended) # --------------------------------------------------------- function Get-ClipboardRaw { Add-Type @" using System; using System.Runtime.InteropServices; public static class RawClipboard { [DllImport("user32.dll", SetLastError=true)] public static extern bool OpenClipboard(IntPtr hWndNewOwner); [DllImport("user32.dll", SetLastError=true)] public static extern bool CloseClipboard(); [DllImport("user32.dll", SetLastError=true)] public static extern IntPtr GetClipboardData(uint uFormat); [DllImport("kernel32.dll", SetLastError=true)] public static extern IntPtr GlobalLock(IntPtr hMem); [DllImport("kernel32.dll", SetLastError=true)] public static extern bool GlobalUnlock(IntPtr hMem); public const uint CF_UNICODETEXT = 13; } "@ $text = "" if ([RawClipboard]::OpenClipboard([IntPtr]::Zero)) { $h = [RawClipboard]::GetClipboardData([RawClipboard]::CF_UNICODETEXT) if ($h -ne [IntPtr]::Zero) { $p = [RawClipboard]::GlobalLock($h) if ($p -ne [IntPtr]::Zero) { $text = [Runtime.InteropServices.Marshal]::PtrToStringUni($p) [RawClipboard]::GlobalUnlock($h) | Out-Null } } [RawClipboard]::CloseClipboard() | Out-Null } return $text } function Type-Char { param( [Parameter(Mandatory=$true)][char]$Char, [int]$DelayMs = 15 ) # Scancode-basiertes Tippen über die neue Engine [ScanInput]::SendCharByScan($Char, $DelayMs) if ($DelayMs -gt 0) { Start-Sleep -Milliseconds $DelayMs } } function Type-ScancodeText { param( [Parameter(Mandatory=$true)][string]$Text, [int]$DelayMs = 15 ) if ([string]::IsNullOrEmpty($Text)) { return } # Normalize CRLF → LF $normalized = $Text -replace "`r`n","`n" -replace "`r","`n" foreach ($c in $normalized.ToCharArray()) { switch ($c) { # ENTER (0x0D → ScanCode 0x1C) "`n" { [ScanInput]::SendCharByScan([char]0x0D, $DelayMs) if ($DelayMs -gt 0) { Start-Sleep -Milliseconds $DelayMs } continue } # TAB (0x09) "`t" { [ScanInput]::SendCharByScan([char]0x09, $DelayMs) if ($DelayMs -gt 0) { Start-Sleep -Milliseconds $DelayMs } continue } # BACKSPACE (0x08) "`b" { # Backspace ist darstellbar → SendCharByScan erzeugt Scancode 0x0E [ScanInput]::SendCharByScan([char]0x08, $DelayMs) if ($DelayMs -gt 0) { Start-Sleep -Milliseconds $DelayMs } continue } default { Type-Char -Char $c -DelayMs $DelayMs } } } } # ========== WinForms-UI ========== function Show-TextWithCopyButtons { param( [Parameter()] [string[]]$InputText ) # Normalize to an array of lines (split on CR/LF across all provided items) $lines = @() if ($InputText) { foreach ($item in $InputText) { $lines += ($item -split "`r?`n") } } else { # Default: 10 empty lines if clipboard is empty $lines = @(for ($i = 1; $i -le 10; $i++) { "" }) } # Layout constants $textboxWidth = 300 $buttonWidth = 75 $padding = 10 $lineHeight = 35 $logBoxHeight = 150 # Compute form height and cap it to avoid oversized windows ### $formHeight = $lines.Count * $lineHeight + $logBoxHeight + 60 + 30 $formHeight = [Math]::Min($formHeight, 1000) # Create form $form = New-Object System.Windows.Forms.Form $form.Text = "TextCopyHelper v5" $form.Size = New-Object System.Drawing.Size(425, $formHeight) $form.StartPosition = "CenterScreen" $form.AutoScroll = $true $form.TopMost = $true # Log textbox $logBox = New-Object System.Windows.Forms.TextBox $logBox.Multiline = $true $logBox.ScrollBars = "Vertical" $logBox.ReadOnly = $true $logBox.Size = New-Object System.Drawing.Size(375, $logBoxHeight) $y = 10 foreach ($line in $lines) { $textBox = New-Object System.Windows.Forms.RichTextBox $textBox.Multiline = $false $textBox.AcceptsTab = $false $textBox.ShortcutsEnabled = $true $textBox.BorderStyle = 'Fixed3D' $textBox.ScrollBars = 'None' $textBox.Text = $line $textBox.Location = New-Object System.Drawing.Point($padding, $y) $textBox.Size = New-Object System.Drawing.Size($textboxWidth, 20) $form.Controls.Add($textBox) $button = New-Object System.Windows.Forms.Button $button.Text = "Copy" $button.Location = New-Object System.Drawing.Point(($padding + $textboxWidth + 10), ([int]$y - 1)) $button.Size = New-Object System.Drawing.Size($buttonWidth, 23) $currentTextBox = $textBox $currentLogBox = $logBox $button.Add_Click({ $text = $currentTextBox.Text if (![string]::IsNullOrWhiteSpace($text)) { [System.Windows.Forms.Clipboard]::SetText($text) $currentLogBox.AppendText("Copied: $text`r`n") } else { $currentLogBox.AppendText("Nothing to copy (empty line).`r`n") } }.GetNewClosure()) $form.Controls.Add($button) $y += $lineHeight } # Paste button $pasteButton = New-Object System.Windows.Forms.Button $pasteButton.Text = "Paste clipboard as keyboard input" $pasteButton.Size = New-Object System.Drawing.Size(250, 30) $pasteButton.Location = New-Object System.Drawing.Point($padding, ($y + 5)) $currentForm = $form $currentLog = $logBox $pasteButton.Add_Click({ try { $currentLog.AppendText("Reading clipboard...`r`n") $clipboardText = Get-ClipboardRaw } catch { $currentLog.AppendText("Failed to read clipboard: $($_.Exception.Message)`r`n") return } if ([string]::IsNullOrEmpty($clipboardText)) { $currentLog.AppendText("Clipboard is empty. Nothing to type.`r`n") return } $currentLog.AppendText("Read: $clipboardText`r`n") $currentLog.AppendText("Minimizing window and typing...`r`n") $currentForm.WindowState = 'Minimized' Start-Sleep -Seconds 2 Type-ScancodeText -text $clipboardText $currentForm.WindowState = 'Normal' $currentLog.AppendText("Done typing.`r`n") }.GetNewClosure()) $form.Controls.Add($pasteButton) $y += 30 $logBox.Location = New-Object System.Drawing.Point($padding, ($y + 10)) $form.Controls.Add($logBox) $form.ShowDialog() | Out-Null } try { $inp = Get-ClipboardRaw -ErrorAction Stop } catch { $inp = "" } Show-TextWithCopyButtons -InputText $inp