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

<#
    .SYNOPSIS
        Converts an absolute path to a relative path.
 
    .DESCRIPTION
        The ConvertTo-RelativePath command takes an absolute path and converts it
        to a relative path based on the current location. If the absolute path
        starts with the current location, the function removes the current location
        from the beginning of the path and inserts a '.' to indicate the relative path.
 
    .PARAMETER AbsolutePath
        Specifies the absolute path that needs to be converted to a relative path.
 
    .PARAMETER CurrentLocation
        Specifies the current location used as a reference for converting the absolute
        path to a relative path. If not specified, the function uses the current
        location obtained from Get-Location.
 
    .EXAMPLE
        ConvertTo-RelativePath -AbsolutePath '/source/Viscalyx.Common/source/Public/ConvertTo-RelativePath.ps1' -CurrentLocation "/source/Viscalyx.Common"
 
        Returns "./source/Public/ConvertTo-RelativePath.ps1", which is the
        relative path of the given absolute path based on the current location.
 
    .INPUTS
        [System.String]
 
    .OUTPUTS
        [System.String]
#>

function ConvertTo-RelativePath
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [System.String]
        $AbsolutePath,

        [Parameter(Position = 1)]
        [System.String]
        $CurrentLocation
    )

    begin
    {
        if (-not $PSBoundParameters.ContainsKey('CurrentLocation'))
        {
            $CurrentLocation = (Get-Location).Path
        }
    }

    process
    {
        $relativePath = $AbsolutePath

        if ($relativePath.StartsWith($CurrentLocation))
        {
            $relativePath = $relativePath.Substring($CurrentLocation.Length).Insert(0, '.')
        }

        return $relativePath
    }
}
#EndRegion '.\Public\ConvertTo-RelativePath.ps1' 66
#Region '.\Public\Get-ModuleVersion.ps1' -1

<#
    .SYNOPSIS
        Retrieves the version of a PowerShell module.
 
    .DESCRIPTION
        The Get-ModuleVersion command retrieves the version of a PowerShell module.
        It accepts a module name or a PSModuleInfo object as input and returns the
        module version as a string.
 
    .PARAMETER Module
        Specifies the module for which to retrieve the version. This can be either
        a module name or a PSModuleInfo object.
 
    .EXAMPLE
        Get-ModuleVersion -Module 'MyModule'
 
        Retrieves the version of the module named "MyModule".
 
    .EXAMPLE
        $moduleInfo = Get-Module -Name 'MyModule'
        Get-ModuleVersion -Module $moduleInfo
 
        Retrieves the version of the module specified by the PSModuleInfo object $moduleInfo.
 
    .INPUTS
        [System.Object]
 
        Accepts a module name or a PSModuleInfo object as input.
 
    .OUTPUTS
        [System.String]
 
        Returns the module version as a string.
#>

function Get-ModuleVersion
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [System.Object]
        $Module
    )

    process
    {
        $moduleInfo = $null
        $moduleVersion = $null

        if ($Module -is [System.String])
        {
            $moduleInfo = Get-Module -Name $Module -ErrorAction 'Stop'

            if (-not $moduleInfo)
            {
                Write-Error -Message "Cannot find the module '$Module'. Make sure it is loaded into the session."
            }
        }
        elseif ($Module -is [System.Management.Automation.PSModuleInfo])
        {
            $moduleInfo = $Module
        }
        else
        {
            Write-Error -Message "Invalid parameter type. The parameter 'Module' must be either a string or a PSModuleInfo object."
        }

        if ($moduleInfo)
        {
            $moduleVersion = $moduleInfo.Version.ToString()

            $previewReleaseTag = $moduleInfo.PrivateData.PSData.Prerelease

            if ($previewReleaseTag)
            {
                $moduleVersion += '-{0}' -f $previewReleaseTag
            }
        }

        return $moduleVersion
    }
}
#EndRegion '.\Public\Get-ModuleVersion.ps1' 83
#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\Invoke-PesterJob.ps1' -1

