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 '.\Public\ConvertTo-AnsiSequence.ps1' -1

<#
    .SYNOPSIS
        Converts a string value to an ANSI escape sequence.
 
    .DESCRIPTION
        The ConvertTo-AnsiSequence command converts a string value to an ANSI escape
        sequence. It is used to format text with ANSI escape codes for color and
        formatting in console output.
 
    .PARAMETER Value
        The string value to be converted to an ANSI escape sequence.
 
    .INPUTS
        System.String
 
    .OUTPUTS
        System.String
 
    .EXAMPLE
        ConvertTo-AnsiSequence -Value "31"
 
        This example converts the string value "31" to its ANSI escape sequence.
 
    .NOTES
        This function supports ANSI escape codes for color and formatting. It checks
        if the input string matches the pattern of an ANSI escape sequence and
        converts it accordingly. If the input string does not match the pattern,
        it is returned as is.
#>

function ConvertTo-AnsiSequence
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [AllowEmptyString()]
        [AllowNull()]
        [System.String]
        $Value
    )

    if ($Value)
    {
        if ($Value -match "^(?:`e)?\[?([0-9;]+)m?$")
        {
            $Value = "`e[" + $Matches[1] + 'm'
        }
    }

    return $Value
}
#EndRegion '.\Public\ConvertTo-AnsiSequence.ps1' 53
#Region '.\Public\ConvertTo-DifferenceString.ps1' -1

<#
    .SYNOPSIS
        Converts two strings into a difference string, highlighting the differences
        between them.
 
    .DESCRIPTION
        The ConvertTo-DifferenceString command takes two strings, a reference string
        and a difference string, and converts them into a difference string that
        highlights the differences between the two strings. The function compares
        the byte values of each character in the strings and outputs the differences
        in a formatted manner. It supports customizing the indicators, labels, colors,
        and encoding type.
 
    .PARAMETER ReferenceString
        Specifies the reference string to compare against.
 
    .PARAMETER DifferenceString
        Specifies the difference string to compare.
 
    .PARAMETER EqualIndicator
        Specifies the indicator to use for equal bytes. Default is '=='.
 
    .PARAMETER NotEqualIndicator
        Specifies the indicator to use for not equal bytes. Default is '!='.
 
    .PARAMETER HighlightStart
        Specifies the ANSI escape sequence to start highlighting. Default is
        "[31m" (red color).
 
    .PARAMETER HighlightEnd
        Specifies the ANSI escape sequence to end highlighting. Default is
        "[0m" (reset color).
 
    .PARAMETER ReferenceLabel
        Specifies the label for the reference string. Default is 'Expected:'.
 
    .PARAMETER DifferenceLabel
        Specifies the label for the difference string. Default is 'But was:'.
 
    .PARAMETER NoColumnHeader
        Specifies whether to exclude the column header from the output.
 
    .PARAMETER NoLabels
        Specifies whether to exclude the labels from the output.
 
    .PARAMETER ReferenceLabelAnsi
        Specifies the ANSI escape sequence to apply to the reference label.
 
    .PARAMETER DifferenceLabelAnsi
        Specifies the ANSI escape sequence to apply to the difference label.
 
    .PARAMETER ColumnHeaderAnsi
        Specifies the ANSI escape sequence to apply to the column header.
 
    .PARAMETER ColumnHeaderResetAnsi
        Specifies the ANSI escape sequence to reset the column header.
 
    .PARAMETER EncodingType
        Specifies the encoding type to use for converting the strings to byte arrays.
        Default is 'UTF8'.
 
    .EXAMPLE
        PS> ConvertTo-DifferenceString -ReferenceString 'Hello' -DifferenceString 'Hallo'
 
        Expected: But was:
        ---------------------------------------------------------------- --------------------------------------------------------
        Bytes Ascii Bytes Ascii
        ----- ----- ----- -----
        48 65 6C 6C 6F Hello == 48 61 6C 6C 6F Hallo
 
        Converts the reference string 'Hello' and the difference string 'Hallo'
        into a difference string, highlighting the differences.
 
    .INPUTS
        None.
 
    .OUTPUTS
        System.String.
