hGrep.psm1

Function Select-MatchedString {
[Alias('hGrep')]
<#
    .SYNOPSIS
        A grep-like tool with a color highlighting feature.
 
    .DESCRIPTION
        This function searches for text patterns in input strings.
        If input is an object, it is converted to a string prior to processing.
        Matched characters are highlighted.
         
        '(some command) | Select-MatchedString -Pattern <regex>'
        is similar to
        '(some command) | Output-String -Stream | Select-String -Pattern <regex> -AllMatch -CaseSensitive'
        with PowerShell 7.X, in which Select-String cmdlet has a highlighting feature.
 
    .PARAMETER Pattern
        Specifies the text patterns to find. Type a string or regular expression.
        If you type a string, use the SimpleMatch parameter.
 
    .PARAMETER BackgroundColor
        Specifies the background color for matches. (Alias: -bc)
        The default value is "Blue".
 
    .PARAMETER CapturegroupColor
        Specifies the foreground color for capture-group matches. (Alias: -cc)
        The default value is "Red".
 
    .PARAMETER ForegroundColor
        Specifies the foreground color for matches. (Alias: -fc)
        The default value is "White".
 
    .PARAMETER Group
        Specifies the name or number of capture group. (Alias: -g)
        The default value is "0".
 
    .PARAMETER ECMAScript
        Enables ECMAScript-compliant behavior. (Alias: -e)
 
    .PARAMETER IgnoreCase
        Makes matches case-insensitive. By default, matches are case-sensitive. (Alias: -i)
 
    .PARAMETER InputObject
        Specifies the text to be searched. (Alias: -io)
 
    .PARAMETER PassThru
        Outputs all lines, including ones that do not match. (Alias: -p)
 
    .PARAMETER Narrow
        Converts wide characters into narrow ones internally. (Alias: -n)
        Useful when you don't want to distinguish between narrow and wide characters.
 
    .PARAMETER SimpleMatch
        Uses a simple match rather than a regular expression match. (Alias: -s)
 
    .NOTES
        Author: earthdiver1
        Version: V1.03
        Licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
 
#>

    Param(
        [Parameter(Mandatory=$True)][String]$Pattern,
        [Alias("g")][String]$Group                    = "0",
        [Alias("e")][Switch]$ECMAScript,
        [Alias("i")][Switch]$IgnoreCase,
        [Alias("w")][Switch]$Narrow,
        [Alias("n")][Switch]$Number,
        [Alias("p")][Switch]$PassThru,
        [Alias("s")][Switch]$SimpleMatch,
        [Alias("bc")][ConsoleColor]$BackgroundColor   = "Blue",
        [Alias("cc")][ConsoleColor]$CapturegroupColor = "Red",
        [Alias("fc")][ConsoleColor]$ForegroundColor   = "White",

        [Parameter(ValueFromPipeline=$True)][Alias("io")][PSObject]$InputObject
    )
    Begin {
        try {
            if ( -not $Pattern ) { break }
            if ( $Narrow ) {
                Add-Type -AssemblyName "Microsoft.VisualBasic"
                $Pattern = [Microsoft.VisualBasic.Strings]::StrConv($Pattern,[Microsoft.VisualBasic.VbStrConv]::Narrow)
            }
            if ( $SimpleMatch ) { $Pattern = [regex]::Escape( $Pattern ) }
            if ( $Number ) {
                $line = 0
                $width  = $host.UI.RawUI.BufferSize.Width - 7
            } else {
                $width  = $host.UI.RawUI.BufferSize.Width - 1
            }
            $regexOptions = "Compiled"
            if ( $ECMAScript ) { $regexOptions += ", ECMAScript" }
            if ( $IgnoreCase ) { $regexOptions += ", IgnoreCase" }
            $Regex = New-Object Text.RegularExpressions.Regex $Pattern, $regexOptions
            $process_block = {
                Process {
                    $line++
                    $i = 0
                    if ( $Narrow ) { $_ = [Microsoft.VisualBasic.Strings]::StrConv($_,[Microsoft.VisualBasic.VbStrConv]::Narrow) }
                    $match = $Regex.Match($_,$i)
                    $m = $match.Groups[$Group]
                    if (-not $PassThru -and -not $m.Success) { return }
                    if ( $Number ) { Write-Host $("{0,5}:" -F $line) -NoNewline }
                    if ( $Group -eq "0" ) {
                        while ($m.Success) {
                            if ( $m.Index -ge $_.Length ) { break }
                            if ( $m.Length -gt 0 ) {
                                Write-Host $_.SubString($i, $m.Index - $i) -NoNewline
                                Write-Host $m.Value -BackgroundColor $BackgroundColor -ForegroundColor $ForegroundColor -NoNewline 
                                $i = $m.Index + $m.Length
                            } else {
                                Write-Host $_.SubString($i, $m.Index - $i + 1) -NoNewline
                                $i = $m.Index + 1
                            }
                            $m = $Regex.Match($_,$i).Groups[0]
                        }
                    } else {
                        while ( $m.Success ) {
                            if ( $m.Index -ge $_.Length ) { break }
                            $m0 = $match.Groups[0]
                            if ( $m0.Length -gt 0 ) {
                                Write-Host $_.SubString($i, $m0.Index - $i) -NoNewline
                                Write-Host $_.SubString($m0.Index, $m.Index - $m0.Index) `
                                           -BackgroundColor $BackgroundColor -ForegroundColor $ForegroundColor -NoNewline
                                Write-Host $m.Value -BackgroundColor $BackgroundColor -ForegroundColor $CapturegroupColor -NoNewline 
                                $i  = $m0.Index + $m0.Length
                                $ii = $m.Index  + $m.Length
                                Write-Host $_.SubString($ii, $i - $ii) `
                                           -BackgroundColor $BackgroundColor -ForegroundColor $ForegroundColor -NoNewline
                            } else {
                                Write-Host $_.SubString($i, $m0.Index - $i + 1) -NoNewline
                                $i = $m0.Index + 1
                            }
                            $match = $Regex.Match($_,$i)
                            $m  = $match.Groups[$Group]
                        }
                    }
                    Write-Host $_.SubString($i)
                }
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Out-String',[System.Management.Automation.CommandTypes]::Cmdlet)
            $wrappedCmdParameters = @{}
            if ( $PSBoundParameters.ContainsKey("InputObject") ) { $wrappedCmdParameters.Add("InputObject",$InputObject) }
            $wrappedCmdParameters.Add("Stream", $True)
            $wrappedCmdParameters.Add("Width", $width)
            $scriptCmd = {& $wrappedCmd @wrappedCmdParameters | & $process_block }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }
    Process {
        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }
    End {
        try {
            $steppablePipeline.End()
            Remove-Variable Regex
        } catch {
            throw
        }
    }
}
Export-ModuleMember -Function Select-MatchedString -Alias hGrep