src/Public/findstr.ps1

function findstr {
    <#
    .SYNOPSIS
        Wrapper for findstr.exe providing case-insensitive colored matches and /v (invert) support.
    .DESCRIPTION
        Supports the /v switch to invert matches (show lines that do NOT contain the pattern) for both piped input and file searches.
    .PARAMETER pattern
        Pattern to search (first non-switch argument). Additional arguments are passed to findstr.exe when not using piped input.
    .EXAMPLE
        findstr error *.log
    .EXAMPLE
        Get-Content app.log | findstr timeout
    .EXAMPLE
        Get-Content app.log | findstr /v error
    .EXAMPLE
        findstr /v error *.log
    #>

    # Separate switches (starting with /) from other arguments
    $switches = @()
    $others   = @()
    foreach ($a in $args) {
        if ($a -is [string] -and $a.StartsWith('/')) { $switches += $a } else { $others += $a }
    }

    if (-not $others) { return }
    $pattern = $others[0]
    $invert = $switches | Where-Object { $_ -match '^/v$' } | ForEach-Object { $true } | Select-Object -First 1
    if (-not $invert) { $invert = $false }

    if ($input) {
        $prevEncoding = [Console]::OutputEncoding
        try {
            [Console]::OutputEncoding = [System.Text.UTF8Encoding]::UTF8
            $lines = if ($invert) {
                $input | Where-Object { $_ -notmatch $pattern }
            }
            else {
                $input | Where-Object { $_ -match $pattern }
            }
            $lines | ForEach-Object {
                # Repair common UTF-8 mojibake for ellipsis
                $line = $_ -replace '…', '…' -replace 'ΓǪ', '…'
                if (-not $invert) {
                    $line -replace "(?i)($pattern)", "$([char]27)[91m`$1$([char]27)[0m"
                }
                else {
                    $line
                }
            }
        }
        finally {
            [Console]::OutputEncoding = $prevEncoding
        }
    }
    else {
        # Build argument list for external findstr.exe
        $cmdArgs = @()
        # Ensure case-insensitive by default (existing behavior) unless user explicitly specified /I or /i
        if (-not ($switches -contains '/I' -or $switches -contains '/i')) { $cmdArgs += '/I' }
        $cmdArgs += $switches
        $cmdArgs += $others
        & findstr.exe $cmdArgs | ForEach-Object {
            if (-not $invert) {
                $_ -replace "(?i)($pattern)", "$([char]27)[91m`$1$([char]27)[0m"
            }
            else {
                $_
            }
        }
    }
}