AtlassianPS.Configuration.psm1

#region Dependencies
# Load the Module's namespace from C#
if (-not("AtlassianPS.ServerData" -as [Type])) {
    Add-Type -Path (Join-Path $PSScriptRoot AtlassianPS.Configuration.Types.cs) -ReferencedAssemblies Microsoft.CSharp, Microsoft.PowerShell.Commands.Utility, System.Management.Automation, System.Runtime.Extensions
}
if ($PSVersionTable.PSVersion.Major -lt 5) {
   Add-Type -Path (Join-Path $PSScriptRoot AtlassianPS.Configuration.Attributes.cs) -ReferencedAssemblies Microsoft.CSharp, Microsoft.PowerShell.Commands.Utility, System.Management.Automation, System.Runtime.Extensions
}
#region ModuleConfig
# Add our own Converters for serialization
Add-MetadataConverter @{
    [AtlassianPS.MessageStyle] = { "AtlassianPSMessageStyle -Indent {0} -TimeStamp {1} -BreadCrumbs {2} -FunctionName {3}" -f (ConvertTo-Metadata $_.Indent), (ConvertTo-Metadata $_.TimeStamp), (ConvertTo-Metadata $_.BreadCrumbs), (ConvertTo-Metadata $_.FunctionName) }
    AtlassianPSMessageStyle = {
        param($Indent, $TimeStamp, $BreadCrumbs, $FunctionName)
        [AtlassianPS.MessageStyle]$PSBoundParameters
    }
    [AtlassianPS.ServerData] = { "AtlassianPSServerData -Id {0} -Name '{1}' -Uri '{2}' -Type '{3}' -Headers {4}" -f $_.Id, $_.Name, $_.Uri, $_.Type, (ConvertTo-Metadata $_.Headers) }
    AtlassianPSServerData = {
        param($Id, $Name, $Uri, $Type, $Headers)
        if ([string]::IsNullOrEmpty($Headers)) { $Headers = $null }
        [AtlassianPS.ServerData]$PSBoundParameters
    }
}

# Load configuration using
# https://github.com/PoshCode/Configuration
[Hashtable]$script:Configuration = Import-Configuration -CompanyName "AtlassianPS" -Name "AtlassianPS.Configuration"
if (-not $script:Configuration) { $script:Configuration = @{} }
if (-not $script:Configuration.ContainsKey("ServerList")) {
    $script:Configuration.Add("ServerList",[System.Collections.Generic.List[AtlassianPS.ServerData]]::new())
}
function Add-ServerConfiguration {
    # .ExternalHelp ..\AtlassianPS.Configuration-help.xml
    [CmdletBinding()]
    [OutputType( [void] )]
    param(
        [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )]
        [Alias('Url', 'Address')]
        [Uri]
        $Uri,

        [Parameter( ValueFromPipelineByPropertyName )]
        [ValidateNotNullOrEmpty()]
        [Alias('ServerName', 'Alias')]
        [String]
        $Name,

        [Parameter( Mandatory, ValueFromPipelineByPropertyName )]
        [AtlassianPS.ServerType]
        $Type,

        [Parameter( ValueFromPipelineByPropertyName )]
        [Microsoft.PowerShell.Commands.WebRequestSession]
        $Session,

        [Parameter( ValueFromPipelineByPropertyName )]
        [Hashtable]
        $Headers = @{}
    )

    begin {
        Write-Verbose "Function started"

        if (-not ($script:Configuration.ServerList)) {
            $script:Configuration.ServerList = $null
        }

        $serverList = [System.Collections.Generic.List[AtlassianPS.ServerData]]::new()
        if (Get-ServerConfiguration) {
            [System.Collections.Generic.List[AtlassianPS.ServerData]]$serverList = Get-ServerConfiguration
        }
    }

    process {
        Write-DebugMessage "ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "PSBoundParameters: $($PSBoundParameters | Out-String)"

        if (-not $Name) {
            $Name = $Uri.Authority
        }

        if (Get-ServerConfiguration | Where-Object Name -eq $Name) {
            $writeErrorSplat = @{
                ExceptionType = "System.ApplicationException"
                Message       = "An entry with name [$Name] already exists"
                ErrorId       = "AtlassianPS.ServerData.EntryExists"
                Category      = "InvalidData"
                TargetObject  = $Name
            }
            WriteError @writeErrorSplat
        }
        else {
            if (-not ($index = ((Get-ServerConfiguration).Id | Measure-Object -Maximum).Maximum)) {
                $index = 0
            }
            $index++

            $config = [AtlassianPS.ServerData]@{
                Id      = $index
                Name    = $Name
                Uri     = ([Uri]($Uri.AbsoluteUri -replace "\/$", ""))
                Type    = $Type
                # IsCloudServer = (Test-ServerIsCloud -Type $Type -Uri $Uri -Headers $Headers -ErrorAction Stop -verbose)
                Session = $Session
                Headers = $Headers
            }

            Write-Verbose "Adding server #$($index): [$($config.Name)]"
            Write-DebugMessage "Adding server `$config: $($config.Name) @ index $index" -BreakPoint
            $serverList.Add($config)
        }
    }

    end {
        Write-DebugMessage "Persisting ServerList"
        $script:Configuration["ServerList"] = $serverList
        Save-Configuration

        Write-Verbose "Function ended"
    }
}

