AppianPS.psm1

$Script:PSModuleRoot = $PSScriptRoot
$Script:ModuleName = "AppianPS"
$Script:APAppDataPath = [Environment]::GetFolderPath('ApplicationData')
$Script:ModuleDataRoot = (Join-Path -Path $Script:APAppDataPath -ChildPath $Script:ModuleName)
$Script:ModuleDataPath = (Join-Path -Path $Script:ModuleDataRoot -ChildPath "ModuleData.json")
if (-not (Test-Path $Script:ModuleDataRoot)) {New-Item -ItemType Directory -Path $Script:ModuleDataRoot -Force}
# Imported from [D:\a\1\s\AppianPS\Private]
# Get-APNApiEndpoint.ps1
function Get-APNApiEndpoint {
    <#
    .SYNOPSIS
 
    Returns the api uri endpoint.
 
    .DESCRIPTION
 
    Returns the api uri endpoint base on the api type.
 
    .PARAMETER ApiType
 
    Type of the api endpoint to use.
 
    .OUTPUTS
 
    String, The uri endpoint that will be used by Set-APNUri.
 
    .EXAMPLE
 
    Returns the api endpoint for 'deployments'
 
    Get-APApiEndpoint -ApiType deployments
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Deployment_Rest_API.html
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]
        $ApiType
    )

    begin {
    }

    process {
        Switch ($ApiType) {
            'deployments' {
                return '/suite/deployment-management/v1/deployments'
            }
            'deployments-id' {
                return '/suite/deployment-management/v1/deployments/{0}'
            }
            'deployments-log' {
                return '/suite/deployment-management/v1/deployments/{0}/log'
            }
            'inspections' {
                return '/suite/deployment-management/v1/inspections'
            }
            'inspections-id' {
                return '/suite/deployment-management/v1/inspections/{0}'
            }
            default {
                Write-Error "[$($MyInvocation.MyCommand.Name)]: [$ApiType] is not supported" -ErrorAction Stop
            }
        }
    }

    end {
    }
}

# Set-APNAuthenticationType.ps1
function Set-APNAuthenticationType { 
    <#
    .SYNOPSIS
 
    Sets the authentication type used by Invoke-APRestMethod.
 
    .DESCRIPTION
 
    Sets the authentication type used by Invoke-APRestMethod.
    Default authentication will use the pesonal access token that is stored in session data, unless a credential is provided.
 
    .PARAMETER InputObject
     
    The splat parameters used by Invoke-APRestMethod.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to send the request.
     
    .OUTPUTS
 
    PSObject, The modifed inputobject.
 
    .EXAMPLE
 
    Set-APAuthenticationType -InputObject $inputObject
 
    .EXAMPLE
 
    Sets the AP authentication to the credential provided for the input object.
 
    Set-APNAuthenticationType -InputObject $inputObject -Credential $pscredential
 
    .EXAMPLE
 
    Sets the AP authentication to the personal access token provided for the input object.
     
    Set-APNAuthenticationType -InputObject $inputObject -ApiKey $mySecureToken
 
    .LINK
 
    https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/authentication-guidance?view=vsts
    #>

    [CmdletBinding()]
    param 
    (
        [Parameter(Mandatory)]
        [PSObject]
        $InputObject,

        [Parameter()]
        [Security.SecureString]
        $ApiKey,

        [Parameter()]
        [pscredential]
        $Credential
    )

    begin {
    }

    process {
        If ($Credential) {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Authenticating with the provided credential."
            $InputObject.Credential = $Credential
        }
        elseIf ($ApiKey) {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Authenticating with the stored api key."
            $apiKeyToken = Unprotect-APNSecureApiKey -ApiKey $ApiKey
            $InputObject.Headers = @{'Appian-API-Key' = $apiKeyToken }
        }
        else {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Authenticating with default credentials"
            $InputObject.UseDefaultCredentials = $true
        }
    }

    end {
        return $InputObject
    }
}

# Set-APNUri.ps1
function Set-APNUri {
    <#
    .SYNOPSIS
 
    Sets the uri used by Invoke-APNRestMethod.
 
    .DESCRIPTION
 
    Sets the uri used by Invoke-APNRestMethod.
 
    .PARAMETER Domain
 
    The Appian site domain.
 
    .PARAMETER ApiEndpoint
 
    The api endpoint provided by Get-APNApiEndpoint.
 
    .OUTPUTS
 
    Uri, The uri that will be used by Invoke-APNRestMethod.
 
    .EXAMPLE
 
    Set-APNUri -Domain 'myAppianDomain' -ApiEndpoint suite/deployment-management/v1/deployments
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Deployment_Rest_API.html
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter()]
        [string]
        $Domain,

        [Parameter(Mandatory)]
        [string]
        $ApiEndpoint
    )

    begin {
    }

    process {
        return 'https://{0}{1}' -f $Domain, $ApiEndpoint
    }

    end {
    }
}

# Unprotect-APNSecureApiKey.ps1
Function Unprotect-APNSecureApiKey {
    <#
    .SYNOPSIS
 
    Returns decrypted personal access token.
 
    .DESCRIPTION
 
    Returns decrypted personal access token that is stored in the session data.
 
    .PARAMETER ApiKey
 
    Personal access token used to authenticate that has been converted to a secure string.
    It is recomended to uses an Azure Pipelines PS session to pass the personal access token parameter among funcitons, See New-APNSession.
    https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=vsts
         
    .OUTPUTS
 
    String, unsecure personal access token.
 
    .EXAMPLE
 
    Unprotects the personal access token from secure string to plain text.
 
    Unprotect-SecureApiKey -ApiKey $mySecureToken
 
    .LINK
 
    https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/authentication-guidance?view=vsts
    #>


    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [Security.SecureString]
        $ApiKey
    )
    Process {
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ApiKey)
        $plainText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

        if ([environment]::OSVersion.Platform -eq "Unix") {
            $plainText = [System.Net.NetworkCredential]::new("", $ApiKey).Password
        }

        return $plainText
    }
}

# Imported from [D:\a\1\s\AppianPS\Public]
# ConvertTo-APNMarkdownDeploymentReport.ps1
function ConvertTo-APNMarkdownDeploymentReport {
    <#
    .SYNOPSIS
 
    Converts Appian deployment or inspection results to a markdown table.
 
    .DESCRIPTION
 
    Converts Appian deployment or inspection results to a markdown table.
 
    .PARAMETER DeploymentResults
 
    The report to format.
 
    .PARAMETER DeploymentLog
 
    The deployment log to format
 
    .OUTPUTS
 
    String, The deployment or inspection results in markdown table format
 
    .EXAMPLE
 
    $report = Get-APNDeploymentResults -Session 'mySession -DeploymentId '834895a6-6d2f-4180-b396-b9ifb4f38b0f'
    $log = Get-APNDeploymentLog -Session 'mySession' -DeploymentId '834895a6-6d2f-4180-b396-b9ifb4f38b0f'
    ConvertTo-APNMarkdownDeploymentReport -DeploymentReport $report -DeploymentLog $log
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Deployment_Rest_API.html
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [PSObject[]]
        $DeploymentResults,

        [Parameter()]
        [string]
        $DeploymentLog
    )

    begin {
        $databaseScripts = $DeploymentResults.summary.databaseScripts
        $objects = $DeploymentResults.summary.objects
    }

    process {
        '# Deployment Results'
        '## Database Scripts'
        "- Total $databaseScripts"
        '## Objects Expected'
        "- Total $($objects.Total)"
        "- Imported $($objects.Imported)"
        "- Failed $($objects.Failed)"
        "- Skipped $($objects.Skipped)"
        # Log
        '# Deployment Log'
        $Log
    }

    end {

    }
}

