Viscalyx.Common.psm1

#Region '.\prefix.ps1' -1

$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common'
Import-Module -Name $script:dscResourceCommonModulePath

$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'
#EndRegion '.\prefix.ps1' 5
#Region '.\Private\Get-DiffString.ps1' -1

<#
    .SYNOPSIS
        Returns the difference between two byte collections as a formatted string.
 
    .DESCRIPTION
        The Get-DiffString function takes two byte collections, a reference byte
        collection and a difference byte collection, and returns the difference
        between them as a formatted string. The formatted string represents the
        differences between the two byte collections in a human-readable format.
 
    .PARAMETER Reference
        Specifies the reference byte collection. This parameter is mandatory.
 
    .PARAMETER Difference
        Specifies the difference byte collection. This parameter is mandatory.
 
    .PARAMETER Ansi
        Specifies the ANSI color code to apply to the differences in the formatted
        string.
 
    .PARAMETER AnsiReset
        Specifies the ANSI color code to reset the color in the formatted string.
 
    .PARAMETER Column1Width
        Specifies the width of the first column in the formatted string.
 
    .PARAMETER ColumnSeparatorWidth
        Specifies the width of the separator between the columns in the formatted
        string.
 
    .OUTPUTS
        System.String
 
        The formatted string representing the differences between the reference
        and difference byte collections.
 
    .EXAMPLE
        $reference = [Microsoft.PowerShell.Commands.ByteCollection]::new()
        $difference = [Microsoft.PowerShell.Commands.ByteCollection]::new()
        $diffString = Get-DiffString -Reference $reference -Difference $difference
 
        Returns a formatted string representing the differences between the reference
        and difference byte collections.
#>

function Get-DiffString
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [Microsoft.PowerShell.Commands.ByteCollection]
        $Reference,

        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [Microsoft.PowerShell.Commands.ByteCollection]
        $Difference,

        [Parameter()]
        [System.String]
        $Ansi = '30;31m',

        [Parameter()]
        [System.String]
        $AnsiReset = '0m',

        [Parameter()]
        [System.Int32]
        $Column1Width,

        [Parameter()]
        [System.Int32]
        $ColumnSeparatorWidth
    )

    if ($Column1Width -eq 0)
    {
        $Column1Width = $Reference.HexBytes.Length
    }

    if ($ColumnSeparatorWidth -eq 0)
    {
        $ColumnSeparatorWidth = 2
    }

    $convertToDiffStringDefaultParameters = @{
        Ansi      = $Ansi
        AnsiReset = $AnsiReset
    }

    $diffIndex = 0..($Reference.Bytes.Length - 1) |
        Where-Object -FilterScript {
            $Reference.Bytes[$_] -ne @($Difference.Bytes)[$_]
        }

    $rowHexArray = -split $Reference.HexBytes

    $diffIndex |
        ForEach-Object -Process {
            $hexValue = $rowHexArray[$_]

            $rowHexArray[$_] = ConvertTo-DiffString -InputString $hexValue @convertToDiffStringDefaultParameters
        }

    $byteCollectionString = ($rowHexArray -join ' ') + (' ' * ($Column1Width - $Reference.HexBytes.Length))

    $byteCollectionString += ' ' * $ColumnSeparatorWidth

    $byteCollectionString += $diffIndex |
        Get-NumericalSequence |
        ConvertTo-DiffString -InputString $Reference.Ascii @convertToDiffStringDefaultParameters

    return $byteCollectionString
}
#EndRegion '.\Private\Get-DiffString.ps1' 116
#Region '.\Public\ConvertTo-DiffString.ps1' -1