<#
    .SYNOPSIS
        Runs Pester tests using a job-based approach.
 
    .DESCRIPTION
        The `Invoke-PesterJob` command runs Pester tests using a job-based approach.
        It allows you to specify various parameters such as the test path, root path,
        module name, output verbosity, code coverage path, and more.
 
        Its primary purpose is to run Pester tests in a separate job to avoid polluting
        the current session with PowerShell classes and project specific assemblies
        which can cause issues when building the project.
 
        It is helpful for projects based on the Sampler project template, but it can
        also be used for other projects.
 
    .PARAMETER Path
        Specifies one or more paths to the Pester test files. If not specified, the
        current location is used. This also has tab completion support. Just write
        part of the test script file name and press tab to get a list of available
        test files matching the input, or if only one file matches, it will be
        auto-completed.
 
    .PARAMETER RootPath
        Specifies the root path for the Pester tests. If not specified, the current
        location is used.
 
    .PARAMETER Tag
        Specifies the tags to filter the Pester tests.
 
    .PARAMETER ModuleName
        Specifies the name of the module to test. If not specified, it will be
        inferred based on the project type.
 
    .PARAMETER Output
        Specifies the output verbosity level. Valid values are 'Normal', 'Detailed',
        'None', 'Diagnostic', and 'Minimal'. Default is 'Detailed'.
 
    .PARAMETER CodeCoveragePath
        Specifies the paths to one or more the code coverage files (script or module
        script files). If not provided the default path for code coverage is the
        content of the built module. This parameter also has tab completion support.
        Just write part of the script file name and press tab to get a list of
        available script files matching the input, or if only one file matches,
        it will be auto-completed.
 
    .PARAMETER SkipCodeCoverage
        Indicates whether to skip code coverage.
 
    .PARAMETER PassThru
        Indicates whether to pass the Pester result object through.
 
    .PARAMETER ShowError
        Indicates whether to display detailed error information. When using this
        to debug a test it is recommended to run as few tests as possible, or just
        the test having issues, to limit the amount of error information displayed.
 
    .PARAMETER SkipRun
        Indicates whether to skip running the tests, this just runs the discovery
        phase. This is useful when you want to see what tests would be run without
        actually running them. To actually make use of this, the PassThru parameter
        should also be specified. Suggest to also use the parameter SkipCodeCoverage.
 
    .PARAMETER BuildScriptPath
        Specifies the path to the build script. If not specified, it defaults to
        'build.ps1' in the root path. This is used to ensure that the test environment
        is configured correctly, for example required modules are available in the
        session. It is also used to ensure to find the specific Pester module used
        by the project.
 
    .PARAMETER BuildScriptParameter
        Specifies a hashtable with the parameters to pass to the build script.
        Defaults to parameter 'Task' with a value of 'noop'.
 
    .EXAMPLE
        $invokePesterJobParameters = @{
            Path = './tests/Unit/DSC_SqlAlias.Tests.ps1'
            CodeCoveragePath = './output/builtModule/SqlServerDsc/0.0.1/DSCResources/DSC_SqlAlias/DSC_SqlAlias.psm1'
        }
        Invoke-PesterJob @invokePesterJobParameters
 
        Runs the Pester test DSC_SqlAlias.Tests.ps1 located in the 'tests/Unit'
        folder. The code coverage is based on the code in the DSC_SqlAlias.psm1
        file.
 
    .EXAMPLE
        $invokePesterJobParameters = @{
            Path = './tests'
            RootPath = 'C:\Projects\MyModule'
            Tag = 'Unit'
            Output = 'Detailed'
            CodeCoveragePath = 'C:\Projects\MyModule\coverage'
        }
        Invoke-PesterJob @invokePesterJobParameters
 
        Runs Pester tests located in the 'tests' directory of the 'C:\Projects\MyModule'
        root path. Only tests with the 'Unit' tag will be executed. Detailed output
        will be displayed, and code coverage will be collected from the
        'C:\Projects\MyModule\coverage' directory.
 
    .EXAMPLE
        $invokePesterJobParameters = @{
            Path = './tests/Unit'
            SkipRun = $true
            SkipCodeCoverage = $true
            PassThru = $true
        }
        Invoke-PesterJob @invokePesterJobParameters
 
        Runs the discovery phase on all the Pester tests files located in the
        'tests/Unit' folder and outputs the Pester result object.
 
    .NOTES
        This function requires the Pester module to be imported. If the module is
        not available, it will attempt to run the build script to ensure the
        required modules are available in the session.