#>

function ConvertTo-DifferenceString
{
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseBOMForUnicodeEncodedFile', '', Justification = 'This file is not intended to be saved as a Unicode-encoded file even though it has unicode characters, if that is a problem that can be re-evaluated.')]
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [System.String]
        $ReferenceString,

        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [System.String]
        $DifferenceString,

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

        [Parameter()]
        [ValidateLength(0, 2)]
        [System.String]
        $NotEqualIndicator = '!=',

        [Parameter()]
        [System.String]
        $HighlightStart = '[31m', # Default to red color

        [Parameter()]
        [System.String]
        $HighlightEnd = '[0m', # Default to reset color

        [Parameter()]
        [System.String]
        $ReferenceLabel = 'Expected:',

        [Parameter()]
        [System.String]
        $DifferenceLabel = 'But was:',

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

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

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

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

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

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

        [Parameter()]
        [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')]
        [System.String]
        $EncodingType = 'UTF8'
    )

    $HighlightStart = ConvertTo-AnsiSequence -Value $HighlightStart
    $HighlightEnd = ConvertTo-AnsiSequence -Value $HighlightEnd
    $ReferenceLabelAnsi = ConvertTo-AnsiSequence -Value $ReferenceLabelAnsi
    $DifferenceLabelAnsi = ConvertTo-AnsiSequence -Value $DifferenceLabelAnsi
    $ColumnHeaderAnsi = ConvertTo-AnsiSequence -Value $ColumnHeaderAnsi
    $ColumnHeaderResetAnsi = ConvertTo-AnsiSequence -Value $ColumnHeaderResetAnsi

    # Handle empty string or single character indicator
    $NotEqualIndicator = $NotEqualIndicator.PadRight(2)
    $EqualIndicator = $EqualIndicator.PadRight(2)

    # Convert the strings to byte arrays using the specified encoding
    $referenceBytes = ([System.Text.Encoding]::$EncodingType).GetBytes($ReferenceString)
    $differenceBytes = ([System.Text.Encoding]::$EncodingType).GetBytes($DifferenceString)

    # Determine the maximum length of the two byte arrays
    $maxLength = [Math]::Max($referenceBytes.Length, $differenceBytes.Length)

    # Initialize arrays to hold hex values and characters
    $refHexArray = @()
    $refCharArray = @()
    $diffHexArray = @()
    $diffCharArray = @()

    # Escape $HighlightStart and $HighlightEnd for regex matching
    $escapedHighlightStart = [regex]::Escape($HighlightStart)
    $escapedHighlightEnd = [regex]::Escape($HighlightEnd)

    # Output the labels if NoLabels is not specified
    if (-not $NoLabels)
    {
        "$($ReferenceLabelAnsi)$($ReferenceLabel)$($HighlightEnd) $($DifferenceLabelAnsi)$($DifferenceLabel)$($HighlightEnd)"
        ('-' * 64) + (' ' * 8) + ('-' * 64) # Output a line of dashes under the labels
    }

    # Output the column header once with dashes underline if NoColumnHeader is not specified
    if (-not $NoColumnHeader)
    {
        "$($ColumnHeaderAnsi)Bytes Ascii Bytes Ascii$($ColumnHeaderResetAnsi)"
        "$($ColumnHeaderAnsi)----- ----- ----- -----$($ColumnHeaderResetAnsi)"
    }

    # Loop through each byte in the arrays up to the maximum length
    for ($i = 0; $i -lt $maxLength; $i++)
    {
        # Get the byte and character for the reference string
        if ($i -lt $referenceBytes.Length)
        {
            $refByte = $referenceBytes[$i]
            $refHex = '{0:X2}' -f $refByte
            $refChar = [char]$refByte
        }
        else
        {
            $refHex = ' '
            $refChar = ' '
        }

        # Get the byte and character for the difference string
        if ($i -lt $differenceBytes.Length)
        {
            $diffByte = $differenceBytes[$i]
            $diffHex = '{0:X2}' -f $diffByte
            $diffChar = [char]$diffByte
        }
        else
        {
            $diffHex = ' '
            $diffChar = ' '
        }

        # Highlight differences
        if ($refHex -ne $diffHex)
        {
            $refHex = "$($HighlightStart)$refHex$($HighlightEnd)"
            $refChar = "$($HighlightStart)$refChar$($HighlightEnd)"
            $diffHex = "$($HighlightStart)$diffHex$($HighlightEnd)"
            $diffChar = "$($HighlightStart)$diffChar$($HighlightEnd)"
        }

        # Replace control characters with their Unicode representations in the output
        $refChar = $refChar`
            -replace "`0", '␀' `
            -replace "`a", '␇' `
            -replace "`b", '␈' `
            -replace "`t", '␉' `
            -replace "`f", '␌' `
            -replace "`r", '␍' `
            -replace "`n", '␊' `
            -replace "(?!$($escapedHighlightStart))(?!$($escapedHighlightEnd))`e", '␛'

        $diffChar = $diffChar `
            -replace "`0", '␀' `
            -replace "`a", '␇' `
            -replace "`b", '␈' `
            -replace "`t", '␉' `
            -replace "`f", '␌' `
            -replace "`r", '␍' `
            -replace "`n", '␊' `
            -replace "(?!$($escapedHighlightStart))(?!$($escapedHighlightEnd))`e", '␛'

        # Add to arrays
        $refHexArray += $refHex
        $refCharArray += $refChar
        $diffHexArray += $diffHex
        $diffCharArray += $diffChar

        # Output the results in groups of 16
        if (($i + 1) % 16 -eq 0 -or $i -eq $maxLength - 1)
        {
            # Pad arrays to ensure they have 16 elements
            while ($refHexArray.Count -lt 16)
            {
                $refHexArray += ' '
            }
            while ($refCharArray.Count -lt 16)
            {
                $refCharArray += ' '
            }
            while ($diffHexArray.Count -lt 16)
            {
                $diffHexArray += ' '
            }
            while ($diffCharArray.Count -lt 16)
            {
                $diffCharArray += ' '
            }

            $refHexLine = ($refHexArray -join ' ')
            $refCharLine = ($refCharArray -join '')
            $diffHexLine = ($diffHexArray -join ' ')
            $diffCharLine = ($diffCharArray -join '')

            # Determine if the line was highlighted
            $indicator = if ($refHexLine -match $escapedHighlightStart -or $diffHexLine -match $escapedHighlightStart)
            {
                $NotEqualIndicator
            }
            else
            {
                $EqualIndicator
            }

            # Output the results in the specified format
            '{0} {1} {2} {3} {4}' -f $refHexLine, $refCharLine, $indicator, $diffHexLine, $diffCharLine

            # Clear arrays for the next group of 16
            $refHexArray = @()
            $refCharArray = @()
            $diffHexArray = @()
            $diffCharArray = @()
        }
    }
}
#EndRegion '.\Public\ConvertTo-DifferenceString.ps1' 307
#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\Get-PSReadLineHistory.ps1' -1