<#
    .SYNOPSIS
        Converts a specified portion of a string to a diff string with ANSI color
        codes.
 
    .DESCRIPTION
        The ConvertTo-DiffString function converts a specified portion of a string
        to a diff string with ANSI color codes. It can be used to highlight differences
        in text or display changes in a visually appealing way.
 
    .PARAMETER IndexObject
        Specifies the input object containing the start and end indices of the
        portion to convert. This parameter is mandatory when using pipeline input.
 
    .PARAMETER InputString
        Specifies the input string to convert. This parameter is mandatory when
        using start and end indices or when only the input string is provided.
 
    .PARAMETER StartIndex
        Specifies the start index of the portion to convert. This parameter is
        mandatory when using start and end indices.
 
    .PARAMETER EndIndex
        Specifies the end index of the portion to convert. This parameter is
        optional when using start and end indices. If not provided, only one
        character will be converted specified by the start index.
 
    .PARAMETER Ansi
        Specifies the ANSI color code to apply to the converted portion. The
        default value is '30;43m', which represents black text on a yellow
        background.
 
    .PARAMETER AnsiReset
        Specifies the ANSI color code to reset the color after the converted portion.
        The default value is '0m', which resets the color to the default.
 
    .EXAMPLE
        PS> ConvertTo-DiffString -InputString "Hello, world!" -StartIndex 7 -EndIndex 11
 
        Converts the portion of the input string from index 7 to index 11 to a diff
        string with the default ANSI color codes.
 
    .EXAMPLE
        PS> @{Start = 7; End = 11 } | ConvertTo-DiffString -InputString "Hello, world!"
 
        Converts the portion of the input string from index 7 to index 11, provided
        through pipeline input, to a diff string with the default ANSI color codes.
 
    .NOTES
        This function uses ANSI escape sequences to apply color codes to the converted
        portion of the string. The resulting diff string can be displayed in a console
        or terminal that supports ANSI color codes.
#>

function ConvertTo-DiffString
{
    [CmdletBinding()]
    param
    (
        [Parameter(ParameterSetName = 'PipelineInput', Mandatory = $true, ValueFromPipeline = $true)]
        [PSCustomObject]
        $IndexObject,

        [Parameter(ParameterSetName = 'StartEndInput', Mandatory = $true)]
        [Parameter(ParameterSetName = 'PipelineInput', Mandatory = $true)]
        [Parameter(ParameterSetName = 'InputStringOnly', Mandatory = $true)]
        [System.String]
        $InputString,

        [Parameter(ParameterSetName = 'StartEndInput', Mandatory = $true)]
        [ValidateScript({ $_ -lt $InputString.Length })]
        [System.UInt32]
        $StartIndex,

        [Parameter(ParameterSetName = 'StartEndInput')]
        [ValidateScript({ $_ -lt $InputString.Length })]
        [Nullable[System.UInt32]]
        $EndIndex,

        [Parameter()]
        [System.String]
        $Ansi = '30;43m',

        [Parameter()]
        [System.String]
        $AnsiReset = '0m'
    )

    begin
    {
        $result = @()
        $previousIndex = 0

        $ansiSequence = "`e[$Ansi"
        $resetSequence = "`e[$AnsiReset"
    }

    process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            'InputStringOnly'
            {
                $start = 0
                $end = $InputString.Length - 1
            }

            'PipelineInput'
            {
                $start = $IndexObject.Start
                $end = $IndexObject.End
            }

            'StartEndInput'
            {
                $start = $StartIndex
                $end = $EndIndex
            }
        }

        if ($null -eq $end)
        {
            $end = $start
        }

        if ($start -gt $previousIndex)
        {
            $result += $InputString.Substring($previousIndex, $start - $previousIndex)
        }

        $result += ($ansiSequence + $InputString.Substring($start, $end - $start + 1) + $resetSequence)

        $previousIndex = $end + 1
    }

    end
    {
        if ($previousIndex -lt $InputString.Length)
        {
            $result += $InputString.Substring($previousIndex)
        }

        $result -join ''
    }
}
#EndRegion '.\Public\ConvertTo-DiffString.ps1' 145
#Region '.\Public\Get-NumericalSequence.ps1' -1

<#
    .SYNOPSIS
        Retrieves numerical sequences from a given set of numbers.
 
    .DESCRIPTION
        The Get-NumericalSequence command retrieves numerical sequences from a given
        set of numbers. It identifies consecutive numbers and groups them into ranges.
 
    .PARAMETER Number
        Specifies the number to be processed. This parameter is mandatory and can be
        provided via the pipeline.
 
    .OUTPUTS
        System.Object[]
 
        An array of PSCustomObject objects representing the numerical sequences.
        Each object contains the Start and End properties, indicating the start
        and end numbers of a sequence.
 
    .EXAMPLE
        Get-NumericalSequence -Number 1, 2, 3, 5, 6, 7, 10
 
        Returns:
        Start End
        ----- ---
        1 3
        5 7
        10
#>

