# A script to toggle the Touch Keyboard of Windows 11,
# compatible with both Windows PowerShell and PowerShell 7.
# Based on code by @torvin (https://stackoverflow.com/users/332528/torvin): https://stackoverflow.com/a/40921638
# Based on code by @Andrea S. (https://stackoverflow.com/users/5887913/andrea-s): https://stackoverflow.com/a/55513524

# Warning: Relies on undocumented behaviour of the Windows Shell
# and may break with any update.
# Last tested on Windows 11 Home 22000.978.

Add-Type -ReferencedAssemblies $(if ($PSVersionTable.PSEdition -eq "Desktop") {"System.Drawing.dll"} else {$null}) -Language CSharp -TypeDefinition @'
using System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;

public class TouchKeyboardController
{
    public static void ToggleTouchKeyboard()
    {
        try
        {
            UIHostNoLaunch uiHostNoLaunch = new UIHostNoLaunch();
            ((ITipInvocation)uiHostNoLaunch).Toggle(GetDesktopWindow());
            Marshal.ReleaseComObject(uiHostNoLaunch);
        }
        catch (COMException exc) 
        {
            if (exc.HResult == unchecked((int)0x80040154)) // REGDB_E_CLASSNOTREG
            {
                ProcessStartInfo processStartInfo = new ProcessStartInfo("TabTip.exe")
                {
                    UseShellExecute = true
                };
                using (Process process = Process.Start(processStartInfo))
                {
                }
            }
            else
            {
                throw;
            }
        }
    }

    [ComImport, Guid("4ce576fa-83dc-4F88-951c-9d0782b4e376")]
    class UIHostNoLaunch
    {
    }

    [ComImport, Guid("37c994e7-432b-4834-a2f7-dce1f13b834b")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface ITipInvocation
    {
        void Toggle(IntPtr hwnd);
    }

    [DllImport("user32.dll", SetLastError = false)]
    static extern IntPtr GetDesktopWindow();


    public static bool IsInputPaneOpen()
    {
        FrameworkInputPane frameworkInputPane = new FrameworkInputPane();
        Rectangle rect;
        ((IFrameworkInputPane)frameworkInputPane).Location(out rect);
        Marshal.ReleaseComObject(frameworkInputPane);
        return !rect.IsEmpty;
    }

    [ComImport, Guid("d5120aa3-46ba-44c5-822d-ca8092c1fc72")]
    public class FrameworkInputPane
    {
    }

    [ComImport, Guid("5752238b-24f0-495a-82f1-2fd593056796")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IFrameworkInputPane
    {
        int Advise([MarshalAs(UnmanagedType.IUnknown)] object pWindow, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
        int AdviseWithHWND(IntPtr hwnd, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
        int Unadvise(int pdwCookie);
        int Location(out Rectangle prcInputPaneScreenLocation);
    }
}
'@

# Toggle the Touch Keyboard regardless of whether it is currently shown or not.
#[TouchKeyboardController]::ToggleTouchKeyboard()

# Alternatively, if you only want to show the Touch Keyboard
# and not hide it if it is already active:
if (-not [TouchKeyboardController]::IsInputPaneOpen()) {
    [TouchKeyboardController]::ToggleTouchKeyboard()
}