<#
    .SYNOPSIS
        Retrieves the PSReadLine history content.
 
    .DESCRIPTION
        The Get-PSReadLineHistory function retrieves the content of the PSReadLine
        history file. By default, it returns the entire history content, but you
        can specify a pattern to filter the results.
 
    .PARAMETER Pattern
        Specifies a pattern to filter the history content. Only lines matching the
        pattern will be returned.
 
    .EXAMPLE
        Get-PSReadLineHistory
 
        Returns the entire content of the PSReadLine history file.
 
    .EXAMPLE
        Get-PSReadLineHistory -Pattern "git"
 
        Returns only the lines from the PSReadLine history file that contain the word "git".
 
    .INPUTS
        None
 
    .OUTPUTS
        System.String
 
    .NOTES
        This function requires the PSReadLine module to be installed.
 
    .LINK
        https://docs.microsoft.com/en-us/powershell/module/psreadline/
#>

function Get-PSReadLineHistory
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position = 0)]
        [System.String]
        $Pattern
    )

    $historyPath = (Get-PSReadLineOption).HistorySavePath

    $historyContent = Get-Content -Path $historyPath

    if ($Pattern)
    {
        $historyContent = $historyContent |
            Select-Object -SkipLast 1 |
            Select-String -Pattern $Pattern -Raw
    }

    $historyContent
}
#EndRegion '.\Public\Get-PSReadLineHistory.ps1' 59
#Region '.\Public\New-SamplerGitHubReleaseTag.ps1' -1