function Get-Configuration {
    # .ExternalHelp ..\AtlassianPS.Configuration-help.xml
    [CmdletBinding( DefaultParameterSetName = 'asObject' )]
    [OutputType( [PSCustomObject], ParameterSetName = 'asObject' )]
    [OutputType( [PSObject], ParameterSetName = 'asValue' )]
    [OutputType( [Hashtable], ParameterSetName = 'asHashTable' )]
    param(
        [Parameter( ValueFromPipeline, ValueFromPipelineByPropertyName )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [ArgumentCompleter(
            {
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
                $command = "Get-Configuration"
                $module = (Get-command -Name $commandName).Module
                $commandName = $module.ExportedCommands.Keys | Where-Object {$_ -like ($command -replace "-", "-$($module.Prefix)")}
                & $commandName |
                    Where-Object { $_.Name -like "$wordToComplete*" } |
                    ForEach-Object { [System.Management.Automation.CompletionResult]::new( $_.Name, $_.Name, [System.Management.Automation.CompletionResultType]::ParameterValue, $_.Name ) }
            }
        )]
        [String[]]
        $Name = '*',

        [Parameter( Mandatory, ParameterSetName = 'asValue' )]
        [Switch]
        $ValueOnly,

        [Parameter( Mandatory, ParameterSetName = 'asHashtable' )]
        [Switch]
        $AsHashtable
    )

    begin {
        Write-Verbose "Function started"
    }

    process {
        Write-DebugMessage "ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "PSBoundParameters: $($PSBoundParameters | Out-String)"

        $data = @{}

        foreach ($_name in $Name) {
            foreach ($key in ($script:Configuration.Keys | Where-Object { $_ -like $_name })) {
                Write-Verbose "Filtering for [name = $key]"

                $data[$key] = $script:Configuration[$key]
            }
        }

        if ($AsHashtable) { $data }
        elseif ($ValueOnly) { $data.Values }
        else {
            foreach ($key in $data.Keys) {
                New-Object -TypeName PSCustomObject -Property @{ Name = $key; Value = $data[$key] }
            }
        }
    }

    end {
        Write-Verbose "Function ended"
    }
}

function Get-ServerConfiguration {
    # .ExternalHelp ..\AtlassianPS.Configuration-help.xml
    [CmdletBinding( DefaultParameterSetName = '_All' )]
    [OutputType( [AtlassianPS.ServerData] )]
    param(
        [Parameter( Position = 0, Mandatory, ParameterSetName = 'ServerDataByUri' )]
        [ArgumentCompleter(
            {
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
                $command = "Get-ServerConfiguration"
                $module = (Get-command -Name $commandName).Module
                $commandName = $module.ExportedCommands.Keys | Where-Object {$_ -like ($command -replace "-", "-$($module.Prefix)")}
                & $commandName |
                    Where-Object { $_.Name -like "$wordToComplete*" } |
                    ForEach-Object { [System.Management.Automation.CompletionResult]::new( $_.Name, $_.Name, [System.Management.Automation.CompletionResultType]::ParameterValue, $_.Name ) }
            }
        )]
        [Alias('Url', 'Address')]
        [Uri]
        $Uri,

        [Parameter( Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'ServerDataByName' )]
        [ArgumentCompleter(
            {
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
                $command = "Get-ServerConfiguration"
                $module = (Get-command -Name $commandName).Module
                $commandName = $module.ExportedCommands.Keys | Where-Object {$_ -like ($command -replace "-", "-$($module.Prefix)")}
                & $commandName |
                    Where-Object { $_.Uri -like "$wordToComplete*" } |
                    ForEach-Object { [System.Management.Automation.CompletionResult]::new( $_.Name, $_.Name, [System.Management.Automation.CompletionResultType]::ParameterValue, $_.Name ) }
            }
        )]
        [Alias('ServerName', 'Alias')]
        [String[]]
        $Name
    )

    begin {
        Write-Verbose "Function started"

        $serverList = Get-Configuration -Name ServerList -ValueOnly
    }

    process {
        Write-DebugMessage "ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "PSBoundParameters: $($PSBoundParameters | Out-String)"

        switch ($PsCmdlet.ParameterSetName) {
            'ServerDataByName' {
                Write-Verbose "Filtering ServerList for [Name = $Name]"

                $serverList | Where-Object { $_.Name -in $Name }
            }
            'ServerDataByUri' {
                Write-Verbose "Filtering ServerList for [URI = $Uri]"

                $serverList | Where-Object { $_.Uri -like $Uri }
            }
            '_All' {
                $serverList
            }
        }
    }

    end {
        Write-Verbose "Function ended"
    }
}

function Remove-Configuration {
    # .ExternalHelp ..\AtlassianPS.Configuration-help.xml
    [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $false )]
    [OutputType( [void] )]
    [System.Diagnostics.CodeAnalysis.SuppressMessage( 'PSUseShouldProcessForStateChangingFunctions', '' )]
    param(
        [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter(
            {
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
                $command = "Get-Configuration"
                $module = (Get-command -Name $commandName).Module
                $commandName = $module.ExportedCommands.Keys | Where-Object {$_ -like ($command -replace "-", "-$($module.Prefix)")}
                & $commandName |
                    Where-Object { $_.Name -like "$wordToComplete*" } |
                    ForEach-Object { [System.Management.Automation.CompletionResult]::new( $_.Name, $_.Name, [System.Management.Automation.CompletionResultType]::ParameterValue, $_.Name ) }
            }
        )]
        [String[]]
        $Name
    )

    begin {
        Write-Verbose "Function started"
    }

    process {
        Write-DebugMessage "ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "PSBoundParameters: $($PSBoundParameters | Out-String)"

        foreach ($_name in $Name) {
            Write-Verbose "Filtering for [name = $_name]"

            $script:Configuration.Remove($_name)
        }
    }

    end {
        Save-Configuration

        Write-Verbose "Function ended"
    }
}

