Merc.psm1

function Invoke-Merc {
    <#
    .SYNOPSIS
        Interactively kill processes using fzf, with window titles for context.
 
    .DESCRIPTION
        Lists all processes with their start time, PID, name, and associated window titles.
        Uses fzf for fuzzy selection (multi-select with Tab). Selected processes are killed.
 
        Requires fzf to be installed and available in PATH.
 
    .PARAMETER IncludeJunk
        Include junk/system windows (Default IME, MSCTFIME UI, etc.) in the display.
 
    .EXAMPLE
        Invoke-Merc
        # Then type "teams" or "gluk" to filter, Tab to select multiple, Enter to kill
 
    .EXAMPLE
        Invoke-Merc -IncludeJunk
        # Include junk/system windows in the list
 
    .EXAMPLE
        merc -j
        # Using the alias with short parameter
 
    .LINK
        https://github.com/junegunn/fzf
    #>


    [CmdletBinding()]
    [Alias('merc')]
    param(
        [Alias('j', 'Junk')]
        [switch]$IncludeJunk
    )

    # Use Win32 API to enumerate all visible windows
    Add-Type -ErrorAction SilentlyContinue @"
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
 
public class MercWindowEnumerator {
    [DllImport("user32.dll")]
    private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
 
    [DllImport("user32.dll")]
    private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
 
    [DllImport("user32.dll")]
    private static extern int GetWindowTextLength(IntPtr hWnd);
 
    [DllImport("user32.dll")]
    private static extern bool IsWindowVisible(IntPtr hWnd);
 
    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
 
    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
 
    public static Dictionary<uint, List<string>> GetWindowsByProcess() {
        var result = new Dictionary<uint, List<string>>();
 
        EnumWindows((hWnd, lParam) => {
            if (IsWindowVisible(hWnd)) {
                int length = GetWindowTextLength(hWnd);
                if (length > 0) {
                    StringBuilder sb = new StringBuilder(length + 1);
                    GetWindowText(hWnd, sb, sb.Capacity);
                    string title = sb.ToString();
 
                    if (!string.IsNullOrWhiteSpace(title)) {
                        uint pid;
                        GetWindowThreadProcessId(hWnd, out pid);
 
                        if (!result.ContainsKey(pid)) {
                            result[pid] = new List<string>();
                        }
                        result[pid].Add(title);
                    }
                }
            }
            return true;
        }, IntPtr.Zero);
 
        return result;
    }
}
"@


    # Get all windows grouped by PID
    $windowsByPid = [MercWindowEnumerator]::GetWindowsByProcess()

    # Junk window titles to filter out
    $junkTitles = @(
        'Default IME',
        'MSCTFIME UI',
        'DesktopWindowXamlSource',
        'Program Manager',
        'Battery Meter'
    )

    Get-Process |
        Where-Object { $null -ne $_.StartTime } |
        ForEach-Object {
            $proc = $_
            $windows = ""

            # Look up windows for this PID
            if ($windowsByPid.ContainsKey([uint32]$proc.Id)) {
                $titles = $windowsByPid[[uint32]$proc.Id] |
                    Where-Object { $_ -and ($IncludeJunk -or ($_ -notin $junkTitles)) } |
                    Select-Object -Unique
                $windows = $titles -join " | "
            }

            # Truncate long window titles
            if ($windows.Length -gt 80) {
                $windows = $windows.Substring(0, 77) + "..."
            }

            [PSCustomObject]@{
                StartTime   = $proc.StartTime
                Id          = $proc.Id
                ProcessName = $proc.ProcessName
                Windows     = $windows
            }
        } |
        Sort-Object StartTime |
        ForEach-Object {
            $time = $_.StartTime.ToString("yyyy-MM-dd HH:mm:ss")
            $display = "{0} {1,6} {2,-25} {3}" -f $time, $_.Id, $_.ProcessName, $_.Windows
            # Embed the PID at the start with a delimiter for easy extraction
            "$($_.Id)`t$display"
        } |
        fzf -m --with-nth=2.. --delimiter="`t" --header="Select processes to kill (Tab=multi, Enter=confirm)" |
        ForEach-Object {
            $procId = $_.Split("`t")[0]
            $procName = (Get-Process -Id $procId -ErrorAction SilentlyContinue).ProcessName
            Write-Host "Killing $procName (PID $procId)..." -ForegroundColor Yellow
            Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue
        }
}