<#
    .SYNOPSIS
        Creates a new GitHub release tag for the Sampler project.
 
    .DESCRIPTION
        The New-SamplerGitHubReleaseTag function creates a new release tag for the
        Sampler project on GitHub. It performs the following steps:
 
        1. Checks if the remote specified in $UpstreamRemoteName exists locally and throws an error if it doesn't.
        2. Fetches the $DefaultBranchName branch from the $UpstreamRemoteName remote and throws an error if it doesn't exist.
        3. Checks out the $DefaultBranchName branch.
        4. Fetches the $DefaultBranchName branch from the $UpstreamRemoteName remote.
        5. Rebases the local $DefaultBranchName branch with the $UpstreamRemoteName/$DefaultBranchName branch.
        6. Gets the last commit ID of the $DefaultBranchName branch.
        7. Fetches tags from the $UpstreamRemoteName remote.
        8. If no release tag is specified, it checks if there are any tags in the local repository and selects the latest preview tag.
        9. Creates a new tag with the specified release tag or based on the latest preview tag.
        10. Optionally pushes the tag to the $UpstreamRemoteName remote.
        11. Switches back to the previous branch if requested.
 
    .PARAMETER DefaultBranchName
        Specifies the name of the default branch. Default value is 'main'.
 
    .PARAMETER UpstreamRemoteName
        Specifies the name of the upstream remote. Default value is 'origin'.
 
    .PARAMETER ReleaseTag
        Specifies the release tag to create. Must be in the format 'vX.X.X'. If
        not specified, the latest preview tag will be used.
 
    .PARAMETER SwitchBackToPreviousBranch
        Specifies that the command should switches back to the previous branch after
        creating the release tag.
 
    .PARAMETER Force
        Specifies that the command should run without prompting for confirmation.
 
    .PARAMETER PushTag
        Specifies that the tag should also be pushed to the upstream remote after
        creating it. This will always ask for confirmation before pushing the tag,
        unless Force is also specified.
 
    .EXAMPLE
        New-SamplerGitHubReleaseTag -ReleaseTag 'v1.0.0' -PushTag
 
        Creates a new release tag with the specified tag 'v1.0.0' and pushes it
        to the 'origin' remote.
 
    .EXAMPLE
        New-SamplerGitHubReleaseTag -SwitchBackToPreviousBranch
 
        Creates a new release tag and switches back to the previous branch.
 
    .NOTES
        This function requires Git to be installed and accessible from the command
        line.
#>