# ConvertTo-APNMarkdownInspectionReport.ps1
function ConvertTo-APNMarkdownInspectionReport {
    <#
    .SYNOPSIS
 
    Converts Appian inspection results to a markdown table.
 
    .DESCRIPTION
 
    Converts Appian inspection results to a markdown table.
 
    .PARAMETER InputObject
 
    The input object to convert.
 
    .OUTPUTS
 
    String, The inspection results in markdown table format
 
    .EXAMPLE
    $content = Get-Content -Path $pathToFile
    ConvertTo-APNMarkdownInspectionReport -InputObject $content
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Deployment_Rest_API.html
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [PSObject[]]
        $InputObject
    )

    begin {
        $objectsExpected = $InputObject.summary.objectsExpected
        $problems = $InputObject.summary.problems
        $errorRows = @()
        $errorColumns = @{ }
        $errorHeaderOrder = @(
            'objectName'
            'errorMessage'
            'objectUuid'
        )
        $warningRows = @()
        $warningColumns = @{ }
        $warningHeaderOrder = @(
            'objectName'
            'warningMessage'
            'objectUuid'
        )
    }

    process {
        foreach ($row in $problems.errors) {
            $errorRows += $row
            foreach ($property in $row.PSObject.Properties) {
                if ($null -ne $property.Value) {
                    if (-not $errorColumns.ContainsKey($property.Name) -or $errorColumns[$property.Name] -lt $property.Value.ToString().Length) {
                        $errorColumns[$property.Name] = $property.Value.ToString().Length
                    }
                }
            }
        }
        $errorRows = $errorRows | Sort-Object 'objectName'

        foreach ($row in $problems.warnings) {
            $warningRows += $row
            foreach ($property in $row.PSObject.Properties) {
                if ($null -ne $property.Value) {
                    if (-not $warningColumns.ContainsKey($property.Name) -or $warningColumns[$property.Name] -lt $property.Value.ToString().Length) {
                        $warningColumns[$property.Name] = $property.Value.ToString().Length
                    }
                }
            }
        }
        $warningRows = $warningRows | Sort-Object 'objectName'
    }

    end {
        # Summary
        '# Summary'
        '## Objects Expected'
        "- Total $($objectsExpected.Total)"
        "- Imported $($objectsExpected.Imported)"
        "- Failed $($objectsExpected.Failed)"
        "- Skipped $($objectsExpected.Skipped)"
        # Problems
        '# Problems'
        "- Total Errors $($problems.totalErrors)"
        "- Total Warnings $($problems.totalWarnings)"
        If ($problems.totalErrors -gt 0) {
            '## Errors'
            # Column width
            foreach ($key in $($errorColumns.Keys)) {
                $errorColumns[$key] = [Math]::Max($errorColumns[$key], $key.Length)
            }
            # Header
            $header = @()
            $sortedKeys = $errorColumns.Keys | Sort-Object { $errorHeaderOrder.IndexOf($PSitem) }
            foreach ($key in $sortedKeys) {
                $header += ('{0,-' + $errorColumns[$key] + '}') -f $key
            }
            $header -join ' | '
            # Separator
            $separator = @()
            foreach ($key in $sortedKeys) {
                $separator += '-' * $errorColumns[$key]
            }
            $separator -join ' | '
            # Rows
            foreach ($row in $errorRows) {
                $values = @()
                foreach ($key in $sortedKeys) {
                    $values += ('{0,-' + $errorColumns[$key] + '}') -f $row.($key)
                }
                $values -join ' | '
            }
        }
        If ($problems.totalWarnings -gt 0) {
            '## Warnings'
            foreach ($key in $($warningColumns.Keys)) {
                $warningColumns[$key] = [Math]::Max($warningColumns[$key], $key.Length)
            }
            # Header
            $header = @()
            $sortedKeys = $warningColumns.Keys | Sort-Object { $warningHeaderOrder.IndexOf($PSitem) }
            foreach ($key in $sortedKeys) {
                $header += ('{0,-' + $warningColumns[$key] + '}') -f $key
            }
            $header -join ' | '
            # Separator
            $separator = @()
            foreach ($key in $sortedKeys) {
                $separator += '-' * $warningColumns[$key]
            }
            $separator -join ' | '
            # Rows
            foreach ($row in $warningRows) {
                $values = @()
                foreach ($key in $sortedKeys) {
                    $values += ('{0,-' + $warningColumns[$key] + '}') -f $row.($key)
                }
                $values -join ' | '
            }
        }

    }
}