#>

function Invoke-PesterJob
{
    # cSpell: ignore Runspaces
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseUsingScopeModifierInNewRunspaces', '', Justification = 'This is a false positive. The script block is used in a job and does not use variables from the parent scope, they are passed in ArgumentList.')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidWriteErrorStop', '', Justification = 'If $PSCmdlet.ThrowTerminatingError were used, the error would not stop any command that would call Invoke-PesterJob.')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Argument completers always need the same parameters even if they are not used in the argument completer script.')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-Hashtable', '', Justification = 'The hashtable must be format as is to work when documentation is being generated by PlatyPS.')]
    [Alias('ipj')]
    [CmdletBinding()]
    param
    (
        [Parameter(Position = 0)]
        [ArgumentCompleter(
            {
                <#
                    This scriptblock is used to provide tab completion for the Path
                    parameter. The scriptblock could be a command, but then it would
                    need to be a public command. Also, if anything goes wrong in the
                    completer scriptblock, it will just fail silently and not provide
                    any completion results.
                #>

                param
                (
                    [Parameter()]
                    $CommandName,

                    [Parameter()]
                    $ParameterName,

                    [Parameter()]
                    $WordToComplete,

                    [Parameter()]
                    $CommandAst,

                    [Parameter()]
                    $FakeBoundParameters
                )

                # This parameter is from Invoke-PesterJob.
                if (-not $FakeBoundParameters.ContainsKey('RootPath'))
                {
                    $RootPath = (Get-Location).Path
                }

                $testRoot = Join-Path -Path $RootPath -ChildPath 'tests/unit'

                $values = (Get-ChildItem -Path $testRoot -Recurse -Filter '*.tests.ps1' -File).FullName

                foreach ($val in $values)
                {
                    if ($val -like "*$WordToComplete*")
                    {
                        New-Object -Type System.Management.Automation.CompletionResult -ArgumentList @(
                            (ConvertTo-RelativePath -AbsolutePath $val -CurrentLocation $RootPath) # completionText
                            (Split-Path -Path $val -Leaf) -replace '\.[Tt]ests.ps1' # listItemText
                            'ParameterValue' # resultType
                            $val # toolTip
                        )
                    }
                }
            })]
        [ValidateNotNullOrEmpty()]
        [System.String[]]
        $Path = (Get-Location).Path,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $RootPath = (Get-Location).Path,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String[]]
        $Tag,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ModuleName,

        [Parameter()]
        [System.String]
        [ValidateSet('Normal', 'Detailed', 'None', 'Diagnostic', 'Minimal')]
        $Output,

        [Parameter(Position = 1)]
        [ArgumentCompleter(
            {
                <#
                    This scriptblock is used to provide tab completion for the
                    CodeCoveragePath parameter. The scriptblock could be a command,
                    but then it would need to be a public command. Also, if anything
                    goes wrong in the completer scriptblock, it will just fail
                    silently and not provide any completion results.
                #>

                param
                (
                    [Parameter()]
                    $CommandName,

                    [Parameter()]
                    $ParameterName,

                    [Parameter()]
                    $WordToComplete,

                    [Parameter()]
                    $CommandAst,

                    [Parameter()]
                    $FakeBoundParameters
                )

                # This parameter is from Invoke-PesterJob.
                if (-not $FakeBoundParameters.ContainsKey('RootPath'))
                {
                    $RootPath = (Get-Location).Path
                }

                # TODO: builtModule should be dynamic.
                $builtModuleCodePath = @(
                    Join-Path -Path $RootPath -ChildPath 'output/builtModule'
                )

                $paths = Get-ChildItem -Path $builtModuleCodePath -Recurse -Include @('*.psm1', '*.ps1') -File -ErrorAction 'SilentlyContinue'

                # Filter out the external Modules directory.
                $values = $paths.FullName -notmatch 'Modules'

                $leafRegex = [regex]::new('([^\\/]+)$')

                foreach ($val in $values)
                {
                    $leaf = $leafRegex.Match($val).Groups[1].Value

                    if ($leaf -like "*$WordToComplete*")
                    {
                        New-Object -Type System.Management.Automation.CompletionResult -ArgumentList @(
                            (ConvertTo-RelativePath -AbsolutePath $val -CurrentLocation $RootPath) # completionText
                            $leaf -replace '\.(ps1|psm1)' # listItemText
                            'ParameterValue' # resultType
                            $val # toolTip
                        )
                    }
                }
            })]
        [ValidateNotNullOrEmpty()]
        [System.String[]]
        $CodeCoveragePath,

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

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

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

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

        [Parameter()]
        [ValidateScript({
                if (-not (Test-Path $_ -PathType 'Leaf'))
                {
                    throw "The file path '$_' does not exist or is a container."
                }

                $true
            })]
        [System.String]
        $BuildScriptPath,

        [Parameter()]
        [System.Collections.Hashtable]
        $BuildScriptParameter = @{ Task = 'noop' }
    )

    if (-not $PSBoundParameters.ContainsKey('BuildScriptPath'))
    {
        $BuildScriptPath = Join-Path -Path $RootPath -ChildPath 'build.ps1'
    }

    $pesterModuleVersion = $null

    do
    {
        $triesCount = 0

        try
        {
            $importedPesterModule = Import-Module -Name 'Pester' -MinimumVersion '4.10.1' -ErrorAction 'Stop' -PassThru

            $pesterModuleVersion = $importedPesterModule | Get-ModuleVersion

            <#
                Assuming that the project is a Sampler project if the Sampler
                module is available in the session. Also assuming that a Sampler
                build task has been run prior to running the command.
            #>

            $isSamplerProject = $null -ne (Get-Module -Name 'Sampler')
        }
        catch
        {
            $triesCount++

            if ($triesCount -eq 1 -and (Test-Path -Path $BuildScriptPath))
            {
                Write-Information -MessageData 'Could not import Pester. Running build script to make sure required modules is available in session. This can take a few seconds.' -InformationAction 'Continue'

                # Redirect all streams to $null, except the error stream (stream 2)
                & $BuildScriptPath @buildScriptParameter 2>&1 4>&1 5>&1 6>&1 > $null
            }
            else
            {
                Write-Error -ErrorRecord $_ -ErrorAction 'Stop'
            }
        }
    } until ($importedPesterModule)

    Write-Information -MessageData ('Using imported Pester v{0}.' -f $pesterModuleVersion) -InformationAction 'Continue'

    if (-not $PSBoundParameters.ContainsKey('ModuleName'))
    {
        if ($isSamplerProject)
        {
            $ModuleName = Get-SamplerProjectName -BuildRoot $RootPath
        }
        else
        {
            $ModuleName = (Get-Item -Path $RootPath).BaseName
        }
    }

    $testResultsPath = Join-Path -Path $RootPath -ChildPath 'output/testResults'

    if (-not $PSBoundParameters.ContainsKey('CodeCoveragePath'))
    {
        # TODO: Should be possible to use default coverage paths for a module that is not based on Sampler.
        if ($isSamplerProject)
        {
            $BuiltModuleBase = Get-SamplerBuiltModuleBase -OutputDirectory "$RootPath/output" -BuiltModuleSubdirectory 'builtModule' -ModuleName $ModuleName

            # TODO: This does not take into account any .ps1 files in the module.
            # TODO: This does not take into account any other .psm1 files in the module, e.g. MOF-based DSC resources.
            $CodeCoveragePath = '{0}/*/{1}.psm1' -f $BuiltModuleBase, $ModuleName
        }
    }

    if ($importedPesterModule.Version.Major -eq 4)
    {
        $pesterConfig = @{
            Script = $Path
        }
    }
    else
    {
        $pesterConfig = New-PesterConfiguration -Hashtable @{
            CodeCoverage = @{
                Enabled        = $true
                Path           = $CodeCoveragePath
                OutputPath     = (Join-Path -Path $testResultsPath -ChildPath 'PesterJob_coverage.xml')
                UseBreakpoints = $false
            }
            Run          = @{
                Path = $Path
            }
        }
    }

    if ($PSBoundParameters.ContainsKey('Output'))
    {
        if ($importedPesterModule.Version.Major -eq 4)
        {
            $pesterConfig.Show = $Output
        }
        else
        {
            $pesterConfig.Output.Verbosity = $Output
        }
    }
    else
    {
        if ($importedPesterModule.Version.Major -eq 4)
        {
            $pesterConfig.Show = 'All'
        }
        else
        {
            $pesterConfig.Output.Verbosity = 'Detailed'
        }
    }

    # Turn off code coverage if the user has specified that they don't want it
    if ($SkipCodeCoverage.IsPresent)
    {
        # Pester v4: By not passing code paths the code coverage is disabled.

        # Pester v5: By setting the Enabled property to false the code coverage is disabled.
        if ($importedPesterModule.Version.Major -ge 5)
        {
            $pesterConfig.CodeCoverage.Enabled = $false
        }
    }
    else
    {
        # Pester 4: By passing code paths the code coverage is enabled.
        if ($importedPesterModule.Version.Major -eq 4)
        {
            $pesterConfig.CodeCoverage = $CodeCoveragePath
        }
    }

    if ($PassThru.IsPresent)
    {
        if ($importedPesterModule.Version.Major -eq 4)
        {
            $pesterConfig.PassThru = $true
        }
        else
        {
            $pesterConfig.Run.PassThru = $true
        }
    }

    if ($SkipRun.IsPresent)
    {
        # This is only supported in Pester v5 or higher.
        if ($importedPesterModule.Version.Major -ge 5)
        {
            $pesterConfig.Run.SkipRun = $true
        }
    }

    if ($PSBoundParameters.ContainsKey('Tag'))
    {
        if ($importedPesterModule.Version.Major -eq 4)
        {
            $pesterConfig.Tag = $Tag
        }
        else
        {
            $pesterConfig.Filter.Tag = $Tag
        }
    }

    Start-Job -ScriptBlock {
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory = $true, Position = 0)]
            [System.Object]
            $PesterConfiguration,

            [Parameter(Mandatory = $true, Position = 1)]
            [System.Management.Automation.SwitchParameter]
            $ShowError,

            [Parameter(Mandatory = $true, Position = 2)]
            [System.Version]
            $PesterVersion,

            [Parameter(Mandatory = $true, Position = 3)]
            [System.String]
            $BuildScriptPath,

            [Parameter(Mandatory = $true, Position = 4)]
            [System.Collections.Hashtable]
            $BuildScriptParameter
        )

        Write-Information -MessageData 'Running build task ''noop'' inside the job to setup the test pipeline.' -InformationAction 'Continue'

        & $BuildScriptPath @buildScriptParameter

        if ($ShowError.IsPresent)
        {
            $Error.Clear()
            $ErrorView = 'DetailedView'
        }

        if ($PesterVersion.Major -eq 4)
        {
            Invoke-Pester @PesterConfiguration
        }
        else
        {
            Invoke-Pester -Configuration $PesterConfiguration
        }

        if ($ShowError.IsPresent)
        {
            'Error count: {0}' -f $Error.Count
            $Error | Out-String
        }
    } -ArgumentList @(
        $pesterConfig
        $ShowError.IsPresent
        $importedPesterModule.Version
        $BuildScriptPath
        $BuildScriptParameter
    ) |
        Receive-Job -AutoRemoveJob -Wait
}
#EndRegion '.\Public\Invoke-PesterJob.ps1' 528
#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