Public/Connect-AutomateAPI.ps1

function Connect-AutomateAPI {
<#
.SYNOPSIS
Connect to the Automate API.
.DESCRIPTION
Connects to the Automate API and returns a bearer token which when passed with each requests grants up to an hours worth of access.
.PARAMETER Server
The address to your Automate Server. Example 'rancor.hostedrmm.com'
.PARAMETER Credential
Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass
.PARAMETER ClientID
Takes the Client ID required by Automate v2020.11 for API access
.PARAMETER TwoFactorToken
Takes a string that represents the 2FA number
.PARAMETER AuthorizationToken
Used internally when quietly refreshing the Token
.PARAMETER SkipCheck
Used internally when quietly refreshing the Token
.PARAMETER Verify
Specifies to test the current token, and if it is not valid attempt to obtain a new one using the current credentials. Does not refresh (re-issue) the current token.
.PARAMETER Force
Will not attempt to refresh a current session. Force new connection with new parameters.
.PARAMETER Quiet
Will not output any standard messages. Returns $True if connection was successful.
.OUTPUTS
Three strings into Script variables, $CWAServer containing the server address, $CWACredentials containing the bearer token and $CWACredentialsExpirationDate containing the date the credentials expire
.NOTES
Version: 1.2.0
Author: Gavin Stone
Creation Date: 2019-01-20
Purpose/Change: Initial script development
 
Update Date: 2019-02-12
Author: Darren White
Purpose/Change: Credential and 2FA prompting is only if needed. Supports Token Refresh.
 
Update Date: 2020-08-01
Purpose/Change: Change to use CWAIsConnected script variable to track connection state
 
Update Date: 2020-11-19
Author: Brandon Fahnestock
Purpose/Change: ConnectWise Automate v2020.11 requires a registered ClientID for API access. Added Support for ClientIDs
 
.EXAMPLE
Connect-AutomateAPI -Server "rancor.hostedrmm.com" -Credential $CredentialObject -TwoFactorToken "999999" -ClientID '123123123-1234-1234-1234-123123123123'
 
.EXAMPLE
Connect-AutomateAPI -Quiet
#>

    [CmdletBinding(DefaultParameterSetName = 'refresh')]
    param (
        [Parameter(ParameterSetName = 'credential', Mandatory = $False)]
        [System.Management.Automation.PSCredential]$Credential,

        [Parameter(ParameterSetName = 'credential', Mandatory = $False)]
        [Parameter(ParameterSetName = 'refresh', Mandatory = $False)]
        [Parameter(ParameterSetName = 'verify', Mandatory = $False)]
        [String]$ClientID = $Script:CWAClientID,

        [Parameter(ParameterSetName = 'credential', Mandatory = $False)]
        [Parameter(ParameterSetName = 'refresh', Mandatory = $False)]
        [Parameter(ParameterSetName = 'verify', Mandatory = $False)]
        [String]$Server = $Script:CWAServer,

        [Parameter(ParameterSetName = 'refresh', Mandatory = $False)]
        [Parameter(ParameterSetName = 'verify', Mandatory = $False)]
        [String]$AuthorizationToken = ($Script:CWAToken.Authorization -replace 'Bearer ',''),

        [Parameter(ParameterSetName = 'refresh', Mandatory = $False)]
        [Parameter(ParameterSetName = 'credential', Mandatory = $False)]
        [Switch]$SkipCheck,

        [Parameter(ParameterSetName = 'verify', Mandatory = $False)]
        [Switch]$Verify,

        [Parameter(ParameterSetName = 'credential', Mandatory = $False)]
        [String]$TwoFactorToken,

        [Parameter(ParameterSetName = 'credential', Mandatory = $False)]
        [Switch]$Force,

        [Parameter(ParameterSetName = 'credential', Mandatory = $False)]
        [Parameter(ParameterSetName = 'refresh', Mandatory = $False)]
        [Parameter(ParameterSetName = 'verify', Mandatory = $False)]
        [Switch]$Quiet
    )

    Begin {
        If ($TwoFactorToken -match '.+') {$Force=$True}
        $TwoFactorNeeded=$False

        If (!$Quiet) {
            If ($Force) {$Server=$Null}
            While (!($Server -match '.+')) {
                $Server = Read-Host -Prompt "Please enter your Automate Server address, IE: rancor.hostedrmm.com" 
            }
            If (!($ClientID -match '.+')) {
                $ClientID = Read-Host -Prompt "Please enter API Client ID (Required for 2020.P11 and above)" 
            }
        }
        $Server = $Server -replace '^https?://','' -replace '/[^\/]*$',''
        $testAuthorizationToken = $AuthorizationToken -replace 'Bearer ',''
        $Script:CWAIsConnected=$False
        If ($ClientID -notmatch '.+') {
            $ClientID=$Null
            Write-Warning "API ClientID is missing or in invalid format."
        }

    } #End Begin
    
    Process {
        If (!($Server -match '^[a-z0-9][a-z0-9\.\-\/]*$')) {Throw "Server address ($Server) is missing or in invalid format."; Return}
        $AutomateAPIURI = ('https://' + $Server + '/cwa/api/v1')
        If ($SkipCheck) {
            $Script:CWAServer = ("https://" + $Server)
            If ($Credential) {
                Write-Debug "Setting Credentials to $($Credential.UserName)"
                $Script:CWACredentials = $Credential
            }
            If ($testAuthorizationToken) {
                #Build the token
                $AutomateToken = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
                $Null = $AutomateToken.Add("Authorization", "Bearer $testAuthorizationToken")
                Write-Debug "Setting Authorization Token to $($AutomateToken.Authorization)"
                $Script:CWAToken = $AutomateToken
            }
            If ($ClientID) {
                Write-Debug "Setting ClientID to $ClientID"
                $Script:CWAClientID = $ClientID
            }
            Return
        }
        If (!$testAuthorizationToken -and $PSCmdlet.ParameterSetName -eq 'verify') {
            If (!$Quiet) { Throw "Attempt to verify token failed. No token was provided or was cached." }
            Return
        }
        Do {
            $testCredential=$Credential
            If (!$Quiet) {
                If (!$Credential -and ($Force -or !$testAuthorizationToken)) {
                    If ($Force -or !$Script:CWACredentials) {
                        $Username = Read-Host -Prompt "Please enter your Automate Username"
                        $Password = Read-Host -Prompt "Please enter your Automate Password" -AsSecureString
                        $Credential = New-Object System.Management.Automation.PSCredential ($Username, $Password)
                        $testCredential=$Credential
                    }
                }
                If ($TwoFactorNeeded -eq $True -and $TwoFactorToken -match '') {
                    $TwoFactorToken = Read-Host -Prompt "Please enter your 2FA Token"
                }
            }

            If (!$testAuthorizationToken -and !$testCredential -and $Script:CWACredentials -and $Force -ne $True) {
                $testCredential = $Script:CWACredentials
            }
            If ($testCredential) {
                #Build the headers for the Authentication
                $PostBody = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
                $PostBody.Add("username", $testCredential.UserName)
                $PostBody.Add("password", $testCredential.GetNetworkCredential().Password)
                If (!([string]::IsNullOrEmpty($TwoFactorToken))) {
                    #Remove any spaces that were added
                    $TwoFactorToken = $TwoFactorToken -replace '\s', ''
                    $PostBody.Add("TwoFactorPasscode", $TwoFactorToken)
                }
                $RESTRequest = @{
                    'URI' = ($AutomateAPIURI + '/apitoken')
                    'Method' = 'POST'
                    'ContentType' = 'application/json'
                    'Headers' = @{}
                    'Body' = $($PostBody | ConvertTo-Json -Compress)
                }
            } ElseIf ($PSCmdlet.ParameterSetName -eq 'refresh') {
                $PostBody = $testAuthorizationToken -replace 'Bearer ',''
                $RESTRequest = @{
                    'URI' = ($AutomateAPIURI + '/apitoken/refresh')
                    'Method' = 'POST'
                    'ContentType' = 'application/json'
                    'Headers' = @{}
                    'Body' = $PostBody | ConvertTo-Json -Compress
                }
            } ElseIf ($PSCmdlet.ParameterSetName -eq 'verify') {
                $PostBody = $testAuthorizationToken -replace 'Bearer ',''
                $RESTRequest = @{
                    'URI' = ($AutomateAPIURI + '/PatchInformation')
                    'Method' = 'GET'
                    'ContentType' = 'application/json'
                    'Headers' = @{'Authorization' = "Bearer $PostBody"}
                }
            }
            If ($ClientID) {
                $RESTRequest.Headers += @{'clientID' = "$ClientID"}
            }

            #Invoke the REST Method
            Write-Debug "Submitting Request to $($RESTRequest.URI)`nHeaders:`n$($RESTRequest.Headers|ConvertTo-JSON -Depth 5)`nBody:`n$($RESTRequest.Body|ConvertTo-JSON -Depth 5)"
            Try {
                $AutomateAPITokenResult = Invoke-RestMethod @RESTRequest
                Write-Debug "Request Results: $($AutomateAPITokenResult|ConvertTo-Json -Depth 5 -Compress -EA 0)"
            }
            Catch {
                Remove-Variable CWAToken,CWATokenKey -Scope Script -ErrorAction 0
                If ($testCredential) {
                    Remove-Variable CWACredentials -Scope Script -ErrorAction 0
                }
                If ($Credential) {
                    Throw "Attempt to authenticate to the Automate API has failed with error $_.Exception.Message"
                    Return
                }
            }
            
            $AuthorizationToken=$AutomateAPITokenResult.Accesstoken
            $TwoFactorNeeded=$AutomateAPITokenResult.IsTwoFactorRequired
            If ($PSCmdlet.ParameterSetName -eq 'verify' -and !$AuthorizationToken -and $TwoFactorNeeded -ne $True) {
                If ($AutomateAPITokenResult) {
                    Write-Verbose "Server Version: $($AutomateAPITokenResult.DBAgentServerPatchVersion)"
                    $AuthorizationToken=$testAuthorizationToken
                } Else {
                    $testAuthorizationToken = $Script:CWAToken.Authorization -replace 'Bearer ',''
                    Try {$Script:CWAToken.Authorization=$Null} Catch {}
                }
            }
        } Until (![string]::IsNullOrEmpty($AuthorizationToken) -or 
                ($PSCmdlet.ParameterSetName -eq 'verify' -and $testCredential) -or
                ($TwoFactorNeeded -ne $True -and $Credential) -or 
                ($TwoFactorNeeded -eq $True -and $TwoFactorToken -ne '')
            )
    } #End Process

    End {
        If ($SkipCheck) {
            $Script:CWAIsConnected=$True
            If ($Quiet) {
                Return $False
            } Else {
                Return
            }
        } ElseIf ([string]::IsNullOrEmpty($AuthorizationToken)) {
            Remove-Variable CWAToken -Scope Script -ErrorAction 0
            If ($Quiet) {
                Return $False
            } Else {
                Throw "Unable to get Access Token. Either the credentials you entered are incorrect or you did not pass a valid two factor token" 
                Return
            }
        } Else {
            #Build the returned token
            $AutomateToken = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
            $AutomateToken.Add("Authorization", "Bearer $AuthorizationToken")
            #Create Script Variables for this session in order to use the token
            $Script:CWATokenKey = ConvertTo-SecureString $AuthorizationToken -AsPlainText -Force
            $Script:CWAServer = ("https://" + $Server)
            $Script:CWAToken = $AutomateToken
            $Script:CWAIsConnected=$True
            If ($Credential) {
                $Script:CWACredentials = $Credential
            }
            If ($ClientID) {
                $Script:CWAClientID = $ClientID
            }
            If ($PSCmdlet.ParameterSetName -ne 'verify') {
                $AutomateAPITokenResult.PSObject.properties.remove('AccessToken')
                $Script:CWATokenInfo = $AutomateAPITokenResult
            }
            Write-Verbose "Token retrieved: $AuthorizationToken, expiration is $($Script:CWATokenInfo.ExpirationDate)"

            If (!$Quiet) {
                Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully tested and connected to the Automate REST API. Token will expire at $($Script:CWATokenInfo.ExpirationDate)"
            } Else {
                Return $True
            }
        }
    }
}