# Get-APNDeploymentLog.ps1
function Get-APNDeploymentLog {
    <#
    .SYNOPSIS
 
    Returns Appian deployment log.
 
    .DESCRIPTION
 
    Returns Appian deployment log based on the deployment id.
    The deployment id is returned in the response of New-APNDeployment.
 
    .PARAMETER Domain
 
    The Appian site domain.
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external deployments.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to send the request.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Session
 
    Azure DevOps PS session, created by New-APNSession.
 
    .PARAMETER DeploymentId
 
    The id of the deployment
 
    .PARAMETER Wait
 
    Flag to wait for summary status to not be 'IN_PROGRESS'.
 
    .PARAMETER TimeoutSeconds
 
    The number of seconds to wait before timing out. Defaults to 120.
 
    .PARAMETER SleepSeconds
 
    The number of seconds to sleep inbetween requests. Defaults to 1.
 
    .INPUTS
 
    None, does not support pipeline.
 
    .OUTPUTS
 
    String, Appian deployment log.
 
    .EXAMPLE
 
    Returns the Appian deployment log.
 
    Get-APNDeploymentLog -Domain 'myAppianDomain' -ApiKey '*******' -DeploymentId '834895a6-6d2f-4180-b396-b9ifb4f38b0f'
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Get_Deployment_Log_API.html
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByApiKey')]
    Param
    (
        [Parameter(Mandatory,
            ParameterSetName = 'ByApiKey')]
        [Parameter(Mandatory,
            ParameterSetName = 'ByCredential')]
        [string]
        $Domain,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Security.SecureString]
        $ApiKey,

        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $Credential,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [string]
        $Proxy,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $ProxyCredential,

        [Parameter(Mandatory,
            ParameterSetName = 'BySession')]
        [object]
        $Session,

        [Parameter(Mandatory)]
        [string]
        $DeploymentId,

        [Parameter()]
        [bool]
        $Wait,

        [Parameter()]
        [int]
        $TimeoutSeconds = 120,

        [Parameter()]
        [int]
        $SleepSeconds = 1
    )

    begin {
        $timeoutIterations = $TimeoutSeconds / $SleepSeconds
        If ($PSCmdlet.ParameterSetName -eq 'BySession') {
            $currentSession = $Session | Get-APNSession
            If ($currentSession) {
                $Domain = $currentSession.Domain
                $ApiKey = $currentSession.ApiKey
                $Credential = $currentSession.Credential
                $Proxy = $currentSession.Proxy
                $ProxyCredential = $currentSession.ProxyCredential
            }
        }
    }

    process {
        $apiEndpoint = (Get-APNApiEndpoint -ApiType 'deployments-log') -f $DeploymentId
        $setAPNUriSplat = @{
            Domain      = $Domain
            ApiEndpoint = $apiEndpoint
        }
        [uri] $uri = Set-APNUri @setAPNUriSplat
        $invokeAPNRestMethodSplat = @{
            Method          = 'GET'
            Uri             = $uri
            Credential      = $Credential
            ApiKey          = $ApiKey
            Proxy           = $Proxy
            ProxyCredential = $ProxyCredential
        }
        $results = Invoke-APNRestMethod @invokeAPNRestMethodSplat
        If ($Wait) {
            $i = 0
            while (($results.Status -eq 'IN_PROGRESS') -or ($results.Status -eq 'PENDING_REVIEW')) {
                Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Sleeping for $SleepSeconds seconds"
                Start-Sleep -Seconds $SleepSeconds
                $i = $i + 1
                If ($i -gt $timeoutIterations) {
                    Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Timeout reached it's maximum of $TimeoutSeconds seconds."
                    break
                }
                $results = Get-APNDeploymentLog @PSBoundParameters
            }
        }
        return $results
    }
    

    end {
    }
}
# Get-APNDeploymentResults.ps1
function Get-APNDeploymentResults {
    <#
    .SYNOPSIS
 
    Returns Appian deployment results.
 
    .DESCRIPTION
 
    Returns Appian deployment results based on the deployment id.
    The deployment id is returned in the response of New-APNDeployment.
 
    .PARAMETER Domain
 
    The Appian site domain.
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external deployments.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to send the request.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Session
 
    Azure DevOps PS session, created by New-APNSession.
 
    .PARAMETER DeploymentId
 
    The id of the deployment.
 
    .PARAMETER Wait
 
    Flag to wait for summary status to not be 'IN_PROGRESS'.
 
    .PARAMETER TimeoutSeconds
 
    The number of seconds to wait before timing out. Defaults to 120.
 
    .PARAMETER SleepSeconds
 
    The number of seconds to sleep inbetween requests. Defaults to 1.
 
    .INPUTS
 
    None, does not support pipeline.
 
    .OUTPUTS
 
    String, Appian deployment results.
 
    .EXAMPLE
 
    Returns the Appian deployment results.
 
    Get-APNDeploymentResults -Domain 'myAppianDomain' -ApiKey '*******' -DeploymentId '834895a6-6d2f-4180-b396-b9ifb4f38b0f'
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Get_Deployment_Results_API.html
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByApiKey')]
    Param
    (
        [Parameter(Mandatory,
            ParameterSetName = 'ByApiKey')]
        [Parameter(Mandatory,
            ParameterSetName = 'ByCredential')]
        [string]
        $Domain,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Security.SecureString]
        $ApiKey,

        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $Credential,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [string]
        $Proxy,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $ProxyCredential,

        [Parameter(Mandatory,
            ParameterSetName = 'BySession')]
        [object]
        $Session,

        [Parameter(Mandatory)]
        [string]
        $DeploymentId,

        [Parameter()]
        [bool]
        $Wait,

        [Parameter()]
        [int]
        $TimeoutSeconds = 120,

        [Parameter()]
        [int]
        $SleepSeconds = 1
    )

    begin {
        $timeoutIterations = $TimeoutSeconds / $SleepSeconds
        If ($PSCmdlet.ParameterSetName -eq 'BySession') {
            $currentSession = $Session | Get-APNSession
            If ($currentSession) {
                $Domain = $currentSession.Domain
                $ApiKey = $currentSession.ApiKey
                $Credential = $currentSession.Credential
                $Proxy = $currentSession.Proxy
                $ProxyCredential = $currentSession.ProxyCredential
            }
        }
    }

    process {
        $apiEndpoint = (Get-APNApiEndpoint -ApiType 'deployments-id') -f $DeploymentId
        $setAPNUriSplat = @{
            Domain      = $Domain
            ApiEndpoint = $apiEndpoint
        }
        [uri] $uri = Set-APNUri @setAPNUriSplat
        $invokeAPNRestMethodSplat = @{
            Method          = 'GET'
            Uri             = $uri
            Credential      = $Credential
            ApiKey          = $ApiKey
            Proxy           = $Proxy
            ProxyCredential = $ProxyCredential
        }
        $results = Invoke-APNRestMethod @invokeAPNRestMethodSplat
        If ($Wait) {
            $i = 0
            while (($results.Status -eq 'IN_PROGRESS') -or ($results.Status -eq 'PENDING_REVIEW')) {
                Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Sleeping for $SleepSeconds seconds"
                Start-Sleep -Seconds $SleepSeconds
                $i = $i + 1
                If ($i -gt $timeoutIterations) {
                    Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Timeout reached it's maximum of $TimeoutSeconds seconds."
                    break
                }
                $results = Get-APNDeploymentResults @PSBoundParameters
            }
        }
        return $results
    }

    end {
    }
}
# Get-APNInspectionResults.ps1
function Get-APNInspectionResults {
    <#
    .SYNOPSIS
 
    Returns Appian inspection results.
 
    .DESCRIPTION
 
    Returns Appian inspection results based on the inspection id.
    The inspection id is returned in the response of New-APNinspection.
 
    .PARAMETER Domain
 
    The Appian site domain.
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external deployments.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to send the request.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Session
 
    Azure DevOps PS session, created by New-APNSession.
 
    .PARAMETER InspectionId
 
    The id of the inspection
 
    .PARAMETER Wait
 
    Flag to wait for summary status to not be 'IN_PROGRESS'.
 
    .PARAMETER TimeoutSeconds
 
    The number of seconds to wait before timing out. Defaults to 120.
 
    .PARAMETER SleepSeconds
 
    The number of seconds to sleep inbetween requests. Defaults to 1.
 
    .INPUTS
 
    None, does not support pipeline.
 
    .OUTPUTS
 
    String, Appian inspection results.
 
    .EXAMPLE
 
    Returns the Appian inspection results.
 
    Get-APNInspectionResults -Domain 'myAppianDomain' -ApiKey '*******' -InspectionId '834895a6-6d2f-4180-b396-b9ifb4f38b0f'
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Get_Inspection_Results_API.html
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByApiKey')]
    Param
    (
        [Parameter(Mandatory,
            ParameterSetName = 'ByApiKey')]
        [Parameter(Mandatory,
            ParameterSetName = 'ByCredential')]
        [string]
        $Domain,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Security.SecureString]
        $ApiKey,

        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $Credential,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [string]
        $Proxy,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $ProxyCredential,

        [Parameter(Mandatory,
            ParameterSetName = 'BySession')]
        [object]
        $Session,

        [Parameter(Mandatory)]
        [string]
        $InspectionId,

        [Parameter()]
        [bool]
        $Wait,

        [Parameter()]
        [int]
        $TimeoutSeconds = 120,

        [Parameter()]
        [int]
        $SleepSeconds = 1
    )

    begin {
        $timeoutIterations = $TimeoutSeconds / $SleepSeconds
        If ($PSCmdlet.ParameterSetName -eq 'BySession') {
            $currentSession = $Session | Get-APNSession
            If ($currentSession) {
                $Domain = $currentSession.Domain
                $ApiKey = $currentSession.ApiKey
                $Credential = $currentSession.Credential
                $Proxy = $currentSession.Proxy
                $ProxyCredential = $currentSession.ProxyCredential
            }
        }
    }

    process {
        $apiEndpoint = (Get-APNApiEndpoint -ApiType 'inspections-id') -f $InspectionId
        $setAPNUriSplat = @{
            Domain      = $Domain
            ApiEndpoint = $apiEndpoint
        }
        [uri] $uri = Set-APNUri @setAPNUriSplat
        $invokeAPNRestMethodSplat = @{
            Method          = 'GET'
            Uri             = $uri
            Credential      = $Credential
            ApiKey          = $ApiKey
            Proxy           = $Proxy
            ProxyCredential = $ProxyCredential
        }
        $results = Invoke-APNRestMethod @invokeAPNRestMethodSplat
        If ($Wait) {
            $i = 0
            while ($results.Status -eq 'IN_PROGRESS') {
                Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Sleeping for $SleepSeconds seconds"
                Start-Sleep -Seconds $SleepSeconds
                $i = $i + 1
                If ($i -gt $timeoutIterations) {
                    Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Timeout reached it's maximum of $TimeoutSeconds seconds."
                    break
                }
                $results = Get-APNInspectionResults @PSBoundParameters
            }
        }
        return $results
    }

    end {
    }
}
# Get-APNSession.ps1
Function Get-APNSession {
    <#
    .SYNOPSIS
 
    Returns Appian session data.
 
    .DESCRIPTION
 
    Returns Appian session data that has been stored in the users local application data.
    Use Save-APNSession to persist the session data to disk.
    The sensetive data is returned encrypted.
 
    .PARAMETER Id
 
    Session id.
 
    .PARAMETER SessionName
 
    The friendly name of the session.
 
    .PARAMETER Path
 
    The path where session data will be stored, defaults to $Script:ModuleDataPath.
 
    .LINK
 
    Save-APNSession
    Remove-APNSession
 
    .INPUTS
 
    None, does not support pipeline.
 
    .OUTPUTS
 
    PSObject. Get-APNSession returns a PSObject that contains the following:
        Domain
        ApiKey
 
    .EXAMPLE
 
    Returns all Appian sessions saved or in memory.
 
    Get-APNSession
 
    .EXAMPLE
 
    Returns Appian session with the session name of 'myFirstSession'.
 
    Get-APNSession -SessionName 'myFirstSession'
    #>


    [CmdletBinding()]
    Param
    (
        [Parameter(ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [string]
        $SessionName,

        [Parameter(ParameterSetName = 'ById', 
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [int]
        $Id,

        [Parameter()]
        [string]
        $Path = $Script:ModuleDataPath
    )
    Process {
        # Process memory sessions
        $_sessions = @()
        If ($null -ne $Global:_APNSessions) {
            Foreach ($_memSession in $Global:_APNSessions) {
                $_sessions += $_memSession
            }
        }
        
        # Process saved sessions
        If (Test-Path $Path) {
            $data = Get-Content -Path $Path -Raw | ConvertFrom-Json
            Foreach ($_data in $data.SessionData) {
                $_object = New-Object -TypeName PSCustomObject -Property @{
                    Id          = $_data.Id
                    Domain      = $_data.Domain
                    SessionName = $_data.SessionName
                    Saved       = $_data.Saved
                }
                If ($_data.ApiKey) {
                    $_object | Add-Member -NotePropertyName 'ApiKey' -NotePropertyValue ($_data.ApiKey | ConvertTo-SecureString)
                }
                If ($_data.Credential) {
                    $_psCredentialObject = [pscredential]::new($_data.Credential.Username, ($_data.Credential.Password | ConvertTo-SecureString))
                    $_object | Add-Member -NotePropertyName 'Credential' -NotePropertyValue $_psCredentialObject
                }
                If ($_data.Proxy) {
                    $_object | Add-Member -NotePropertyName 'Proxy' -NotePropertyValue $_data.Proxy
                }
                If ($_data.ProxyCredential) {
                    $_psProxyCredentialObject = [pscredential]::new($_data.ProxyCredential.Username, ($_data.ProxyCredential.Password | ConvertTo-SecureString))
                    $_object | Add-Member -NotePropertyName 'ProxyCredential' -NotePropertyValue $_psProxyCredentialObject
                }
                $_sessions += $_object
            }
        }
        If ($PSCmdlet.ParameterSetName -eq 'ById') {
            $_sessions = $_sessions | Where-Object { $PSItem.Id -eq $Id }
        }
        If ($SessionName) {
            $_sessions = $_sessions | Where-Object { $PSItem.SessionName -eq $SessionName }
            If (-not($_sessions)) {
                Write-Error "[$($MyInvocation.MyCommand.Name)]: Unable to locate a session by the name of [$SessionName]" -ErrorAction 'Stop'
            }
        }
        return $_sessions
    }
}
# Invoke-APNPackageDeployment.ps1
function Invoke-APNPackageDeployment {
    <#
    .SYNOPSIS
 
    Starts the Appian package deployment process.
 
    .DESCRIPTION
 
    Starts the Appian package deployment process by running the following commands.
        1. New-APNPackageDeployment - Creates a package deployment request
        2. Get-APNDeploymentResults - Waits for the deployment results, then returns the summary
        3. ConvertTo-APNMarkdownTable - Converts the summary to a markdown table and writes it to file.
 
    .PARAMETER Domain
 
    The Appian site domain.
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external deployments.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to send the request.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Name
 
    Name of the deployment. This name will appear in the deployments view in Appian Designer.
 
    .PARAMETER Description
 
    Description of the deployment. This description will appear in the deployments view in Appian Designer.
 
    .PARAMETER Session
 
    Azure DevOps PS session, created by New-APNSession.
 
    .PARAMETER PackageFilePath
 
    The local path to the package.
 
    .PARAMETER CustomizationFilePath
 
    The local path to the import customization file (.properties).
 
    .PARAMETER DataSource
 
    Name or UUID of the data source. If the data source is connected through the Administration Console, use the value in the Name field. If the data source is connected through a data source connected system, use the UUID of the connected system.
 
    .PARAMETER DatabaseScriptPath
 
    One or multiple paths to a script file; scripts will be executed in alphabetical order.
 
    .PARAMETER OutputFormat
 
    Format to use when outputing the deployment results. PSObject or Markdown. Defaults to PSObject.
 
    .PARAMETER OutputPath
 
    The file path to write the summary report to. Only used when OutputFormat is set to Markdown.
 
    .PARAMETER Wait
 
    Flag to wait for summary status to not be 'In Progress'.
 
    .PARAMETER TimeoutSeconds
 
    The number of seconds to wait before timing out. Defaults to 120.
 
    .PARAMETER SleepSeconds
 
    The number of seconds to sleep inbetween requests. Defaults to 1.
 
    .INPUTS
 
    None, does not support pipeline.
 
    .OUTPUTS
 
    PSObject, Appian deployment object
 
    .EXAMPLE
 
    Invokes the Appian package deployment process.
 
    $splat = @{
        Session = 'mySession'
        PackageFilePath = ".\myZipPackage.zip"
        CustomizationFilePath = '.\DEV.properties'
        OutputFormat = 'Markdown'
        OutputPath = '.\Report.md'
        Verbose = $true
    }
    Invoke-APNPackageDeployment @splat
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Inspect_Package_API.html
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByApiKey')]
    Param
    (
        [Parameter(Mandatory,
            ParameterSetName = 'ByApiKey')]
        [Parameter(Mandatory,
            ParameterSetName = 'ByCredential')]
        [string]
        $Domain,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Security.SecureString]
        $ApiKey,

        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $Credential,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [string]
        $Proxy,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $ProxyCredential,

        [Parameter(Mandatory,
            ParameterSetName = 'BySession')]
        [object]
        $Session,

        [Parameter(Mandatory)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Description,

        [Parameter(Mandatory)]
        [string]
        $PackageFilePath,

        [Parameter()]
        [string]
        $CustomizationFilePath,

        [Parameter()]
        [string]
        $DataSource,

        [Parameter()]
        [string[]]
        $DatabaseScriptPath,

        [Parameter()]
        [ValidateSet('PSObject', 'Markdown')]
        [string]
        $OutputFormat = 'PSObject',

        [Parameter()]
        [string]
        $OutputPath,

        [Parameter()]
        [bool]
        $Wait,

        [Parameter()]
        [int]
        $TimeoutSeconds = 120,

        [Parameter()]
        [int]
        $SleepSeconds = 1
    )

    begin {
        If ($PSCmdlet.ParameterSetName -eq 'BySession') {
            $currentSession = $Session | Get-APNSession
            If ($currentSession) {
                $Domain = $currentSession.Domain
                $ApiKey = $currentSession.ApiKey
                $Credential = $currentSession.Credential
                $Proxy = $currentSession.Proxy
                $ProxyCredential = $currentSession.ProxyCredential
            }
        }
    }

    process {
        $splat = @{
            Domain          = $Domain
            Proxy           = $Proxy
            ProxyCredential = $ProxyCredential
        }
        If ($ApiKey) {
            $splat.ApiKey = $ApiKey
        }
        If ($Credential) {
            $splat.Credential = $Credential
        }
        Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Invoking New-APNPackageDeployment"
        $results = New-APNPackageDeployment @splat -Name $Name -Description $Description -PackageFilePath $PackageFilePath -CustomizationFilePath $CustomizationFilePath -DataSource $DataSource -DatabaseScriptPath $DatabaseScriptPath
        Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Invoking Get-APNDeploymentResults"
        $report = Get-APNDeploymentResults @splat -DeploymentId $results.UUID -Wait $true
        Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Invoking Get-APNDeploymentLog"
        $log = Get-APNDeploymentLog @splat -DeploymentId $results.UUID
        If ($OutputFormat -eq 'Markdown') {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Invoking ConvertTo-APNMarkdownDeploymentReport"
            $formattedReport = ConvertTo-APNMarkdownDeploymentReport -DeploymentResults $report -DeploymentLog $log
            If ($OutputPath) {
                $formattedReport | Out-File $OutputPath
            }
        }
        return @{
            report = $report
            log    = $log
            UUID   = $results.UUID
        }
   
    }

    end {
    }
}
# Invoke-APNPackageInspection.ps1
function Invoke-APNPackageInspection {
    <#
    .SYNOPSIS
 
    Starts the Appian package inspection process.
 
    .DESCRIPTION
 
    Starts the Appian package inspection process by running the following commands.
        1. New-APNPackageInspection - Creates a package inspection request
        2. Get-APNInspectionResults - Waits for the inspection results, then returns the summary
        3. ConvertTo-APNMarkdownTable - Converts the summary to a markdown table and writes it to file.
 
    .PARAMETER Domain
 
    The Appian site domain.
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external inspections.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to send the request.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Session
 
    Azure DevOps PS session, created by New-APNSession.
 
    .PARAMETER PackageFilePath
 
    The local path to the package.
 
    .PARAMETER CustomizationFilePath
 
    The local path to the import customization file (.properties).
 
    .PARAMETER OutputFormat
 
    Format to use when outputing the inspection results. PSObject or Markdown. Defaults to PSObject.
 
    .PARAMETER OutputPath
 
    The file path to write the summary report to. Only used when OutputFormat is set to Markdown.
 
    .PARAMETER Wait
 
    Flag to wait for summary status to not be 'In Progress'.
 
    .PARAMETER TimeoutSeconds
 
    The number of seconds to wait before timing out. Defaults to 120.
 
    .PARAMETER SleepSeconds
 
    The number of seconds to sleep inbetween requests. Defaults to 1.
 
    .INPUTS
 
    None, does not support pipeline.
 
    .OUTPUTS
 
    PSObject, Appian inspection object
 
    .EXAMPLE
 
    Invokes the Appian package inspection process.
 
    $splat = @{
        Session = 'mySession'
        PackageFilePath = ".\myZipPackage.zip"
        CustomizationFilePath = '.\DEV.properties'
        OutputFormat = 'Markdown'
        OutputPath = '.\Report.md'
        Verbose = $true
    }
    Invoke-APNPackageInspection @splat
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Inspect_Package_API.html
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByApiKey')]
    Param
    (
        [Parameter(Mandatory,
            ParameterSetName = 'ByApiKey')]
        [Parameter(Mandatory,
            ParameterSetName = 'ByCredential')]
        [string]
        $Domain,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Security.SecureString]
        $ApiKey,

        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $Credential,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [string]
        $Proxy,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $ProxyCredential,

        [Parameter(Mandatory,
            ParameterSetName = 'BySession')]
        [object]
        $Session,

        [Parameter(Mandatory)]
        [string]
        $PackageFilePath,

        [Parameter()]
        [string]
        $CustomizationFilePath,

        [Parameter()]
        [ValidateSet('PSObject', 'Markdown')]
        [string]
        $OutputFormat = 'PSObject',

        [Parameter()]
        [string]
        $OutputPath,

        [Parameter()]
        [bool]
        $Wait,

        [Parameter()]
        [int]
        $TimeoutSeconds = 120,

        [Parameter()]
        [int]
        $SleepSeconds = 1
    )

    begin {
        If ($PSCmdlet.ParameterSetName -eq 'BySession') {
            $currentSession = $Session | Get-APNSession
            If ($currentSession) {
                $Domain = $currentSession.Domain
                $ApiKey = $currentSession.ApiKey
                $Credential = $currentSession.Credential
                $Proxy = $currentSession.Proxy
                $ProxyCredential = $currentSession.ProxyCredential
            }
        }
    }

    process {
        $splat = @{
            Domain          = $Domain
            Proxy           = $Proxy
            ProxyCredential = $ProxyCredential
        }
        If ($ApiKey) {
            $splat.ApiKey = $ApiKey
        }
        If ($Credential) {
            $splat.Credential = $Credential
        }
        Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Invoking New-APNPackageInspection"
        $results = New-APNPackageInspection @splat -PackageFilePath $PackageFilePath -CustomizationFilePath $CustomizationFilePath
        Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Invoking Get-APNInspectionResults"
        $report = Get-APNInspectionResults @splat -InspectionId $results.UUID -Wait $true
        If ($OutputFormat -eq 'Markdown') {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Invoking ConvertTo-APNMarkdownInspectionReport"
            $formattedReport = ConvertTo-APNMarkdownInspectionReport -InputObject $report
            If ($OutputPath) {
                $formattedReport | Out-File $OutputPath
            }
        }
        return @{
            report = $report
            UUID   = $results.UUID
        }
   
    }

    end {
    }
}
# Invoke-APNRestMethod.ps1
function Invoke-APNRestMethod {
    <#
    .SYNOPSIS
 
    Invokes an Appian rest method.
 
    .DESCRIPTION
 
    Invokes an Appian rest method.
 
    .PARAMETER Method
 
    Specifies the method used for the web request.
 
    .PARAMETER Body
 
    Specifies the body of the request. The body is the content of the request that follows the headers.
 
    .PARAMETER Form
 
    Converts a dictionary to a multipart/form-data submission. Form may not be used with Body. If ContentType is used, it's ignored.
 
    .PARAMETER ContentType
 
    Specifies the content type of the web request. If this parameter is omitted and the request method is POST, Invoke-RestMethod sets the content type to application/x-www-form-urlencoded. Otherwise, the content type is not specified in the call.
 
    .PARAMETER Uri
 
    Specifies the Uniform Resource Identifier (URI) of the Internet resource to which the web request is sent. This parameter supports HTTP, HTTPS, FTP, and FILE values.
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external deployments.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to send the request.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Path
 
    The directory to output files to.
 
    .PARAMETER Infile
 
    The fullname/path to the file that will be uploaded.
 
    .OUTPUTS
 
    System.Int64, System.String, System.Xml.XmlDocument, The output of the cmdlet depends upon the format of the content that is retrieved.
 
    .OUTPUTS
 
    PSObject, If the request returns JSON strings, Invoke-RestMethod returns a PSObject that represents the strings.
 
    .EXAMPLE
 
    NA
 
    .LINK
 
    https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-6
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]
        $Method,

        [Parameter()]
        [object]
        $Body,

        [Parameter()]
        [object]
        $Form,

        [Parameter(Mandatory)]
        [uri]
        $Uri,

        [Parameter()]
        [string]
        $ContentType,

        [Parameter()]
        [Security.SecureString]
        $ApiKey,

        [Parameter()]
        [pscredential]
        $Credential,

        [Parameter()]
        [string]
        $Proxy,

        [Parameter()]
        [pscredential]
        $ProxyCredential,

        [Parameter()]
        [string]
        $Path,

        [Parameter()]
        [string]
        $InFile
    )

    begin {
    }

    process {
        $invokeRestMethodSplat = @{
            Method          = $Method
            Uri             = $uri.AbsoluteUri
        }
        If ($Body) {
            $invokeRestMethodSplat.ContentType = $ContentType
        }
        If ($Body) {
            $invokeRestMethodSplat.Body = ConvertTo-Json -InputObject $Body -Depth 20
        }
        If ($Form) {
            $invokeRestMethodSplat.Form = $Form
        }
        If ($Proxy) {
            $invokeRestMethodSplat.Proxy = $Proxy
            If ($ProxyCredential) {
                $invokeRestMethodSplat.ProxyCredential = $ProxyCredential
            }
            else {
                $invokeRestMethodSplat.ProxyUseDefaultCredentials = $true
            }
        }
        If ($Path) {
            $invokeRestMethodSplat.OutFile = $Path
        }
        If ($InFile) {
            $invokeRestMethodSplat.InFile = $InFile
        }
        $authenticatedRestMethodSplat = Set-APNAuthenticationType -InputObject $invokeRestMethodSplat -Credential $Credential -ApiKey $ApiKey
        Write-Verbose "[$($MyInvocation.MyCommand.Name)]: Invoking $($uri.AbsoluteUri)"
        return Invoke-RestMethod @authenticatedRestMethodSplat
    }

    end {
    }
}

# New-APNPackageDeployment.ps1
function New-APNPackageDeployment {
    <#
    .SYNOPSIS
 
    Creates an Appian package deployment.
 
    .DESCRIPTION
 
    Creates an Appian package deployment.
 
    .PARAMETER Domain
 
    The Appian site domain.
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external deployments.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to send the request.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Session
 
    Azure DevOps PS session, created by New-APNSession.
 
    .PARAMETER Name
 
    Name of the deployment. This name will appear in the deployments view in Appian Designer.
 
    .PARAMETER Description
 
    Description of the deployment. This description will appear in the deployments view in Appian Designer.
 
    .PARAMETER PackageFilePath
 
    The local path to the package.
 
    .PARAMETER CustomizationFilePath
 
    The local path to the import customization file (.properties).
 
    .PARAMETER DataSource
 
    Name or UUID of the data source. If the data source is connected through the Administration Console, use the value in the Name field. If the data source is connected through a data source connected system, use the UUID of the connected system.
 
    .PARAMETER DatabaseScriptPath
 
    One or multiple paths to a script file; scripts will be executed in alphabetical order.
 
    .INPUTS
 
    None, does not support pipeline.
 
    .OUTPUTS
 
    PSObject, Appian deployment object
 
    .EXAMPLE
 
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Deploy_Package_API.html
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByApiKey')]
    Param
    (
        [Parameter(Mandatory,
            ParameterSetName = 'ByApiKey')]
        [Parameter(Mandatory,
            ParameterSetName = 'ByCredential')]
        [string]
        $Domain,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Security.SecureString]
        $ApiKey,

        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $Credential,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [string]
        $Proxy,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $ProxyCredential,

        [Parameter(Mandatory,
            ParameterSetName = 'BySession')]
        [object]
        $Session,

        [Parameter(Mandatory)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Description,

        [Parameter(Mandatory)]
        [string]
        $PackageFilePath,

        [Parameter()]
        [string]
        $CustomizationFilePath,

        [Parameter()]
        [string]
        $DataSource,

        [Parameter()]
        [string[]]
        $DatabaseScriptPath
    )

    begin {
        If ($PSCmdlet.ParameterSetName -eq 'BySession') {
            $currentSession = $Session | Get-APNSession
            If ($currentSession) {
                $Domain = $currentSession.Domain
                $ApiKey = $currentSession.ApiKey
                $Credential = $currentSession.Credential
                $Proxy = $currentSession.Proxy
                $ProxyCredential = $currentSession.ProxyCredential
            }
        }
    }

    process {
        $package = Get-Item -Path $PackageFilePath
        $json = @{
            name            = $Name
            packageFileName = $package.Name
        } 
        if ($Description) {
            $json.description = $Description
        }
        if ($DataSource) {
            $json.dataSource = $DataSource
        }
        $form = @{
            zipFile = $package
        }
        if ($CustomizationFilePath) {
            $propertiesFile = Get-Item -Path $CustomizationFilePath
            $json.customizationFileName = $propertiesFile.Name
            $form.propertiesFile = $propertiesFile
        }
        if ($DatabaseScriptPath) {
            $databaseScripts = @()
            $orderId = 1
            $scripts = Get-Item -Path $DatabaseScriptPath
            foreach ($script in $scripts | Sort-Object -Property 'Name') {
                $databaseScripts += @{
                    fileName = $script.Name
                    orderId  = $orderId
                }
                $form.$($script.name) = $script
                $orderId ++
            }
            $json.databaseScripts = $databaseScripts
        }
        $form.json = $json | ConvertTo-Json
        $apiEndpoint = Get-APNApiEndpoint -ApiType 'deployments'
        $setAPNUriSplat = @{
            Domain      = $Domain
            ApiEndpoint = $apiEndpoint
        }
        [uri] $uri = Set-APNUri @setAPNUriSplat
        $invokeAPNRestMethodSplat = @{
            Form            = $form
            Method          = 'POST'
            Uri             = $uri
            Credential      = $Credential
            ApiKey          = $ApiKey
            Proxy           = $Proxy
            ProxyCredential = $ProxyCredential
        }
        $results = Invoke-APNRestMethod @invokeAPNRestMethodSplat
        return $results
    }

    end {
    }
}
# New-APNPackageInspection.ps1
function New-APNPackageInspection {
    <#
    .SYNOPSIS
 
    Creates an Appian package inspection.
 
    .DESCRIPTION
 
    Creates an Appian package inspection.
 
    .PARAMETER Domain
 
    The Appian site domain.
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external inspections.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to send the request.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Session
 
    Azure DevOps PS session, created by New-APNSession.
 
    .PARAMETER PackageFilePath
 
    The local path to the package.
 
    .PARAMETER CustomizationFilePath
 
    The local path to the import customization file (.properties).
 
    .INPUTS
 
    None, does not support pipeline.
 
    .OUTPUTS
 
    PSObject, Appian inspection object
 
    .EXAMPLE
 
 
    .LINK
 
    https://docs.appian.com/suite/help/22.1/Inspect_Package_API.html
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByApiKey')]
    Param
    (
        [Parameter(Mandatory,
            ParameterSetName = 'ByApiKey')]
        [Parameter(Mandatory,
            ParameterSetName = 'ByCredential')]
        [string]
        $Domain,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Security.SecureString]
        $ApiKey,

        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $Credential,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [string]
        $Proxy,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $ProxyCredential,

        [Parameter(Mandatory,
            ParameterSetName = 'BySession')]
        [object]
        $Session,

        [Parameter(Mandatory)]
        [string]
        $PackageFilePath,

        [Parameter()]
        [string]
        $CustomizationFilePath
    )

    begin {
        If ($PSCmdlet.ParameterSetName -eq 'BySession') {
            $currentSession = $Session | Get-APNSession
            If ($currentSession) {
                $Domain = $currentSession.Domain
                $ApiKey = $currentSession.ApiKey
                $Credential = $currentSession.Credential
                $Proxy = $currentSession.Proxy
                $ProxyCredential = $currentSession.ProxyCredential
            }
        }
    }

    process {
        $package = Get-Item -Path $PackageFilePath
        $json = @{
            packageFileName = $package.Name
        } 
        $form = @{
            zipFile = $package
        }
        if ($CustomizationFilePath) {
            $propertiesFile = Get-Item -Path $CustomizationFilePath
            $json.customizationFileName = $propertiesFile.Name
            $form.propertiesFile = $propertiesFile
        }
        $form.json = $json | ConvertTo-Json
        $apiEndpoint = Get-APNApiEndpoint -ApiType 'inspections'
        $setAPNUriSplat = @{
            Domain      = $Domain
            ApiEndpoint = $apiEndpoint
        }
        [uri] $uri = Set-APNUri @setAPNUriSplat
        $invokeAPNRestMethodSplat = @{
            Form            = $form
            Method          = 'POST'
            Uri             = $uri
            Credential      = $Credential
            ApiKey          = $ApiKey
            Proxy           = $Proxy
            ProxyCredential = $ProxyCredential
        }
        $results = Invoke-APNRestMethod @invokeAPNRestMethodSplat
        return $results
    }

    end {
    }
}
# New-APNSession.ps1
Function New-APNSession {
    <#
    .SYNOPSIS
 
    Creates an Appian session.
 
    .DESCRIPTION
 
    Creates an Appian session.
    Use Save-APNSession to persist the session data to disk.
    Save the session to a variable to pass the session to other functions.
 
    .PARAMETER SessionName
 
    The friendly name of the session.
 
    .PARAMETER Domain
 
    The Appian site domain. Example 'mydomain.appiancloud.com'
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external inspections.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to the project.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Path
 
    The path where module data will be stored, defaults to $Script:ModuleDataPath.
 
    .LINK
 
    Save-APNSession
    Remove-APNSession
 
    .INPUTS
 
    None, does not support pipeline.
 
    .OUTPUTS
 
    PSObject. New-APNSession returns a PSObject that contains the following:
        Domain
        ApiKey
 
    .EXAMPLE
 
    Creates a Appian session names 'myFirstSession'
 
    New-APNSession -SessionName 'myFirstSession' -Domain 'myAppianDomain.appiancloud.com' -ApiKey '********'
#>


    [CmdletBinding(DefaultParameterSetName = 'ByApiKey')]
    Param
    (
        [Parameter(Mandatory)]
        [string]
        $SessionName,

        [Parameter(Mandatory)]
        [string]
        $Domain,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [string]
        $ApiKey,

        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $Credential,

        [Parameter()]
        [string]
        $Proxy,

        [Parameter()]
        [pscredential]
        $ProxyCredential,
        
        [Parameter()]
        [string]
        $Path = $Script:ModuleDataPath
    )
    Process {
        [int] $_sessionIdcount = (Get-APNSession | Sort-Object -Property 'Id' | Select-Object -Last 1 -ExpandProperty 'Id') + 1
        $_session = New-Object -TypeName PSCustomObject -Property @{
            Domain      = $Domain
            SessionName = $SessionName
            Id          = $_sessionIdcount
        }
        If ($ApiKey) {
            $securedPat = (ConvertTo-SecureString -String $ApiKey -AsPlainText -Force)
            $_session | Add-Member -NotePropertyName 'ApiKey' -NotePropertyValue $securedPat
        }
        If ($Credential) {
            $_session | Add-Member -NotePropertyName 'Credential' -NotePropertyValue $Credential
        }
        If ($Proxy) {
            $_session | Add-Member -NotePropertyName 'Proxy' -NotePropertyValue $Proxy
        }
        If ($ProxyCredential) {
            $_session | Add-Member -NotePropertyName 'ProxyCredential' -NotePropertyValue $ProxyCredential
        }
        If ($null -eq $Global:_APNSessions) {
            $Global:_APNSessions = @()
        }
        $Global:_APNSessions += $_session
        return $_session
    }
}