function Remove-ServerConfiguration {
    # .ExternalHelp ..\AtlassianPS.Configuration-help.xml
    [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $false )]
    [OutputType( [void] )]
    [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')]
    param(
        [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )]
        [ArgumentCompleter(
            {
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
                $commandName = (Get-Command -Module "AtlassianPS.Configuration" -Name "Get-*ServerConfiguration").Name
                & $commandName |
                    Where-Object { $_.Name -like "$wordToComplete*" } |
                    ForEach-Object { [System.Management.Automation.CompletionResult]::new( $_.Name, $_.Name, [System.Management.Automation.CompletionResultType]::ParameterValue, $_.Name ) }
            }
        )]
        [Alias('ServerName', 'Alias')]
        [String[]]
        $Name
    )

    begin {
        Write-Verbose "Function started"

        $serverList = Get-ServerConfiguration
    }

    process {
        Write-DebugMessage "ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "PSBoundParameters: $($PSBoundParameters | Out-String)"

        foreach ($serverToRemove in $Name) {
            if ($serverToRemove -notin $serverList.Name) {
                $writeErrorSplat = @{
                    ExceptionType = "System.ApplicationException"
                    ErrorId      = "AtlassianPS.ServerData.ServerNotFound"
                    Category     = "ObjectNotFound"
                    Message      = "No server '$serverToRemove' could be found."
                    TargetObject = $serverToRemove
                    Cmdlet       = $PSCmdlet
                }
                WriteError @writeErrorSplat
            }
        }

        $serverList = $serverList | Where-Object { $_.Name -notin $Name }
    }

    end {
        Write-DebugMessage "Persisting ServerList"
        $script:Configuration.ServerList = $serverList
        Save-Configuration

        Write-Verbose "Function ended"
    }
}

function Set-Configuration {
    # .ExternalHelp ..\AtlassianPS.Configuration-help.xml
    [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $false )]
    [OutputType( [PSCustomObject] )]
    [System.Diagnostics.CodeAnalysis.SuppressMessage( 'PSUseShouldProcessForStateChangingFunctions', '' )]
    param(
        [Parameter( Mandatory, ValueFromPipelineByPropertyName )]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter(
            {
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
                $command = "Get-Configuration"
                $module = (Get-command -Name $commandName).Module
                $commandName = $module.ExportedCommands.Keys | Where-Object {$_ -like ($command -replace "-", "-$($module.Prefix)")}
                & $commandName |
                    Where-Object { $_.Name -like "$wordToComplete*" } |
                    ForEach-Object { [System.Management.Automation.CompletionResult]::new( $_.Name, $_.Name, [System.Management.Automation.CompletionResultType]::ParameterValue, $_.Name ) }
            }
        )]
        [String]
        $Name,

        [Parameter( ValueFromPipelineByPropertyName )]
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [Object]
        $Value,

        [Switch]
        $Append,

        [Switch]
        $Passthru
    )

    begin {
        Write-Verbose "Function started"
    }

    process {
        Write-DebugMessage "ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "PSBoundParameters: $($PSBoundParameters | Out-String)"

        If ($Append) {
            Write-Verbose "Appending to existing value"
            $oldValue = (Get-Configuration -Name $Name -ValueOnly)
            try {
                $newValue = @(@($oldValue) + @($Value)) -as ($oldValue.GetType())
                if (-not $newValue) {
                    throw "failed to case to $($oldValue.GetType().Name)"
                }
            }
            catch {
                Write-DebugMessage $_

                $newValue = @(@($oldValue) + @($Value))
            }
            $Value = $newValue
        }

        if ($Value) { $dataType = $Value.GetType().Name }
        else { $dataType = "null" }
        Write-Verbose "Storing value [$dataType] to [name = $Name]"

        $script:Configuration.Remove($Name)
        $script:Configuration.Add($Name, $Value)

        if ($Passthru) {
            Get-Configuration -Name $Name
        }
    }

    end {
        Save-Configuration

        Write-Verbose "Function ended"
    }
}