function New-SamplerGitHubReleaseTag
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param
    (
        [Parameter()]
        [System.String]
        $DefaultBranchName = 'main',

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

        [Parameter()]
        [System.String]
        [ValidatePattern('^v\d+\.\d+\.\d+$')]
        $ReleaseTag,

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

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

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

    if ($Force.IsPresent -and -not $Confirm)
    {
        $ConfirmPreference = 'None'
    }

    # Check if the remote specified in $UpstreamRemoteName exists locally and throw an error if it doesn't.
    $remoteExists = git remote | Where-Object -FilterScript { $_ -eq $UpstreamRemoteName }

    if (-not $remoteExists)
    {
        $PSCmdlet.ThrowTerminatingError(
            [System.Management.Automation.ErrorRecord]::new(
                ($script:localizedData.New_SamplerGitHubReleaseTag_RemoteMissing -f $UpstreamRemoteName),
                'NSGRT0001', # cspell: disable-line
                [System.Management.Automation.ErrorCategory]::ObjectNotFound,
                $DatabaseName
            )
        )
    }

    $verboseDescriptionMessage = $script:localizedData.New_SamplerGitHubReleaseTag_FetchUpstream_ShouldProcessVerboseDescription -f $DefaultBranchName, $UpstreamRemoteName
    $verboseWarningMessage = $script:localizedData.New_SamplerGitHubReleaseTag_FetchUpstream_ShouldProcessVerboseWarning -f $DefaultBranchName, $UpstreamRemoteName
    $captionMessage = $script:localizedData.New_SamplerGitHubReleaseTag_FetchUpstream_ShouldProcessCaption

    if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage))
    {
        # Fetch $DefaultBranchName from upstream and throw an error if it doesn't exist.
        git fetch $UpstreamRemoteName $DefaultBranchName

        if ($LASTEXITCODE -ne 0) # cSpell: ignore LASTEXITCODE
        {
            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    ($script:localizedData.New_SamplerGitHubReleaseTag_FailedFetchBranchFromRemote -f $DefaultBranchName, $UpstreamRemoteName),
                    'NSGRT0002', # cspell: disable-line
                    [System.Management.Automation.ErrorCategory]::InvalidOperation,
                    $DatabaseName
                )
            )
        }
    }

    if ($SwitchBackToPreviousBranch.IsPresent)
    {
        $currentLocalBranchName = git rev-parse --abbrev-ref HEAD

        if ($LASTEXITCODE -ne 0)
        {
            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    $script:localizedData.New_SamplerGitHubReleaseTag_FailedGetLocalBranchName,
                    'NSGRT0003', # cspell: disable-line
                    [System.Management.Automation.ErrorCategory]::InvalidOperation,
                    $DatabaseName
                )
            )
        }
    }

    $continueProcessing = $true
    $errorMessage = $null

    $verboseDescriptionMessage = $script:localizedData.New_SamplerGitHubReleaseTag_Rebase_ShouldProcessVerboseDescription -f $DefaultBranchName, $UpstreamRemoteName
    $verboseWarningMessage = $script:localizedData.New_SamplerGitHubReleaseTag_Rebase_ShouldProcessVerboseWarning -f $DefaultBranchName
    $captionMessage = $script:localizedData.New_SamplerGitHubReleaseTag_Rebase_ShouldProcessCaption

    if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage))
    {
        git checkout $DefaultBranchName

        if ($LASTEXITCODE -ne 0)
        {
            $continueProcessing = $false
            $errorMessage = $script:localizedData.New_SamplerGitHubReleaseTag_FailedCheckoutLocalBranch -f $DefaultBranchName
            $errorCode = 'NSGRT0004' # cspell: disable-line
        }

        $switchedToDefaultBranch = $true

        if ($continueProcessing)
        {
            git rebase $UpstreamRemoteName/$DefaultBranchName

            if ($LASTEXITCODE -ne 0)
            {
                $continueProcessing = $false
                $errorMessage = $script:localizedData.New_SamplerGitHubReleaseTag_FailedRebaseLocalDefaultBranch -f $DefaultBranchName, $UpstreamRemoteName
                $errorCode = 'NSGRT0005' # cspell: disable-line
            }

            if ($continueProcessing)
            {
                $headCommitId = git rev-parse HEAD

                if ($LASTEXITCODE -ne 0)
                {
                    $continueProcessing = $false
                    $errorMessage = $script:localizedData.New_SamplerGitHubReleaseTag_FailedGetLastCommitId -f $DefaultBranchName
                    $errorCode = 'NSGRT0006' # cspell: disable-line
                }
            }
        }

        if (-not $continueProcessing)
        {
            # If something failed, revert back to the previous branch if requested.
            if ($SwitchBackToPreviousBranch.IsPresent -and $switchedToDefaultBranch)
            {
                git checkout $currentLocalBranchName
            }

            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    $errorMessage,
                    $errorCode, # cspell: disable-line
                    [System.Management.Automation.ErrorCategory]::InvalidOperation,
                    $DatabaseName
                )
            )
        }
    }

    $verboseDescriptionMessage = $script:localizedData.New_SamplerGitHubReleaseTag_UpstreamTags_ShouldProcessVerboseDescription -f $UpstreamRemoteName
    $verboseWarningMessage = $script:localizedData.New_SamplerGitHubReleaseTag_UpstreamTags_ShouldProcessVerboseWarning -f $UpstreamRemoteName
    $captionMessage = $script:localizedData.New_SamplerGitHubReleaseTag_UpstreamTags_ShouldProcessCaption

    if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage))
    {
        git fetch $UpstreamRemoteName --tags

        if ($LASTEXITCODE -ne 0)
        {
            if ($SwitchBackToPreviousBranch.IsPresent -and $switchedToDefaultBranch)
            {
                git checkout $currentLocalBranchName
            }

            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    ($script:localizedData.New_SamplerGitHubReleaseTag_FailedFetchTagsFromUpstreamRemote -f $UpstreamRemoteName),
                    'NSGRT0007', # cspell: disable-line
                    [System.Management.Automation.ErrorCategory]::InvalidOperation,
                    $DatabaseName
                )
            )
        }
    }

    if (-not $ReleaseTag)
    {
        $tagExist = git tag | Select-Object -First 1

        if ($LASTEXITCODE -ne 0 -or -not $tagExist)
        {
            $continueProcessing = $false
            $errorMessage = $script:localizedData.New_SamplerGitHubReleaseTag_FailedGetTagsOrMissingTagsInLocalRepository
            $errorCode = 'NSGRT0008' # cspell: disable-line
        }

        if ($continueProcessing)
        {
            $latestPreviewTag = git describe --tags --abbrev=0

            if ($LASTEXITCODE -ne 0)
            {
                $continueProcessing = $false
                $errorMessage = $script:localizedData.New_SamplerGitHubReleaseTag_FailedDescribeTags
                $errorCode = 'NSGRT0009' # cspell: disable-line
            }

            if ($continueProcessing)
            {
                $isCorrectlyFormattedPreviewTag = $latestPreviewTag -match '^(v\d+\.\d+\.\d+)-.*'

                if ($isCorrectlyFormattedPreviewTag)
                {
                    $ReleaseTag = $matches[1]
                }
                else
                {
                    $continueProcessing = $false
                    $errorMessage = $script:localizedData.New_SamplerGitHubReleaseTag_LatestTagIsNotPreview -f $latestPreviewTag
                    $errorCode = 'NSGRT0010' # cspell: disable-line
                }
            }
        }

        if (-not $continueProcessing)
        {
            if ($SwitchBackToPreviousBranch.IsPresent -and $switchedToDefaultBranch)
            {
                git checkout $currentLocalBranchName
            }

            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    $errorMessage,
                    $errorCode, # cspell: disable-line
                    [System.Management.Automation.ErrorCategory]::InvalidOperation,
                    $DatabaseName
                )
            )
        }
    }

    if ($WhatIfPreference)
    {
        $messageShouldProcess = $script:localizedData.New_SamplerGitHubReleaseTag_NewTagWhatIf_ShouldProcessVerboseDescription
    }
    else
    {
        $messageShouldProcess = $script:localizedData.New_SamplerGitHubReleaseTag_NewTag_ShouldProcessVerboseDescription
    }

    $verboseDescriptionMessage = $messageShouldProcess -f $ReleaseTag, $DefaultBranchName, $headCommitId
    $verboseWarningMessage = $script:localizedData.New_SamplerGitHubReleaseTag_NewTag_ShouldProcessVerboseWarning -f $ReleaseTag
    $captionMessage = $script:localizedData.New_SamplerGitHubReleaseTag_NewTag_ShouldProcessCaption

    if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage))
    {
        git tag $ReleaseTag

        if ($PushTag -and ($Force -or $PSCmdlet.ShouldContinue(('Do you want to push the tags to the upstream ''{0}''?' -f $UpstreamRemoteName), 'Confirm')))
        {
            git push origin --tags

            Write-Information -MessageData ("`e[32mTag `e[1;37;44m{0}`e[0m`e[32m was created and pushed to upstream '{1}'`e[0m" -f $ReleaseTag, $UpstreamRemoteName) -InformationAction Continue
        }
        else
        {
            # cSpell: disable-next-line
            Write-Information -MessageData ("`e[32mTag `e[1;37;44m{0}`e[0m`e[32m was created. To push the tag to upstream, run `e[1;37;44mgit push {1} --tags`e[0m`e[32m.`e[0m" -f $ReleaseTag, $UpstreamRemoteName) -InformationAction Continue
        }
    }

    if ($SwitchBackToPreviousBranch.IsPresent)
    {
        $verboseDescriptionMessage = $script:localizedData.New_SamplerGitHubReleaseTag_SwitchBack_ShouldProcessVerboseDescription -f $currentLocalBranchName
        $verboseWarningMessage = $script:localizedData.New_SamplerGitHubReleaseTag_SwitchBack_ShouldProcessVerboseWarning -f $currentLocalBranchName
        $captionMessage = $script:localizedData.New_SamplerGitHubReleaseTag_SwitchBack_ShouldProcessCaption

        if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage))
        {
            git checkout $currentLocalBranchName

            if ($LASTEXITCODE -ne 0)
            {
                $PSCmdlet.ThrowTerminatingError(
                    [System.Management.Automation.ErrorRecord]::new(
                        ($script:localizedData.New_SamplerGitHubReleaseTag_FailedCheckoutPreviousBranch -f $currentLocalBranchName),
                        'NSGRT0011', # cspell: disable-line
                        [System.Management.Automation.ErrorCategory]::InvalidOperation,
                        $DatabaseName
                    )
                )
            }
        }
    }
}
#EndRegion '.\Public\New-SamplerGitHubReleaseTag.ps1' 348
#Region '.\Public\Out-Difference.ps1' -1