function Get-NumericalSequence
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.Int32]
        $Number
    )

    begin
    {
        $ranges = @()
        $start = $null
        $end = $null
    }

    process
    {
        if ($null -eq $start)
        {
            $start = $Number
            $end = $Number
        }
        elseif ($Number -eq $end + 1)
        {
            $end = $Number
        }
        else
        {
            if ($start -eq $end)
            {
                $end = $null
            }

            $ranges += [PSCustomObject] @{
                Start = $start
                End = $end
            }

            $start = $Number
            $end = $Number
        }
    }

    end
    {
        if ($null -ne $start)
        {
            if ($start -eq $end)
            {
                $end = $null
            }

            $ranges += [PSCustomObject] @{
                Start = $start
                End = $end
            }
        }

        $ranges
    }
}
#EndRegion '.\Public\Get-NumericalSequence.ps1' 93
#Region '.\Public\Out-Diff.ps1' -1

<#
    .SYNOPSIS
        Compares two sets of strings and highlights the differences in hexadecimal
        output.
 
    .DESCRIPTION
        The Out-Diff command compares two sets of strings, $Difference and $Reference.
        Outputs the result using hexadecimal output and highlights the differences
        between the two sets of strings in a side-by-side format, making it easier
        to spot discrepancies at a byte level.
 
        Its main intended use is in unit tests when comparing large text masses that
        can have small, normally invisible differences, such as an extra or missing
        line feed or new line character.
 
        The command supports ANSI escape sequences for coloring the differences
        between the actual and expected strings. The default color for differences
        is red. The command defaults to outputting the result as informational
        messages, but it also supports displaying the differences using the
        Write-Verbose cmdlet or returning the result as output.
 
    .PARAMETER Difference
        Specifies the set of strings to compare against the reference strings.
        This parameter is mandatory.
 
    .PARAMETER Reference
        Specifies the set of reference strings to compare against the difference
        strings. This parameter is mandatory.
 
    .PARAMETER DifferenceAnsi
        Specifies the ANSI escape sequence for controlling how the difference is
        highlighted. The default value is '30;31m' (red).
 
    .PARAMETER AnsiReset
        Specifies the ANSI escape sequence to reset the formatting. The
        default value is '0m'.
 
    .PARAMETER DiffIndicator
        Specifies the indicator to display when there is a differences between the
        strings. The default value is '!='.
 
    .PARAMETER EqualIndicator
        Specifies the indicator to display when there is equality between the strings.
        The default value is '!='.
 
    .PARAMETER AsVerbose
        Switch parameter. If specified, the differences are displayed using the
        Write-Verbose cmdlet.
 
    .PARAMETER NoHeader
        Switch parameter. If specified, the header message is not displayed.
 
    .PARAMETER ReferenceLabel
        Specifies the label for the reference strings. The default value is
        'Expected:'. The label should be no longer than 65 characters.
 
    .PARAMETER DifferenceLabel
        Specifies the label for the difference strings. The default value is
        'But was:'. The label should be no longer than 65 characters.
 
    .PARAMETER ReferenceLabelAnsi
        Specifies the ANSI escape sequence for controlling the look of the reference
        label. The default value is '4m' (underline).
 
    .PARAMETER DifferenceLabelAnsi
        Specifies the ANSI escape sequence for controlling the look of the difference
        label. The default value is '4m' (underline).
 
    .PARAMETER PassThru
        Switch parameter. If specified, the output is returned as an array of strings.
 
    .OUTPUTS
        If the PassThru parameter is specified, the function returns an array of
        strings representing the differences between the actual and expected strings.
 
    .EXAMPLE
        $actual = "Hello", "World"
        $expected = "Hello", "Universe"
        Out-Diff -Difference $actual -Reference $expected
 
        This example compares the actual strings "Hello" and "World" with the expected
        strings "Hello" and "Universe". The function displays the differences between
        the two sets of strings.
#>