# Remove-APNSession.ps1
Function Remove-APNSession {
    <#
    .SYNOPSIS
 
    Removes an Appian session.
 
    .DESCRIPTION
 
    Removes an Appian session.
    If the session is saved, it will be removed from the saved sessions as well.
 
    .PARAMETER Id
 
    Session id.
 
    .PARAMETER Path
 
    The path where session data will be stored, defaults to $Script:ModuleDataPath.
 
    .LINK
 
    Save-APNSession
    Remove-APNSession
 
    .INPUTS
 
    PSObject. Get-APNSession
 
    .OUTPUTS
 
    None. Does not supply output.
 
    .EXAMPLE
 
    Deletes AP session with the id of '2'.
 
    Remove-APNSession -Id 2
 
    .EXAMPLE
 
    Deletes all AP sessions in memory and stored on disk.
 
    Remove-APNSession
 
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory,
            ValueFromPipelineByPropertyName)]
        [int]
        $Id,
       
        [Parameter()]
        [string]
        $Path = $Script:ModuleDataPath
    )
    Process {
        $sessions = Get-APNSession -Id $Id
        Foreach ($session in $sessions) {
            If ($session.Saved -eq $true) {
                $newData = @{SessionData = @() }
                $data = Get-Content -Path $Path -Raw | ConvertFrom-Json
                Foreach ($_data in $data.SessionData) {
                    If ($_data.Id -eq $session.Id) {
                        Continue
                    }
                    else {
                        $newData.SessionData += $_data
                    }
                }
                $newData | ConvertTo-Json -Depth 5 | Out-File -FilePath $Path
            }
            [array] $Global:_APNSessions = $Global:_APNSessions | Where-Object { $PSItem.Id -ne $session.Id }
        }
    }
}
# Save-APNSession.ps1
Function Save-APNSession {
    <#
    .SYNOPSIS
 
    Saves an Appian session to disk.
 
    .DESCRIPTION
 
    Saves an Appian session to disk.
    The sensetive data is encrypted and stored in the users local application data.
    These saved sessions will be available next time the module is imported.
 
    .PARAMETER Session
 
    Appian session, created by New-APNSession.
 
    .PARAMETER Path
     
    The path where session data will be stored, defaults to $Script:ModuleDataPath.
 
    .PARAMETER PassThru
     
    Returns the saved session object.
     
    .INPUTS
 
    PSbject. Get-APNSession, New-APNSession
 
    .OUTPUTS
 
    None. Save-APNSession does not generate any output.
 
    .EXAMPLE
 
    Creates a session with the name of 'myFirstSession' and saves it to disk.
 
    $newAPNSession = @{
        SessionName = 'myFirstSession'
        Domain = 'myAppianDomain.appiancloud.com'
        ApiKey = '********'
    }
    New-APNSession @newAPNSession | Save-APNSession
    #>


    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory,
            ValueFromPipeline)]
        [object]
        $Session,
       
        [Parameter()]
        [string]
        $Path = $Script:ModuleDataPath
    )
    Begin {
        If (-not(Test-Path $Path)) {
            $data = @{SessionData = @() }
        }
        else {
            $data = Get-Content -Path $Path -Raw | ConvertFrom-Json
        }
    }
    Process {
        If ($data.SessionData.Id -notcontains $session.Id) {
            $_object = @{
                Domain      = $session.Domain
                SessionName = $session.SessionName
                Id          = $Session.Id
                Saved       = $true
            }
            If ($Session.ApiKey) {
                $_object.ApiKey = ($Session.ApiKey | ConvertFrom-SecureString) 
            }
            If ($Session.Credential) {
                $_credentialObject = @{
                    Username = $Session.Credential.UserName
                    Password = ($Session.Credential.GetNetworkCredential().SecurePassword | ConvertFrom-SecureString)
                }
                $_object.Credential = $_credentialObject
            }
            If ($Session.Proxy) {
                $_object.Proxy = $Session.Proxy
            }
            If ($Session.ProxyCredential) {
                $_proxyCredentialObject = @{
                    Username = $Session.ProxyCredential.UserName
                    Password = ($Session.ProxyCredential.GetNetworkCredential().SecurePassword | ConvertFrom-SecureString)
                }
                $_object.ProxyCredential = $_proxyCredentialObject
            }
            $data.SessionData += $_object
            $session | Remove-APNSession -Path $Path
        }
    }
    End {
        $data | ConvertTo-Json -Depth 5 | Out-File -FilePath $Path
        Write-Verbose "[$($MyInvocation.MyCommand.Name)]: [$SessionName]: Session data has been stored at [$Path]"
    }
}