<#
    .SYNOPSIS
        Compares two sets of strings and converts them into a difference string.
 
    .DESCRIPTION
        The Out-Difference function compares two sets of strings, Reference and
        Difference, and converts them into a difference string. It provides options
        to customize the indicators, labels, and formatting of the output.
 
    .PARAMETER Reference
        Specifies the reference set of strings to compare.
 
    .PARAMETER Difference
        Specifies the difference set of strings to compare.
 
    .PARAMETER EqualIndicator
        Specifies the indicator to use for equal strings.
 
    .PARAMETER NotEqualIndicator
        Specifies the indicator to use for unequal strings.
 
    .PARAMETER HighlightStart
        Specifies the starting indicator for highlighting differences.
 
    .PARAMETER HighlightEnd
        Specifies the ending indicator for highlighting differences.
 
    .PARAMETER ReferenceLabel
        Specifies the label for the reference set.
 
    .PARAMETER DifferenceLabel
        Specifies the label for the difference set.
 
    .PARAMETER NoColumnHeader
        Indicates whether to exclude the column header from the output.
 
    .PARAMETER NoLabels
        Indicates whether to exclude the labels from the output.
 
    .PARAMETER ReferenceLabelAnsi
        Specifies the ANSI escape sequence for the reference label.
 
    .PARAMETER DifferenceLabelAnsi
        Specifies the ANSI escape sequence for the difference label.
 
    .PARAMETER ColumnHeaderAnsi
        Specifies the ANSI escape sequence for the column header.
 
    .PARAMETER ColumnHeaderResetAnsi
        Specifies the ANSI escape sequence to reset the column header formatting.
 
    .PARAMETER EncodingType
        Specifies the encoding type to use for converting the strings to byte arrays.
 
    .PARAMETER ConcatenateArray
        Indicates whether to concatenate the arrays of strings into a single string.
 
    .PARAMETER ConcatenateChar
        Specifies the character used to concatenate the strings. Default is a new line character.
 
    .EXAMPLE
        $reference = "apple", "banana", "cherry"
        $difference = "apple", "orange", "cherry"
        Out-Difference -Reference $reference -Difference $difference -EqualIndicator '' -ReferenceLabel 'Reference:' -DifferenceLabel 'Difference:' -ConcatenateArray -ConcatenateChar ''
 
    .INPUTS
        None. You cannot pipe input to this function.
 
    .OUTPUTS
        System.String. The difference string representing the comparison between
        the reference and difference sets.
 
    .NOTES
        This command is using the default parameters values from the ConvertTo-DifferenceString
        command.