function Out-Diff
{
    # cSpell: ignore isnot
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [AllowNull()]
        [System.String[]]
        $Difference,

        [Parameter(Mandatory = $true)]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [AllowNull()]
        [System.String[]]
        $Reference,

        [Parameter()]
        [System.String]
        $DifferenceAnsi = '30;31m', # '30;43m' or '38;5;(255);48;5;(124)m'

        [Parameter()]
        [System.String]
        $AnsiReset = '0m',

        [Parameter()]
        [System.String]
        $DiffIndicator = '!=',

        [Parameter()]
        [System.String]
        $EqualIndicator = '==',

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $AsVerbose,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $NoHeader,

        [Parameter()]
        [ValidateScript({ $_.Length -le 65 })]
        [System.String]
        $ReferenceLabel = 'Expected:',

        [Parameter()]
        [ValidateScript({ $_.Length -le 65 })]
        [System.String]
        $DifferenceLabel = 'But was:',

        [Parameter()]
        [System.String]
        $ReferenceLabelAnsi = '4m',

        [Parameter()]
        [System.String]
        $DifferenceLabelAnsi = '4m',

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $PassThru
    )

    if ($PassThru.IsPresent)
    {
        $outDiffResult = @()
    }
    else
    {
        $outDiffResult = $null
    }

    if (-not $Difference)
    {
        $Difference = @()
    }

    if (-not $Reference)
    {
        $Reference = @()
    }

    # Using ForEach-Object to convert numerical values to string.
    $expectedHex = $Reference | ForEach-Object -Process { $_.ToString() } | Format-Hex
    $actualHex = $Difference | ForEach-Object -Process { $_.ToString() } | Format-Hex

    $maxLength = @($expectedHex.Length, $actualHex.Length) |
        Measure-Object -Maximum |
        Select-Object -ExpandProperty 'Maximum'

    $columnSeparatorWidth = 2

    if ($expectedHex)
    {
        $expectedColumn1Width = (
            $expectedHex.HexBytes |
                ForEach-Object -Process {
                    $_.Length
                } |
                Measure-Object -Maximum
        ).Maximum

        $expectedColumn2Width = (
            $expectedHex.Ascii |
                ForEach-Object -Process {
                    $_.Length
                } |
                Measure-Object -Maximum
        ).Maximum
    }
    else
    {
        $expectedColumn1Width = 47
        $expectedColumn2Width = 16
    }

    if (@($actualHex)[0])
    {
        $actualColumn1Width = (
            $actualHex.HexBytes |
                ForEach-Object -Process {
                    $_.Length
                } |
                Measure-Object -Maximum
        ).Maximum
    }
    else
    {
        $actualColumn1Width = 47
    }

    if (-not $NoHeader.IsPresent)
    {
        $headerMessage = @(
            (ConvertTo-DiffString -InputString $ReferenceLabel -Ansi $ReferenceLabelAnsi -AnsiReset $AnsiReset)
            ''.PadRight((($expectedColumn1Width + $expectedColumn2Width - $ReferenceLabel.Length) + ($columnSeparatorWidth * 3) + $DiffIndicator.Length))
            (ConvertTo-DiffString -InputString $DifferenceLabel -Ansi $DifferenceLabelAnsi -AnsiReset $AnsiReset)
        ) -join ''

        if ($PassThru.IsPresent)
        {
            $outDiffResult += $headerMessage
        }
        elseif ($AsVerbose.IsPresent)
        {
            Write-Verbose -Message $headerMessage -Verbose
        }
        else
        {
            Write-Information -MessageData $headerMessage -InformationAction 'Continue'
        }
    }

    # Remove one since we start at 0.
    $maxLength -= 1

    foreach ($index in 0..$maxLength)
    {
        $expectedRowAscii = ''
        $actualRowAscii = ''

        if (@($expectedHex)[$index])
        {
            $expectedRowAscii = $expectedHex[$index].Ascii
        }

        if (@($actualHex)[$index])
        {
            $actualRowAscii = $actualHex[$index].Ascii
        }

        ##
        # Color mark the diff in the actual row.
        ##

        if ($actualRowAscii)
        {
            $getDiffStringParameters = @{
                Reference            = @($actualHex)[$index]
                Difference           = @($expectedHex)[$index]
                Column1Width         = $actualColumn1Width
                ColumnSeparatorWidth = $columnSeparatorWidth
                Ansi                 = $DifferenceAnsi
                AnsiReset            = $AnsiReset
            }

            $actualRow = Get-DiffString @getDiffStringParameters
        }
        else
        {
            $actualRow = ''
        }

        ##
        # Color mark the diff in the expected row.
        ##

        if ($expectedRowAscii)
        {
            $getDiffStringParameters = @{
                Reference            = @($expectedHex)[$index]
                Difference           = @($actualHex)[$index]
                Column1Width         = $expectedColumn1Width
                ColumnSeparatorWidth = $columnSeparatorWidth
                Ansi                 = $DifferenceAnsi
                AnsiReset            = $AnsiReset
            }

            $expectedRow = Get-DiffString @getDiffStringParameters
        }
        else
        {
            $expectedRow = ''
        }

        <#
            Calculate the difference in length between the expected row and calculated
            column width by removing all the ANSI escape sequences and subtracting the
            length from the column widths.
        #>

        $expectedRowLengthDiff = ($expectedColumn1Width + $expectedColumn2Width) - ($expectedRow -replace "`e\[[0-9;]*[a-zA-Z]").Length

        $expectedRow = $expectedRow.PadRight($expectedRow.Length + $expectedRowLengthDiff + $columnSeparatorWidth)

        if ($expectedRowAscii -cne $actualRowAscii)
        {
            $outputDiffIndicator = $DiffIndicator
        }
        else
        {
            if ($EqualIndicator)
            {
                $outputDiffIndicator = $EqualIndicator
            }
            else
            {
                $outputDiffIndicator = ' ' * $DiffIndicator.Length
            }
        }

        $diffRowMessage = @(
            $expectedRow
            (' ' * $columnSeparatorWidth)
            $outputDiffIndicator
            (' ' * $columnSeparatorWidth)
            $actualRow
        ) -join ''

        if ($PassThru.IsPresent)
        {
            $outDiffResult += $diffRowMessage
        }
        elseif ($AsVerbose.IsPresent)
        {
            Write-Verbose -Message $diffRowMessage -Verbose
        }
        else
        {
            Write-Information -MessageData $diffRowMessage -InformationAction 'Continue'
        }
    }

    return $outDiffResult
}
#EndRegion '.\Public\Out-Diff.ps1' 353
#Region '.\Public\Remove-History.ps1' -1