function Set-ServerConfiguration {
    # .ExternalHelp ..\AtlassianPS.Configuration-help.xml
    [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $false )]
    [OutputType( [void] )]
    [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')]
    param(
        [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )]
        [UInt32]
        $Id,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Alias('Url', 'Address')]
        [Uri]
        $Uri,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Alias('ServerName', 'Alias')]
        [String]
        $Name = $Uri.Authority,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [AtlassianPS.ServerType]
        $Type,

        [Parameter()]
        [Microsoft.PowerShell.Commands.WebRequestSession]
        $Session,

        [Parameter()]
        [Hashtable]
        $Headers
    )

    begin {
        Write-Verbose "Function started"

        $parametersToIgnore = @(
            'Id'
            'Verbose'
            'Debug'
            'ErrorAction'
            'WarningAction'
            'InformationAction'
            'ErrorVariable'
            'WarningVariable'
            'InformationVariable'
            'OutVariable'
            'OutBuffer'
            'PipelineVariable'
            'WhatIf'
            'Confirm'
        )
    }

    process {
        Write-DebugMessage "ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "PSBoundParameters: $($PSBoundParameters | Out-String)"

        $serverEntry = Get-ServerConfiguration | Where-Object { $_.Id -eq $Id }
        if ($serverEntry) {
            foreach ($property in ($PSBoundParameters.Keys | Where-Object { $_ -notin $parametersToIgnore} )) {
                Write-Verbose "Changing [$property] of entry #$Id"

                $serverEntry.$property = Get-Variable $property -ValueOnly
            }
        }
        else {
            $writeErrorSplat = @{
                ExceptionType = "System.ApplicationException"
                Message       = "No entry could be found at index $Id"
                ErrorId       = "AtlassianPS.ServerData.NoEntryExists"
                Category      = "InvalidData"
                TargetObject  = $Id
            }
            WriteError @writeErrorSplat
        }
    }

    end {
        Save-Configuration

        Write-Verbose "Function ended"
    }
}

function ConvertTo-HashTable {
    <#
    .SYNOPSIS
        Converts a PSCustomObject to Hashtable
 
    .DESCRIPTION
        PowerShell v4 on Windows 8.1 seems to have trouble casting [PSCustomObject] to custom classes.
        This function is a workaround, as casting from [Hashtable] is no problem.
    #>

    [CmdletBinding()]
    [OutputType( [Hashtable] )]
    param(
        # Object to convert
        [Parameter( Mandatory, ValueFromPipeline )]
        [PSCustomObject]
        $InputObject
    )

    process {
        $hash = @{}
        $InputObject.PSObject.Properties | Foreach-Object {
            $hash[$_.Name] = $_.Value
        }
        $hash
    }
}

function Get-BreadCrumb {
    param(
        [String]$Delimiter = " > "
    )

    $depth = 1
    $path = New-Object -TypeName System.Collections.ArrayList

    while ($depth) {
        try {
            $null = $path.Add((Get-Variable MyInvocation -Scope $depth -ValueOnly).MyCommand.Name)
            $depth++
        }
        catch {
            $depth = 0
        }
    }

    $path.Remove("")
    $path -join $Delimiter
}

function Import-MqcnAlias {
    <#
    .SYNOPSIS
        Create an alias for a full command name
 
    .DESCRIPTION
        Create an alias for a full command name
        This can be used to create a mockable call to a full command name
 
    .EXAMPLE
        Import-MqcnAlias -Alias "GetItem" -Command Microsoft.PowerShell.Management\Get-Item
        ---------
        Description
        Create an alias "GetItem" for the normal Get-Item
    #>

    [CmdletBinding()]
    param(
        # Name of the alias to be used
        [Parameter( Mandatory )]
        [String]
        $Alias,

        # Name of the command for which to create the alias
        [Parameter( Mandatory )]
        [String]
        $Command
    )

    begin {
        Set-Alias -Name $Alias -Value $Command -Scope 1
    }
}