#>

function Out-Difference
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [AllowNull()]
        [AllowEmptyCollection()]
        [System.String[]]
        $Reference,

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

        [Parameter()]
        [ValidateLength(0, 2)]
        [System.String]
        $EqualIndicator,

        [Parameter()]
        [ValidateLength(0, 2)]
        [System.String]
        $NotEqualIndicator,

        [Parameter()]
        [System.String]
        $HighlightStart,

        [Parameter()]
        [System.String]
        $HighlightEnd,

        [Parameter()]
        [System.String]
        $ReferenceLabel,

        [Parameter()]
        [System.String]
        $DifferenceLabel,

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

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

        [Parameter()]
        [System.String]
        $ReferenceLabelAnsi,

        [Parameter()]
        [System.String]
        $DifferenceLabelAnsi,

        [Parameter()]
        [System.String]
        $ColumnHeaderAnsi,

        [Parameter()]
        [System.String]
        $ColumnHeaderResetAnsi,

        [Parameter()]
        [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')]
        [System.String]
        $EncodingType,

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

        [Parameter()]
        [System.String]
        $ConcatenateChar = [System.Environment]::NewLine
    )

    if ($null -eq $ConcatenateChar)
    {
        $ConcatenateChar = ''
    }

    $behaviorParameters = @{} + $PSBoundParameters
    $behaviorParameters.Remove('Reference')
    $behaviorParameters.Remove('Difference')
    $behaviorParameters.Remove('ConcatenateArray')
    $behaviorParameters.Remove('ConcatenateChar')

    if ($ConcatenateArray.IsPresent)
    {
        # Handle null values by converting them to empty strings
        if ($null -eq $Reference)
        {
            $refString = ''
        }
        else
        {
            $refString = $Reference -join $ConcatenateChar
        }

        if ($null -eq $Difference)
        {
            $diffString = ''
        }
        else
        {
            $diffString = $Difference -join $ConcatenateChar
        }

        ConvertTo-DifferenceString -ReferenceString $refString -DifferenceString $diffString @behaviorParameters
    }
    else
    {
        for ($i = 0; $i -lt [Math]::Max($Reference.Length, $Difference.Length); $i++)
        {
            $refString = if ($i -lt $Reference.Length)
            {
                $Reference[$i]
            }
            else
            {
                ''
            }

            $diffString = if ($i -lt $Difference.Length)
            {
                $Difference[$i]
            }
            else
            {
                ''
            }

            ConvertTo-DifferenceString -ReferenceString $refString -DifferenceString $diffString @behaviorParameters
        }
    }
}
#EndRegion '.\Public\Out-Difference.ps1' 220
#Region '.\Public\Pop-VMLatestSnapshot.ps1' -1

<#
    .SYNOPSIS
        Sets the latest snapshot of a virtual machine and starts it.
 
    .DESCRIPTION
        The Pop-VMLatestSnapShot command sets the latest snapshot of a virtual
        machine specified by the $ServerName parameter and starts it.
 
    .PARAMETER ServerName
        Specifies the name of the server for which to set the latest snapshot.
 
    .EXAMPLE
        Pop-VMLatestSnapShot -ServerName 'VM1'
 
        Sets the latest snapshot of the virtual machine named "VM1" and starts it.
#>

function Pop-VMLatestSnapShot
{
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ServerName
    )

    Get-VM -Name $ServerName |
        Get-Snapshot | # TODO: Should this not be Get-VMSnapshot?
        Where-Object -FilterScript {
            $_.IsCurrent -eq $true
        } |
        Set-VM -VM $ServerName | # TODO: Is -VM necessary?
        Start-VM
}
#EndRegion '.\Public\Pop-VMLatestSnapshot.ps1' 34
#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