<#
    .SYNOPSIS
        Removes command history entries that match a specified pattern.
 
    .DESCRIPTION
        The Remove-History function removes command history entries that match a
        specified pattern. It removes both the history entries stored by the
        PSReadLine module and the history entries stored by the PowerShell session.
 
    .PARAMETER Pattern
        Specifies the pattern to match against the command history entries. Only
        the entries that match the pattern will be removed.
 
    .PARAMETER EscapeRegularExpression
        Indicates that the pattern should be treated as a literal string. If this
        switch parameter is specified, the pattern will not be treated as a regular
        expression.
 
    .INPUTS
        None. You cannot pipe input to this function.
 
    .OUTPUTS
        None. The function does not generate any output.
 
    .EXAMPLE
        Remove-History -Pattern ".*\.txt"
 
        This example removes all command history entries that end with the ".txt"
        extension, using a regular expression pattern.
 
    .EXAMPLE
        Remove-History -Pattern './build.ps1' -EscapeRegularExpression
 
        This example removes all command history entries that contain the string
        "./build.ps1".
#>

function Remove-History
{
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Because ShouldProcess is handled in the commands it calls')]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [System.String]
        $Pattern,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $EscapeRegularExpression
    )

    Remove-PSReadLineHistory @PSBoundParameters
    Remove-PSHistory @PSBoundParameters
}
#EndRegion '.\Public\Remove-History.ps1' 55
#Region '.\Public\Remove-PSHistory.ps1' -1

<#
    .SYNOPSIS
        Removes PowerShell history content matching a specified pattern.
 
    .DESCRIPTION
        The Remove-PSHistory function removes PowerShell history content that matches
        a specified pattern.
 
    .PARAMETER Pattern
        Specifies the pattern to match against the command history entries. Only
        the entries that match the pattern will be removed.
 
    .PARAMETER EscapeRegularExpression
        Indicates that the pattern should be treated as a literal string. If this
        switch parameter is specified, the pattern will not be treated as a regular
        expression.
 
    .EXAMPLE
        Remove-PSHistory -Pattern ".*\.txt"
 
        This example removes all command history entries that end with the ".txt"
        extension, using a regular expression pattern.
 
    .EXAMPLE
        Remove-PSHistory -Pattern './build.ps1' -EscapeRegularExpression
 
        This example removes all command history entries that contain the string
        "./build.ps1".
 
    .INPUTS
        None. You cannot pipe input to this function.
 
    .OUTPUTS
        None. The function does not generate any output.