function Invoke-WebRequest {
    # For Version up to 5.1
    <#
    .ForwardHelpTargetName
        Microsoft.PowerShell.Utility\Invoke-WebRequest
    .ForwardHelpCategory
        Cmdlet
    #>

    [CmdletBinding(HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=217035')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        "PSAvoidUsingConvertToSecureStringWithPlainText",
        "",
        Justification = "Converting received plaintext token to SecureString"
    )]
    param(
        [switch]
        ${UseBasicParsing},

        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [uri]
        ${Uri},

        [Microsoft.PowerShell.Commands.WebRequestSession]
        ${WebSession},

        [Alias('SV')]
        [string]
        ${SessionVariable},

        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${Credential} = [System.Management.Automation.PSCredential]::Empty,

        [switch]
        ${UseDefaultCredentials},

        [ValidateNotNullOrEmpty()]
        [string]
        ${CertificateThumbprint},

        [ValidateNotNull()]
        [System.Security.Cryptography.X509Certificates.X509Certificate]
        ${Certificate},

        [string]
        ${UserAgent},

        [switch]
        ${DisableKeepAlive},

        [ValidateRange(0, 2147483647)]
        [int32]
        ${TimeoutSec},

        [System.Collections.IDictionary]
        ${Headers},

        [ValidateRange(0, 2147483647)]
        [int]
        ${MaximumRedirection},

        [Microsoft.PowerShell.Commands.WebRequestMethod]
        ${Method},

        [uri]
        ${Proxy},

        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${ProxyCredential} = [System.Management.Automation.PSCredential]::Empty,

        [switch]
        ${ProxyUseDefaultCredentials},

        [Parameter(ValueFromPipeline = $true)]
        [System.Object]
        ${Body},

        [string]
        ${ContentType},

        [ValidateSet('chunked', 'compress', 'deflate', 'gzip', 'identity')]
        [string]
        ${TransferEncoding},

        [string]
        ${InFile},

        [string]
        ${OutFile},

        [switch]
        ${PassThru})

    begin {
        if ($Credential) {
            $SecureCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(
                    $('{0}:{1}' -f $Credential.UserName, $Credential.GetNetworkCredential().Password)
                ))
            $Headers["Authorization"] = "Basic $($SecureCreds)"
            $PSBoundParameters.Remove("Credential")
        }

        if ($InFile) {
            $boundary = [System.Guid]::NewGuid().ToString()
            $enc = [System.Text.Encoding]::GetEncoding("iso-8859-1")
            $fileName = Split-Path -Path $InFile -Leaf
            $readFile = Get-Content -Path $InFile -Encoding Byte
            $fileEnc = $enc.GetString($readFile)
            $PSBoundParameters["Body"] = @'
--{0}
Content-Disposition: form-data; name="file"; filename="{1}"
Content-Type: application/octet-stream
 
{2}
--{0}--
 
'@
 -f $boundary, $fileName, $fileEnc

            $PSBoundParameters["Headers"]['X-Atlassian-Token'] = 'nocheck'
            $PSBoundParameters["ContentType"] = "multipart/form-data; boundary=`"$boundary`""
            $null = $PSBoundParameters.Remove("InFile")
        }

        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Invoke-WebRequest', [System.Management.Automation.CommandTypes]::Cmdlet)
            $scriptCmd = {& $wrappedCmd @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        }
        catch {
            throw
        }
    }

    process {
        try {
            $steppablePipeline.Process($_)
            if ($SessionVariable) {
                Set-Variable -Name $SessionVariable -Value (Get-Variable $SessionVariable).Value -Scope 1
            }
        }
        catch {
            throw
        }
    }

    end {
        try {
            $steppablePipeline.End()
        }
        catch {
            throw
        }
    }
}

if ($PSVersionTable.PSVersion.Major -ge 6) {
    function Invoke-WebRequest {
        #require -Version 6
        <#
        .ForwardHelpTargetName
            Microsoft.PowerShell.Utility\Invoke-WebRequest
        .ForwardHelpCategory
            Cmdlet
        #>

        [CmdletBinding(DefaultParameterSetName = 'StandardMethod', HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=217035')]
        param(
            [switch]
            ${UseBasicParsing},

            [Parameter(Mandatory = $true, Position = 0)]
            [ValidateNotNullOrEmpty()]
            [uri]
            ${Uri},

            [Microsoft.PowerShell.Commands.WebRequestSession]
            ${WebSession},

            [Alias('SV')]
            [string]
            ${SessionVariable},

            [switch]
            ${AllowUnencryptedAuthentication},

            [Microsoft.PowerShell.Commands.WebAuthenticationType]
            ${Authentication},

            [pscredential]
            [System.Management.Automation.CredentialAttribute()]
            ${Credential},

            [switch]
            ${UseDefaultCredentials},

            [ValidateNotNullOrEmpty()]
            [string]
            ${CertificateThumbprint},

            [ValidateNotNull()]
            [X509Certificate]
            ${Certificate},

            [switch]
            ${SkipCertificateCheck},

            [Microsoft.PowerShell.Commands.WebSslProtocol]
            ${SslProtocol},

            [securestring]
            ${Token},

            [string]
            ${UserAgent},

            [switch]
            ${DisableKeepAlive},

            [ValidateRange(0, 2147483647)]
            [int32]
            ${TimeoutSec},

            [System.Collections.IDictionary]
            ${Headers},

            [ValidateRange(0, 2147483647)]
            [int]
            ${MaximumRedirection},

            [Parameter(ParameterSetName = 'StandardMethod')]
            [Parameter(ParameterSetName = 'StandardMethodNoProxy')]
            [Microsoft.PowerShell.Commands.WebRequestMethod]
            ${Method},

            [Parameter(ParameterSetName = 'CustomMethod', Mandatory = $true)]
            [Parameter(ParameterSetName = 'CustomMethodNoProxy', Mandatory = $true)]
            [Alias('CM')]
            [ValidateNotNullOrEmpty()]
            [string]
            ${CustomMethod},

            [Parameter(ParameterSetName = 'CustomMethodNoProxy', Mandatory = $true)]
            [Parameter(ParameterSetName = 'StandardMethodNoProxy', Mandatory = $true)]
            [switch]
            ${NoProxy},

            [Parameter(ParameterSetName = 'StandardMethod')]
            [Parameter(ParameterSetName = 'CustomMethod')]
            [uri]
            ${Proxy},

            [Parameter(ParameterSetName = 'StandardMethod')]
            [Parameter(ParameterSetName = 'CustomMethod')]
            [pscredential]
            [System.Management.Automation.CredentialAttribute()]
            ${ProxyCredential},

            [Parameter(ParameterSetName = 'StandardMethod')]
            [Parameter(ParameterSetName = 'CustomMethod')]
            [switch]
            ${ProxyUseDefaultCredentials},

            [Parameter(ValueFromPipeline = $true)]
            [System.Object]
            ${Body},

            [string]
            ${ContentType},

            [ValidateSet('chunked', 'compress', 'deflate', 'gzip', 'identity')]
            [string]
            ${TransferEncoding},

            [string]
            ${InFile},

            [string]
            ${OutFile},

            [switch]
            ${PassThru},

            [switch]
            ${PreserveAuthorizationOnRedirect},

            [switch]
            ${SkipHeaderValidation})

        begin {
            if ($Credential -and (-not ($Authentication))) {
                $PSBoundParameters["Authentication"] = "Basic"
            }
            if ($InFile) {
                $multipartContent = [System.Net.Http.MultipartFormDataContent]::new()
                $FileStream = [System.IO.FileStream]::new($InFile, [System.IO.FileMode]::Open)
                $fileHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
                $fileHeader.Name = "file"
                $fileHeader.FileName = ([System.io.FileInfo]$InFile).name
                $fileContent = [System.Net.Http.StreamContent]::new($FileStream)
                $fileContent.Headers.ContentDisposition = $fileHeader
                $fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("application/octet-stream")
                $multipartContent.Add($fileContent)
                $PSBoundParameters["Headers"]['X-Atlassian-Token'] = 'nocheck'
                $PSBoundParameters["Body"] = $multipartContent
                $null = $PSBoundParameters.Remove("InFile")
            }
            try {
                $outBuffer = $null
                if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
                    $PSBoundParameters['OutBuffer'] = 1
                }
                $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Invoke-WebRequest', [System.Management.Automation.CommandTypes]::Cmdlet)
                $scriptCmd = {& $wrappedCmd @PSBoundParameters }
                $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
                $steppablePipeline.Begin($PSCmdlet)
            }
            catch {
                throw
            }
        }

        process {
            try {
                $steppablePipeline.Process($_)
                if ($SessionVariable) {
                    Set-Variable -Name $SessionVariable -Value (Get-Variable $SessionVariable).Value -Scope 1
                }
            }
            catch {
                throw
            }
        }

        end {
            try {
                $steppablePipeline.End()
            }
            catch {
                throw
            }
        }
    }
}

function Save-Configuration {
    # .ExternalHelp ..\AtlassianPS.Configuration-help.xml
    [CmdletBinding()]
    param()

    begin {
        Write-Verbose "Function started"

        Import-MqcnAlias -Alias "ExportConfiguration" -Command "Configuration\Export-Configuration"

        Write-DebugMessage "ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "PSBoundParameters: $($PSBoundParameters | Out-String)"

        $export = Get-Configuration -AsHashtable
        $export["ServerList"] |
            Where-Object { $_.Session } |
            Foreach-Object { $_.Session = $null }

        ExportConfiguration -InputObject $export 3>$null

        Write-Verbose "Function ended"
    }
}

function ThrowError {
    <#
    .SYNOPSIS
        Utility to throw a terminating errorrecord
    .NOTES
        Thanks to Jaykul:
        https://github.com/PoshCode/Configuration/blob/master/Source/Metadata.psm1
    #>

    param
    (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCmdlet]
        $Cmdlet = $((Get-Variable -Scope 1 PSCmdlet).Value),

        [Parameter(Mandatory = $true, ParameterSetName = "ExistingException", Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Parameter(ParameterSetName = "NewException")]
        [ValidateNotNullOrEmpty()]
        [System.Exception]
        $Exception,

        [Parameter(ParameterSetName = "NewException", Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ExceptionType = "System.Management.Automation.RuntimeException",

        [Parameter(Mandatory = $true, ParameterSetName = "NewException", Position = 3)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Message,

        [Parameter(Mandatory = $false)]
        [System.Object]
        $TargetObject,

        [Parameter(Mandatory = $true, ParameterSetName = "ExistingException", Position = 10)]
        [Parameter(Mandatory = $true, ParameterSetName = "NewException", Position = 10)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ErrorId,

        [Parameter(Mandatory = $true, ParameterSetName = "ExistingException", Position = 11)]
        [Parameter(Mandatory = $true, ParameterSetName = "NewException", Position = 11)]
        [ValidateNotNull()]
        [System.Management.Automation.ErrorCategory]
        $Category,

        [Parameter(Mandatory = $true, ParameterSetName = "Rethrow", Position = 1)]
        [System.Management.Automation.ErrorRecord]$ErrorRecord
    )

    process {
        if (!$ErrorRecord) {
            if ($PSCmdlet.ParameterSetName -eq "NewException") {
                if ($Exception) {
                    $Exception = New-Object $ExceptionType $Message, $Exception
                }
                else {
                    $Exception = New-Object $ExceptionType $Message
                }
            }
            $errorRecord = New-Object System.Management.Automation.ErrorRecord $Exception, $ErrorId, $Category, $TargetObject
        }
        $Cmdlet.ThrowTerminatingError($errorRecord)
    }
}

function Write-DebugMessage {
    <#
    .SYNOPSIS
        Write a message to the debug stream without creating a breakpoint
 
    .DESCRIPTION
        Write a message to the debug stream without creating a breakpoint
 
        This function allows the user to decide how the Debug Message
        should be formatted. The configuration is inside `$scrpit:Configuration`
        and supports the following structure (json representation):
 
        {
            "message": {
                // show a line with the Bread Crumbs of the caller stack
                "breadcrumbs": true,
 
                // how many whitespaces should be used for indenting the
                // message
                "indent": true,
 
                // show the name of the calling function - this is ignored
                // if breadcrumbs is active
                "functionname": true,
 
                // show the timestamp (HH:mm:ss format) of the message
                "timestamp": true
            }
        }
 
    .EXAMPLE
        Write-DebugMessage "The value of `$var is: $var"
        ----------
        Description
        Shows the message if the user added `-Debug` to the command but does not create a breakpoint
    #>

    [CmdletBinding()]
    param(
        [Parameter( Mandatory, ValueFromPipeline )]
        [String]
        $Message,

        [Switch]
        $BreakPoint,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCmdlet]
        $Cmdlet = ((Get-Variable -Scope 1 PSCmdlet).Value)
    )

    begin {
        $indent, $functionName, $timeStamp = ""

        Import-MqcnAlias -Alias "WriteDebug" -Command "Microsoft.PowerShell.Utility\Write-Debug"

        $messageSettings = $script:Configuration["Message"]

        if (-not $BreakPoint) {
            $oldDebugPreference = $DebugPreference
            if (-not ($DebugPreference -eq "SilentlyContinue")) {
                $DebugPreference = 'Continue'
            }
        }
    }

    process {

        if ((Get-PSCallstack | Select-Object -Last 1 -Skip 1).Arguments.Contains("Debug")) {
            $DebugPreference = 'Continue'
        }

        if ($messageSettings.Breadcrumbs) {
            WriteDebug "[$(Get-BreadCrumb)]:"

            if ($messageSettings.Indent) {
                $indent = " " * $messageSettings.Indent
            }
            else {
                $indent = " " * 4
            }
        }
        else {
            if ($messageSettings.FunctionName) {
                $functionName = "[$($Cmdlet.MyInvocation.MyCommand.Name)] "
            }
        }

        if ($messageSettings.Timestamp) {
            $timeStamp = "[$(Get-Date -f "HH:mm:ss")] "
        }

        WriteDebug ("{0}{1}{2}{3}" -f $timeStamp, $functionName, $indent, $Message)
    }

    end {
        $DebugPreference = $oldDebugPreference
    }
}