# Update-APNSession.ps1
Function Update-APNSession
{
    <#
    .SYNOPSIS
 
    Updates an Appian session.
 
    .DESCRIPTION
 
    Updates an Appian session.
    The sensetive data is encrypted and stored in the users local application data.
    These updated sessions are available immediately.
    If the session was previously saved is will remain saved.
 
    .PARAMETER SessionName
 
    The friendly name of the session.
 
    .PARAMETER Domain
 
    The Appian site domain. Example 'mydomain.appiancloud.com'
 
    .PARAMETER ApiKey
 
    The Appian api key. The API key can be created in the Appian Administration Console, and then configured to secure external inspections.
 
    .PARAMETER Credential
 
    Specifies a user account that has permission to the project.
 
    .PARAMETER Proxy
 
    Use a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
 
    .PARAMETER ProxyCredential
 
    Specifie a user account that has permission to use the proxy server that is specified by the -Proxy parameter. The default is the current user.
 
    .PARAMETER Path
 
    The path where module data will be stored, defaults to $Script:ModuleDataPath.
 
    .INPUTS
 
    PSbject. Get-APNSession, New-APNSession
 
    .OUTPUTS
 
    None. Update-APNSession does not generate any output.
 
    .EXAMPLE
     
    Updates the Appian session named 'myFirstSession'.
 
    Update-APNSession -SessionName 'myFirstSession' -ApiKey '*******'
    #>


    [CmdletBinding(DefaultParameterSetName = 'ByApiKey')]
    Param
    (
        [Parameter(Mandatory)]
        [string]
        $SessionName,

        [Parameter()]
        [string]
        $Domain,

        [Parameter(ParameterSetName = 'ByApiKey')]
        [string]
        $ApiKey,

        [Parameter(ParameterSetName = 'ByCredential')]
        [pscredential]
        $Credential,

        [Parameter()]
        [string]
        $Proxy,

        [Parameter()]
        [pscredential]
        $ProxyCredential,
        
        [Parameter()]
        [string]
        $Path = $Script:ModuleDataPath
    )
    Begin
    {

    }
    Process
    {
        $getAPSessionSplat = @{
            SessionName = $SessionName
        }
        If ($Id)
        {
            $getAPSessionSplat.Id = $Id
        }
        $existingSessions = Get-APNSession @getAPSessionSplat
        If ($existingSessions)
        {
            Foreach ($existingSession in $existingSessions)
            {
                $newAPSessionSplat = @{
                    SessionName = $SessionName
                }
                If ($Domain)
                {
                    $newAPSessionSplat.Domain = $Domain
                }
                else
                {
                    If ($existingSession.Domain)
                    {
                        $newAPSessionSplat.Domain = $existingSession.Domain
                    }
                }
                If ($ApiKey)
                {
                    $newAPSessionSplat.ApiKey = $ApiKey
                }
                else
                {
                    If ($existingSession.ApiKey)
                    {
                        $newAPSessionSplat.ApiKey = $existingSession.ApiKey
                    }
                }
                If ($Credential)
                {
                    $_credentialObject = @{
                        Username = $Session.Credential.UserName
                        Password = ($Session.Credential.GetNetworkCredential().SecurePassword | ConvertFrom-SecureString)
                    }
                    $newAPSessionSplat.Credential = $_credentialObject
                }
                else
                {
                    If ($existingSession.Credential)
                    {
                        $newAPSessionSplat.Credential = $existingSession.Credential
                    }
                }
                If ($Proxy)
                {
                    $newAPSessionSplat.Proxy = $Session.Proxy
                }
                else
                {
                    If ($existingSession.Proxy)
                    {
                        $newAPSessionSplat.Proxy = $existingSession.Proxy
                    }
                }
                If ($ProxyCredential)
                {
                    $_proxyCredentialObject = @{
                        Username = $Session.ProxyCredential.UserName
                        Password = ($Session.ProxyCredential.GetNetworkCredential().SecurePassword | ConvertFrom-SecureString)
                    }
                    $newAPSessionSplat.ProxyCredential = $_proxyCredentialObject
                }
                else
                {
                    If ($existingSession.ProxyCredential)
                    {
                        $newAPSessionSplat.ProxyCredential = $existingSession.ProxyCredential
                    }
                }
                If ($existingSession.Saved)
                {
                    $existingSession | Remove-APNSession -Path $Path
                    $session = New-APNSession @newAPSessionSplat | Save-APNSession
                }
                else
                {
                    $existingSession | Remove-APNSession -Path $Path
                    $session = New-APNSession @newAPSessionSplat
                }
            }
        }
        else
        {
            Write-Error "[$($MyInvocation.MyCommand.Name)]: Unable to locate an AP session with the name [$SessionName]." -ErrorAction Stop
        }
    }
    End
    {
    }
}

# Imported from [D:\a\1\s\AppianPS\Tests]