#>

function Remove-PSHistory
{
    [CmdletBinding(SupportsShouldProcess = $true , ConfirmImpact = 'High')]
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [System.String]
        $Pattern,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $EscapeRegularExpression
    )

    if ($EscapeRegularExpression.IsPresent)
    {
        $Pattern = [System.Text.RegularExpressions.Regex]::Escape($Pattern)
    }

    $historyContent = Get-History

    $matchingLines = $historyContent |
        Where-Object -FilterScript {
            $_.CommandLine -match $Pattern
        }

    if ($matchingLines)
    {
        $matchingLines | Write-Verbose -Verbose

        $shouldProcessVerboseDescription = 'Removing content matching the pattern ''{0}''.' -f $Pattern
        $shouldProcessVerboseWarning = 'Are you sure you want to remove the content matching the pattern ''{0}'' from PowerShell history?' -f $Pattern
        # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages.
        $shouldProcessCaption = 'Remove content matching the pattern from PowerShell history'

        if ($PSCmdlet.ShouldProcess($shouldProcessVerboseDescription, $shouldProcessVerboseWarning, $shouldProcessCaption))
        {
            $matchingLines |
                ForEach-Object -Process {
                    Clear-History -Id $_.Id
                }

            Write-Information -MessageData 'Removed PowerShell history content matching the pattern.' -InformationAction Continue
        }
    }
    else
    {
        Write-Information -MessageData 'No PowerShell history content matching the pattern.' -InformationAction Continue
    }
}
#EndRegion '.\Public\Remove-PSHistory.ps1' 86
#Region '.\Public\Remove-PSReadLineHistory.ps1' -1

<#
    .SYNOPSIS
        Removes content from the PSReadLine history that matches a specified pattern.
 
    .DESCRIPTION
        The Remove-PSReadLineHistory function removes content from the PSReadLine
        history that matches a specified pattern.
 
    .PARAMETER Pattern
        Specifies the pattern to match against the command history entries. Only
        the entries that match the pattern will be removed.
 
    .PARAMETER EscapeRegularExpression
        Indicates that the pattern should be treated as a literal string. If this
        switch parameter is specified, the pattern will not be treated as a regular
        expression.
 
    .NOTES
        - This command requires the PSReadLine module to be installed.
        - The PSReadLine history is stored in a file specified by the HistorySavePath
          property of the PSReadLineOption object.
 
    .EXAMPLE
        Remove-PSReadLineHistory -Pattern ".*\.txt"
 
        This example removes all command history entries that end with the ".txt"
        extension, using a regular expression pattern.
 
    .EXAMPLE
        Remove-PSReadLineHistory -Pattern './build.ps1' -EscapeRegularExpression
 
        This example removes all command history entries that contain the string
        "./build.ps1".
 
    .INPUTS
        None. You cannot pipe input to this function.
 
    .OUTPUTS
        None. The function does not generate any output.
#>


function Remove-PSReadLineHistory
{
    [CmdletBinding(SupportsShouldProcess = $true , ConfirmImpact = 'High')]
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [System.String]
        $Pattern,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $EscapeRegularExpression
    )

    if ($EscapeRegularExpression.IsPresent)
    {
        $Pattern = [System.Text.RegularExpressions.Regex]::Escape($Pattern)
    }

    $historyPath = (Get-PSReadLineOption).HistorySavePath

    $historyContent = Get-Content -Path $historyPath

    # Do not match the last line as it is the line that called the function.
    $matchingContent = $historyContent |
        Select-Object -SkipLast 1 |
        Select-String -Pattern $Pattern

    if ($matchingContent)
    {
        $matchingContent | Write-Verbose -Verbose

        $shouldProcessVerboseDescription = 'Removing content matching the pattern ''{0}''.' -f $Pattern
        $shouldProcessVerboseWarning = 'Are you sure you want to remove the content matching the pattern ''{0}'' from PSReadLine history?' -f $Pattern
        # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages.
        $shouldProcessCaption = 'Remove content matching the pattern from PSReadLine history'

        if ($PSCmdlet.ShouldProcess($shouldProcessVerboseDescription, $shouldProcessVerboseWarning, $shouldProcessCaption))
        {
            Set-Content -Path $historyPath -Value (
                $historyContent |
                    Select-String -NotMatch $Pattern
            ).Line

            Write-Information -MessageData 'Removed PSReadLine history content matching the pattern.' -InformationAction Continue
        }
    }
    else
    {
        Write-Information -MessageData 'No PSReadLine history content matching the pattern.' -InformationAction Continue
    }
}
#EndRegion '.\Public\Remove-PSReadLineHistory.ps1' 94
#Region '.\Public\Split-StringAtIndex.ps1' -1