function Write-Verbose {
    <#
    .SYNOPSIS
        Write a verbose message
 
    .DESCRIPTION
        Write a verbose message
 
        This function allows the user to decide how the Verbose Message
        should be formatted. The configuration is inside `$scrpit:Configuration`
        and supports the following structure (json representation):
 
        {
            "message": {
                // show a line with the Bread Crumbs of the caller stack
                "breadcrumbs": true,
 
                // how many whitespaces should be used for indenting the
                // message
                "indent": true,
 
                // show the name of the calling function - this is ignored
                // if breadcrumbs is active
                "functionname": true,
 
                // show the timestamp (HH:mm:ss format) of the message
                "timestamp": true
            }
        }
 
    .EXAMPLE
        Write-DebugMessage "The value of `$var is: $var"
        ----------
        Description
        Shows the message if the user added `-Debug` to the command but does not create a breakpoint
    #>

    [CmdletBinding()]
    param(
        [Parameter( Mandatory, ValueFromPipeline )]
        [String]
        $Message,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCmdlet]
        $Cmdlet = ((Get-Variable -Scope 1 PSCmdlet).Value)
    )

    begin {
        $indent, $functionName, $timeStamp = ""

        Import-MqcnAlias -Alias "WriteVerbose" -Command "Microsoft.PowerShell.Utility\Write-Verbose"

        $messageSettings = $script:Configuration["Message"]
    }

    process {

        if ((Get-PSCallstack | Select-Object -Last 1 -Skip 1).Arguments.Contains("Verbose")) {
            $VerbosePreference = 'Continue'
        }

        if ($messageSettings.Breadcrumbs) {
            WriteVerbose "[$(Get-BreadCrumb)]:"

            if ($messageSettings.Indent) {
                $indent = " " * $messageSettings.Indent
            }
            else {
                $indent = " " * 4
            }
        }
        else {
            if ($messageSettings.FunctionName) {
                $functionName = "[$($Cmdlet.MyInvocation.MyCommand.Name)] "
            }
        }

        if ($messageSettings.Timestamp) {
            $timeStamp = "[$(Get-Date -f "HH:mm:ss")] "
        }

        WriteVerbose ("{0}{1}{2}{3}" -f $timeStamp, $functionName, $indent, $Message)
    }
}