<#
    .SYNOPSIS
        Splits a string at a specified index or range of indices.
 
    .DESCRIPTION
        The Split-StringAtIndex function splits a given string at a specified index
        or range of indices. It can be used to extract substrings from a larger
        string based on the provided indices.
 
    .PARAMETER IndexObject
        Specifies the index object to split the string. This parameter is used
        when providing input via the pipeline.
 
    .PARAMETER InputString
        Specifies the input string to be split.
 
    .PARAMETER StartIndex
        Specifies the starting index of the substring to be extracted. The value
        must be less than the length of the input string.
 
    .PARAMETER EndIndex
        Specifies the ending index of the substring to be extracted. The value
        must be less than the length of the input string.
 
    .EXAMPLE
        PS> Split-StringAtIndex -InputString "Hello, World!" -StartIndex 0 -EndIndex 4
 
        This example splits the input string "Hello, World!" at the index specified
        by StartIndex and then at the index specified by EndIndex and returns the
        resulting array of substrings.
 
    .EXAMPLE
        PS> @(@{Start = 0; End = 2}, @{Start = 7; End = 11 }) | Split-StringAtIndex -InputString "Hello, world!"
 
        This example splits the input string "Hello, World!" at the indices provided
        by the pipeline. It will split the string at each StartIndex and EndIndex
        and returns the resulting array of substrings.
 
    .EXAMPLE
        PS> @(0, 1, 2, 7, 8, 9, 10, 11) | Get-NumericalSequence | Split-StringAtIndex -InputString "Hello, world!"
 
        This example splits the input string "Hello, World!" at the indices provided
        by the pipeline. It will split the string at each StartIndex and EndIndex
        and returns the resulting array of substrings.
 
    .OUTPUTS
        System.String[]
 
        An array of substrings extracted from the input string.
 
    .NOTES
        The Split-StringAtIndex function is designed to split strings based on indices
        and can be used in various scenarios where string manipulation is required.
        To get the indices the function Get-NumericalSequence can be used.
#>

function Split-StringAtIndex
{
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples are syntactically correct. The rule does not seem to understand that there is pipeline input.')]
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(ParameterSetName = 'PipelineInput', Mandatory = $true, ValueFromPipeline = $true)]
        [PSCustomObject]
        $IndexObject,

        [Parameter(ParameterSetName = 'StartEndInput', Mandatory = $true)]
        [Parameter(ParameterSetName = 'PipelineInput', Mandatory = $true)]
        [System.String]
        $InputString,

        [Parameter(ParameterSetName = 'StartEndInput', Mandatory = $true)]
        [ValidateScript({ $_ -lt $InputString.Length })]
        [System.UInt32]
        $StartIndex,

        [Parameter(ParameterSetName = 'StartEndInput', Mandatory = $true)]
        [ValidateScript({ $_ -lt $InputString.Length })]
        [System.UInt32]
        $EndIndex
    )

    begin
    {
        $result = @()
        $previousIndex = 0
    }

    process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            'PipelineInput'
            {
                $start = $IndexObject.Start
                $end = $IndexObject.End
            }

            'StartEndInput'
            {
                $start = $StartIndex
                $end = $EndIndex
            }
        }

        if ($null -eq $end)
        {
            $end = $start
        }

        if ($start -gt $previousIndex)
        {
            $result += $InputString.Substring($previousIndex, $start - $previousIndex)
        }

        $result += $InputString.Substring($start, $end - $start + 1)

        $previousIndex = $end + 1
    }

    end
    {
        if ($previousIndex -lt $InputString.Length)
        {
            $result += $InputString.Substring($previousIndex)
        }

        $result
    }
}
#EndRegion '.\Public\Split-StringAtIndex.ps1' 131