function WriteError {
    <#
    .SYNOPSIS
        Utility to write an errorrecord to the errstd
    .NOTES
        Thanks to Jaykul:
        https://github.com/PoshCode/Configuration/blob/master/Source/Metadata.psm1
    #>

    param
    (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCmdlet]
        $Cmdlet = $((Get-Variable -Scope 1 PSCmdlet).Value),

        [Parameter(Mandatory = $true, ParameterSetName = "ExistingException", Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Parameter(ParameterSetName = "NewException")]
        [ValidateNotNullOrEmpty()]
        [System.Exception]
        $Exception,

        [Parameter(ParameterSetName = "NewException", Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ExceptionType = "System.Management.Automation.RuntimeException",

        [Parameter(Mandatory = $true, ParameterSetName = "NewException", Position = 3)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Message,

        [Parameter(Mandatory = $false)]
        [System.Object]
        $TargetObject,

        [Parameter(Mandatory = $true, Position = 10)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ErrorId,

        [Parameter(Mandatory = $true, Position = 11)]
        [ValidateNotNull()]
        [System.Management.Automation.ErrorCategory]
        $Category,

        [Parameter(Mandatory = $true, ParameterSetName = "Rethrow", Position = 1)]
        [System.Management.Automation.ErrorRecord]$ErrorRecord
    )

    process {
        if (!$ErrorRecord) {
            if ($PSCmdlet.ParameterSetName -eq "NewException") {
                if ($Exception) {
                    $Exception = New-Object $ExceptionType $Message, $Exception
                }
                else {
                    $Exception = New-Object $ExceptionType $Message
                }
            }
            $errorRecord = New-Object System.Management.Automation.ErrorRecord $Exception, $ErrorId, $Category, $TargetObject
        }
        $Cmdlet.WriteError($errorRecord)
    }
}