AutomateNOW.psm1

$InformationPreference = 'Continue'

Function Confirm-AutomateNOWSession {
    <#
.SYNOPSIS
Confirms that the local session variable created by Connect-AutomateNOW is still apparently valid.
 
.DESCRIPTION
The `Confirm-AutomateNOWSession` function confirms that the local session variable created by Connect-AutomateNOW is still apparently valid (not expired yet). This function does not make any network connections. It is only reviewing and advising on the currently stored session variable.
 
.PARAMETER Quiet
Switch parameter to silence the extraneous output that this outputs by default
 
.PARAMETER IgnoreEmptyDomain
Switch parameter to ignore the lack of configured domain in the session header. This was intended for development purposes and is likely to be removed in the future.
 
.INPUTS
None. You cannot pipe objects to Confirm-AutomateNOWSession (yet).
 
.OUTPUTS
Returns a boolean $True if the local session variable appears to be valid (not expired yet)
 
.EXAMPLE
Confirm-AutomateNOWSession -Quiet
 
.NOTES
You must use Connect-AutomateNOW to establish the token before you can confirm it
 
#>

    [OutputType([boolean])]
    [Cmdletbinding()]
    Param(
        [Parameter(Mandatory = $false)]
        [switch]$Quiet,
        [Parameter(Mandatory = $false)]
        [switch]$IgnoreEmptyDomain
    )
    If ($anow_header.values.count -eq 0) {
        Write-Warning -Message "Please use Connect-AutomateNOW to establish your access token."
        Break
    }
    ElseIf ($anow_header.Authorization -notmatch '^Bearer [a-zA-Z-_/=:,."0-9]{1,}$') {
        [string]$malformed_token = $anow_header.values
        Write-Warning -Message "Somehow the access token is not in the expected format. Please contact the author with this apparently malformed token: [$malformed_token]"
        Break
    }
    ElseIf ($anow_session.ExpirationDate -isnot [datetime]) {
        Write-Warning -Message 'Somehow there is no expiration date available. Please use Connect-AutomateNOW to establish your session properties.'
        Break
    }
    ElseIf ($anow_session.RefreshToken -notmatch '^[a-zA-Z-_/=:,."0-9]{1,}$' -and $anow_session.RefreshToken.Length -gt 0) {
        [string]$malformed_refresh_token = $anow_session.RefreshToken
        Write-Warning -Message "Somehow the refresh token does not appear to be valid. Please contact the author about this apparently malformed token: [$malformed_refresh_token]"
        Break
    }
    If ($null -eq (Get-Command -Name Invoke-AutomateNOWAPI -EA 0)) {
        Write-Warning -Message 'Somehow the Invoke-AutomateNOWAPI function is not available in this session. Did you install -and- import the module?'
        Break
    }
    [datetime]$current_date = Get-Date
    [datetime]$ExpirationDate = $anow_session.ExpirationDate
    [string]$ExpirationDateDisplay = Get-Date -Date $ExpirationDate -Format 'yyyy-MM-dd HH:mm:ss'
    [timespan]$TimeRemaining = ($ExpirationDate - $current_date)
    If ($TimeRemaining.Ticks -lt 1000) {
        [int32]$minutes_elapsed = ($TimeRemaining.TotalMinutes * -1)
        Write-Warning -Message "This token expired [$minutes_elapsed] minutes ago. Please refresh your token by using Connect-AutomateNOW."
        Break
    }
    ElseIf ($Quiet -ne $true) {
        [int32]$minutes_remaining = [math]::floor($TimeRemaining.TotalMinutes)
        Write-Information -Message "This token will expire in $minutes_remaining minutes at $ExpirationDateDisplay."
    }
    If ($anow_header.domain.Length -eq 0 -and $IgnoreEmptyDomain -ne $true) {
        Write-Warning -Message 'You somehow do have not have a domain selected. You can try re-connecting again with Connect-AutomateNOW and the -Domain parameter or use Switch-Domain please.'
        Break
    }
    Return $true
}

Function Connect-AutomateNOW {
    <#
.SYNOPSIS
Connects to the API of an AutomateNOW! instance
 
.DESCRIPTION
The `Connect-AutomateNow` function authenticates to the API of an AutomateNOW! instance. It then sets the access token globally.
 
.PARAMETER User
Specifies the user connecting to the API
 
.PARAMETER Pass
Specifies the password of the user connecting to the API
 
.PARAMETER Instance
Specifies the name of the AutomateNOW! instance. For example: s2.infinitedata.com
 
.PARAMETER Domain
Optional string to set the AutomateNOW domain manually
 
.PARAMETER NotSecure
Switch parameter to accomodate instances that use the http protocol (typically on port 8080)
     
.PARAMETER Quiet
Switch parameter to silence the extraneous output that this outputs by default
 
.PARAMETER Key
Optional 16-byte array for when InfiniteDATA has changed their encryption key but this module has not been updated yet
 
.INPUTS
None. You cannot pipe objects to Connect-AutomateNOW (yet).
 
.OUTPUTS
There is no direct output. Rather, a global variable $anow_header with the bearer access token is set in the current powershell session.
 
.EXAMPLE
Connect-AutomateNOW -User 'user.10' -Pass 'MyCoolPassword!' -Instance 's2.infinitedata.com'
 
.NOTES
More features will be added in the future if this turns out to be useful
 
#>

    [OutputType([string])]
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$User,
        [Parameter(Mandatory = $true)]
        [string]$Pass,
        [Parameter(Mandatory = $true)]
        [string]$Instance,
        [Parameter(Mandatory = $false)]
        [string]$Domain,
        [Parameter(Mandatory = $false)]
        [switch]$NotSecure,
        [Parameter(Mandatory = $false)]
        [switch]$Quiet,
        [Parameter(Mandatory = $false)]
        [byte[]]$Key = @(7, 22, 15, 11, 1, 24, 8, 13, 16, 10, 5, 17, 12, 19, 27, 9)
    )
    Function New-ANowAuthenticationPayload {
        [OutputType([string])]
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $true)]
            [string]$User,
            [Parameter(Mandatory = $true)]
            [string]$Pass,
            [Parameter(Mandatory = $false)]
            [boolean]$SuperUser = $false,
            [Parameter(Mandatory = $false)]
            [byte[]]$Key = @(7, 22, 15, 11, 1, 24, 8, 13, 16, 10, 5, 17, 12, 19, 27, 9)
        )
        [byte[]]$passwd_array = [System.Text.Encoding]::UTF8.GetBytes($pass)
        [byte[]]$encrytped_array = For ($i = 0; $i -lt ($passwd_array.Length); $i++) {
            [byte]$current_byte = $passwd_array[$i]
            [int32]$first = (-bnot $current_byte -shr 0) -band 0x0f
            [int32]$second = (-bnot $current_byte -shr 4) -band 0x0f
            $Key[$first]
            $Key[$second]
        }
        [string]$encrypted_string = [System.Convert]::ToBase64String($encrytped_array)
        [hashtable]$payload = @{}
        $payload.Add('j_username', $user)
        $payload.Add('j_password', "ENCRYPTED::$encrypted_string")
        $payload.Add('superuser', $superuser)
        [string]$payload_json = $payload | ConvertTo-Json -Compress
        Write-Verbose -Message "Sending payload $payload_json"
        Return $payload_json
    }
    
    Function New-ANOWAuthenticationProperties {
        [OutputType([hashtable])]
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $true)]
            [string]$User,
            [Parameter(Mandatory = $true)]
            [string]$Pass
        )
        [string]$body = New-ANOWAuthenticationPayload -User $User -Pass $Pass
        If ($NotSecure -eq $true) {
            [string]$protocol = 'http'
        }
        Else {
            [string]$protocol = 'https'
        }
        [string]$login_url = ($protocol + '://' + $instance + '/automatenow/api/login/authenticate')
        [hashtable]$parameters = @{}
        [int32]$ps_version_major = $PSVersionTable.PSVersion.Major
        If ($ps_version_major -eq 5) {
            # The below C# code provides the equivalent of the -SkipCertificateCheck parameter for Windows PowerShell 5.1 Invoke-WebRequest
            [string]$certificate_policy = @"
        using System.Net;
        using System.Security.Cryptography.X509Certificates;
        public class TrustAllCertsPolicy : ICertificatePolicy {
            public bool CheckValidationResult(
                ServicePoint srvPoint, X509Certificate certificate,
                WebRequest request, int certificateProblem) {
                return true;
            }
        }
"@

            $Error.Clear()
            Try {
                Add-Type -TypeDefinition $certificate_policy
            }
            Catch {
                [string]$Message = $_.Exception.Message
                Write-Warning -Message "Add-Type failed due to [$Message]"
                Break
            }
            $Error.Clear()
            Try {
                [System.Net.ServicePointManager]::CertificatePolicy = New-Object -TypeName TrustAllCertsPolicy
            }
            Catch {
                [string]$Message = $_.Exception.Message                
                Write-Warning -Message "New-Object failed to create a new 'TrustAllCertsPolicy' CertificatePolicy object due to [$Message]."
                Break
            }
            $parameters.Add('UseBasicParsing', $true)
        }
        ElseIf ( $ps_version_major -gt 5) {
            $parameters.Add('SkipCertificateCheck', $true)
        }
        Else {
            Write-Warning -Message "Please use either Windows PowerShell 5.1 or PowerShell Core."
            Break
        }
        $parameters.Add('Uri', $login_url)
        $parameters.Add('Method', 'POST')
        $parameters.Add('Body', $body)
        $parameters.Add('ContentType', 'application/json')
        $Error.Clear()
        Try {
            [Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject]$results = Invoke-WebRequest @parameters
        }
        Catch {
            [string]$Message = $_.Exception.Message
            If ($Message -match '(The underlying connection was closed|The SSL connection could not be established)') {
                Write-Warning -Message 'Please use the -NotSecure parameter if you are connecting to an insecure instance (typically on port 8080).'
            }
            Else {
                Write-Warning -Message "Invoke-WebRequest failed due to [$Message]"
            }
            Break
        }    
        [int32]$return_code = $Results.StatusCode
        If ($return_code -ne 200) {
            Return "The status code was [$return_code] instead of 200. Please check into this."
        }
        [string]$content = $Results.Content
        If ($content -notmatch '^{"token_type":"Bearer","access_token":"[a-zA-Z-_:,."0-9]{1,}"}$') {
            [string]$content = "The returned content does not contain a bearer token. Please check the credential you are using."
        }
        Write-Verbose -Message "`r`nToken properties: $content`r`n"
        $Error.Clear()
        Try {
            [PSCustomObject]$token_properties = $content | ConvertFrom-Json
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning "ConvertFrom-Json or Select-Object failed due to [$Message]."
            Break
        }
        Return $token_properties
    }
    If ($null -ne $anow_header) {
        $Error.Clear()
        Try {
            Remove-Variable -Name anow_header -Scope Global -Force
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning "Remove-Variable failed to remove the `$anow_header variable due to [$Message]."
            Break
        }
    }
    If ($null -ne $anow_session) {
        $Error.Clear()
        Try {
            Remove-Variable -Name anow_session -Scope Global -Force
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning "Remove-Variable failed to remove the `$anow_session variable due to [$Message]."
            Break
        }
    }
    $Error.Clear()
    Try {
        [PSCustomObject]$token_properties = New-ANOWAuthenticationProperties -User $User -Pass $Pass
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning "New-ANOWAuthenticationProperties failed due to [$Message]."
        Break
    }
    If ( $token_properties.expirationDate -isnot [int64]) {
        Write-Warning -Message "How is it that the expiration date value is not a 64-bit integer? Something must be wrong. Are we in a time machine?"
        Break
    }
    [string]$access_token = $token_properties.access_token
    [string]$refresh_token = $token_properties.refresh_token
    [int32]$expires_in = $token_properties.expires_in
    [hashtable]$authorization_header = @{'Authorization' = "Bearer $access_token"; 'domain' = '' }
    $Error.Clear()
    Try {
        New-Variable -Name 'anow_header' -Scope Global -Value $authorization_header
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning "New-Variable failed due to create the header globally due to [$Message]."
        Break
    }
    Write-Verbose -Message 'Global variable $anow_header has been set. Use this as your authentication header.'
    $Error.Clear()
    Try {
        [System.TimeZoneInfo]$timezone = Get-TimeZone
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning "Get-TimeZone failed due to get the time zone due to [$Message]."
        Break
    }
    [System.TimeSpan]$utc_offset = $timezone.BaseUtcOffset
    [System.TimeSpan]$expiration_offset = New-TimeSpan -Seconds $expires_in
    $Error.Clear()
    Try {
        [datetime]$expiration_date_utc = (Get-Date -Date '1970-01-01').AddMilliseconds($token_properties.expirationDate)
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning "Get-Date failed due to process the authentication properties due to [$Message]"
        Break
    }
    [datetime]$expiration_date = ($expiration_date_utc + $utc_offset + $expiration_offset) # We're adding 3 values here for the final expiration date. The current time in UTC, the current machine's UTC offset and the duration of the session (typically 3600 seconds)
    [hashtable]$anow_session = @{}
    $anow_session.Add('User', $User)
    $anow_session.Add('Instance', $Instance)
    If ($NotSecure -eq $true) {
        $anow_session.Add('NotSecure', $True)
    }    
    $anow_session.Add('ExpirationDate', $expiration_date)
    $anow_session.Add('AccessToken', $access_token)
    $anow_session.Add('RefreshToken', $refresh_token)
    If ($Domain.Length -gt 0) {
        $anow_session.Add('current_domain', $Domain)
    }
    $Error.Clear()
    Try {
        [PSCustomObject]$userInfo = Get-AutomateNOWUser
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning "Get-AutomateNOWUser failed to get the currently logged in user info due to [$Message]."
        Break
    }
    If ($userInfo.domains.length -eq 0) {
        Write-Warning "Somehow the user info object is malformed."
        Break
    }
    [array]$domains = $userInfo.domains -split ','
    [int32]$domain_count = $domains.Count
    Write-Verbose -Message "Detected $domain_count domains"
    If ($domain_count -eq 0) {
        Write-Warning "Somehow the count of domains is zero."
        Break
    }
    ElseIf ($domain_count -eq 1) {
        If ($Domain.Length -eq 0) {
            [string]$Domain = $domains
            If ($null -ne $anow_header.Domain) {
                $anow_header.Remove('domain')
            }
            If ($null -ne $anow_session.current_domain) {
                $anow_session.Remove('current_domain')
            }
            If ($null -ne $anow_session.domain) {
                $anow_session.Remove('domain')
            }
            If ($null -ne $anow_session.domains) {
                $anow_session.Remove('domains')
            }
            $anow_header.Add('domain', $Domain)
            $anow_session.Add('current_domain', $Domain)
            $anow_session.Add('domains', @($Domain))
            Write-Verbose -Message "Automatically choosing the [$Domain] domain as it is the only one available."
        }
        ElseIf ($userInfo.domains -ne $Domain) {
            Write-Warning -Message "The domain you chose with -Domain is not the same as the one on [$instance]. Are you sure you entered the domain correctly?"
            Break
        }
    }    
    Elseif ($domain_count -gt 1 -and $Domain.Length -gt 0) {
        If ($domains -notcontains $Domain) {
            Write-Warning -Message "The domain you chose with -Domain is not available on [$instance]. Are you sure you entered the domain correctly?"
            Break
        }
    }
    Else {
        [string]$domains_display = $domains -join ', '
        $anow_session.Add('domains', $domains)
        Write-Information -MessageData "Please use Switch-AutomateNOWDomain to choose from one of these $domain_count domains [$domains_display]"
    }
    $Error.Clear()
    Try {
        New-Variable -Name 'anow_session' -Scope Global -Value $anow_session
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning "New-Variable failed due to create the session properties object due to [$Message]"
        Break
    }
    Write-Verbose -Message 'Global variable $anow_session has been set. Use this for other session properties.'
    If ($Quiet -ne $true) {
        Return [PSCustomObject]@{ instance = $instance; token_expires = $expiration_date; domain = $Domain; }
    }
}

Function Disconnect-AutomateNOW {
    <#
.SYNOPSIS
Disconnects from the API of an AutomateNOW! instance
 
.DESCRIPTION
The `Disconnect-AutomateNOW` function logs out of the API of an AutomateNOW! instance. It then removes the global session variable object.
 
.INPUTS
None. You cannot pipe objects to Disconnect-AutomateNOW.
 
.OUTPUTS
A string indicating the results of the disconnection attempt.
 
.EXAMPLE
Disconnect-AutomateNOW
 
.NOTES
You should do this whenever you are finished with your session. This prevents your token (a.k.a. cookie) from being stolen.
#>

    [CmdletBinding()]
    Param(
    
    )
    If ((Confirm-AutomateNOWSession -Quiet) -ne $true) {
        Write-Warning -Message "Somehow there is not a valid token confirmed."
        Break
    }
    [string]$command = '/logoutEvent'
    [hashtable]$parameters = @{}
    $parameters.Add('Command', $command)
    $parameters.Add('Method', 'POST')
    If ($anow_session.NotSecure -eq $true) {
        $parameters.Add('NotSecure', $true)
    }    
    $Error.Clear()
    Try {
        [PSCustomObject]$results = Invoke-AutomateNOWAPI @parameters
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "Invoke-AutomateNOWAPI failed due to execute [$command] due to [$Message]."
        Break
    }
    [PSCustomObject]$response = $results.response
    If ($response.status -eq 0) {
        [string]$Instance = $anow_session.Instance
        $Error.Clear()
        Try {
            Remove-Variable -Name anow_header -Scope Global -Force
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning "Remove-Variable failed to remove the `$anow_header variable due to [$Message]."
            Break
        }
        $Error.Clear()
        Try {
            Remove-Variable -Name anow_session -Scope Global -Force
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning "Remove-Variable failed to remove the `$anow_session variable due to [$Message]."
            Break
        }
        Write-Information -MessageData "Successfully disconnected from [$Instance]."
    }
}

Function Invoke-AutomateNOWAPI {
    <#
    .SYNOPSIS
    Invokes the API of an AutomateNOW instance
     
    .DESCRIPTION
    The `Invoke-AutomateNOWAPI` cmdlet sends API commands (in the form of HTTPS requests)
    to an instance of AutomateNOW. It returns the results in either JSON or PSCustomObject.
     
    .PARAMETER Command
    Specifies the command to invoke with the API call. The value must begin with a forward slash. For example: /secUser/getUserInfo
     
    .PARAMETER Method
    Specifies the method to use with the API call. Valid values are GET and POST.
     
    .PARAMETER NotSecure
    Switch parameter to accomodate instances using the http protocol. Only use this if the instance is on http and not https.
     
    .PARAMETER Body
    Specifies the body object. The format will depend on what you have for content type. Usually, this is a string or a hashtable.
     
    .PARAMETER ContentType
    Specifies the content type of the body (only needed if a body is included)
     
    .PARAMETER Instance
    Specifies the name of the AutomateNOW instance. For example: s2.infinitedata.com
     
    .PARAMETER JustGiveMeJSON
    Switch parameter to return the results in a JSON string instead of a PSCustomObject
         
    .PARAMETER NotAPICommand
    Rarely used switch parameter that removes the '/api' portion of the API URL. Note: This parameter is slated for removal
         
    .INPUTS
    None. You cannot pipe objects to Invoke-AutomateNOWAPI (yet).
     
    .OUTPUTS
    PSCustomObject or String is returned
     
    .EXAMPLE
    Invoke-AutomateNOWAPI -command '/secUser/getUserInfo' -method GET
     
    .NOTES
    You must use Connect-AutomateNOW to establish the token by way of global variable.
    #>

    
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Command,
        [Parameter(Mandatory = $true)]
        [ValidateSet('GET', 'POST')]
        [string]$Method,
        [Parameter(Mandatory = $false)]
        [switch]$NotSecure = $false,
        [Parameter(Mandatory = $false)]
        [string]$Body,
        [Parameter(Mandatory = $false)]
        [string]$ContentType = 'application/json',
        [Parameter(Mandatory = $false)]
        [string]$Instance,
        [Parameter(Mandatory = $false)]
        [switch]$JustGiveMeJSON,
        [Parameter(Mandatory = $false)]
        [switch]$NotAPICommand = $false
    )
    If ($anow_header.values.count -eq 0 -or $anow_session.Instance.Length -eq 0) {
        Write-Warning -Message "Please use Connect-AutomateNOW to establish your access token."
        Break
    }
    ElseIf ($anow_header.Authorization -notmatch '^Bearer [a-zA-Z-_:,."0-9]{1,}$') {
        [string]$malformed_token = $anow_header.values
        Write-Warning -Message "Somehow the access token is not in the expected format. Please contact the author with this apparently malformed token: $malformed_token"
        Break
    }
    ElseIf ($command -notmatch '^/.{1,}') {
        Write-Warning -Message "Please prefix the command with a forward slash (for example: /secUser/getUserInfo)."
        Break
    }
    If ($Instance.Length -eq 0) {
        [string]$Instance = $anow_session.Instance
    }
    [hashtable]$parameters = @{}
    If ($NotSecure -eq $true) {
        [string]$protocol = 'http'
    }
    Else {
        [string]$protocol = 'https'
    }
    [int32]$ps_version_major = $PSVersionTable.PSVersion.Major
    If ($ps_version_major -eq 5 -and $NotSecure -eq $true) {
        # The below C# code provides the equivalent of the -SkipCertificateCheck parameter for Windows PowerShell 5.1 Invoke-WebRequest
        [string]$certificate_policy = @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@

        $Error.Clear()
        Try {
            Add-Type -TypeDefinition $certificate_policy
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning -Message "Add-Type failed due to [$Message]."
            Break
        }
        $Error.Clear()
        Try {
            [System.Net.ServicePointManager]::CertificatePolicy = New-Object -TypeName TrustAllCertsPolicy
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning -Message "New-Object failed to create a CertificatePolicy due to [$Message]. Wouldn't it be easier to move to PowerShell Core? :-)"
            Break
        }
        $parameters.Add('UseBasicParsing', $true)
    }
    ElseIf ( $ps_version_major -gt 5) {
        $parameters.Add('SkipCertificateCheck', $true)
    }
    Else {
        Write-Warning -Message "Please use either Windows PowerShell 5.x or PowerShell Core. This module is not compatible with PowerShell 4 or below."
        Break
    }
    If ($NotAPICommand -ne $true) {
        [string]$api_url = ($protocol + '://' + $instance + '/automatenow/api' + $command)
    }
    Else {
        [string]$api_url = ($protocol + '://' + $instance + '/automatenow' + $command)
    }
    $parameters.Add('Uri', $api_url)
    $parameters.Add('Headers', $anow_header)
    $parameters.Add('Method', $Method)
    $parameters.Add('ContentType', $ContentType)
    If ($Body.Length -gt 0) {
        $parameters.Add('Body', $Body)
    }
    [string]$parameters_display = $parameters | ConvertTo-Json
    Write-Verbose -Message "Sending the following parameters to $Instance -> $parameters_display."
    $Error.Clear()
    Try {
        [Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject]$results = Invoke-WebRequest @parameters
    }
    Catch {
        [string]$Message = $_.Exception.Message
        If ($Message -match '(The underlying connection was closed|The SSL connection could not be established)') {
            Write-Warning -Message 'Please try again with the -NotSecure parameter if you are connecting to an insecure instance.'
            Break
        }
        ElseIf ($Message -match 'Response status code does not indicate success:') {
            $Error.Clear()
            Try {
                [int32]$return_code = $Message -split 'success: ' -split ' ' | Select-Object -Last 1 -Skip 1
            }
            Catch {
                [string]$Message2 = $_.Exception.Message
                Write-Warning -Message "Unable to extract the error code from [$Message] due to [$Message2]"
            }
            Write-Verbose "Received status code $return_code instead of 200!"
            [string]$ReturnCodeWarning = Switch ($return_code) {
                401 { "You received HTTP Code $return_code (Unauthorized). Has your token expired? :-)" }
                403 { "You received HTTP Code $return_code (Forbidden). Do you have permission to do [$command]?" }
                404 { "You received HTTP Code $return_code (Page Not Found). Is the command [$command] correct?" }
                Default { "You received HTTP Code $return_code instead of '200 OK'. Apparently, something is wrong..." }
            }
            Write-Warning -Message $ReturnCodeWarning       
        }
        Else {
            Write-Warning -Message "Invoke-WebRequest failed due to [$Message]"
        }
        Break
    }
    [string]$content = $Results.Content
    If ($content -notmatch '^{.{1,}}$') {
        Write-Warning -Message "The returned results were somehow not a JSON object."
        Break
    }
    If ($JustGiveMeJSON -eq $true) {
        Return $content
    }
    $Error.Clear()
    Try {
        [PSCustomObject]$content_object = $content | ConvertFrom-JSON
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning "ConvertFrom-JSON failed to convert the resturned results due to [$Message]."
        Break
    }
    Return $content_object
}

Function Show-AutomateNOWDomain {
    <#
    .SYNOPSIS
    Shows the details of the available domains from an AutomateNOW instance
     
    .DESCRIPTION
    The `Show-AutomateNOWDomain` cmdlet invokes the Get-AutomateNOWDomain function to retrieve information about the available domains on the instance of AutomateNOW that you are connected to and to show them
     
    .INPUTS
    None. You cannot pipe objects to Show-AutomateNOWDomain.
     
    .OUTPUTS
    None except for Write-Information messages.
     
    .EXAMPLE
    Show-AutomateNOWDomain
     
    .NOTES
    You must use Connect-AutomateNOW to establish the token by way of global variable.
 
    There are no parameters yet for this function.
    #>

    [Cmdletbinding()]
    Param(
    )
    If ((Confirm-AutomateNOWSession -Quiet) -ne $true) {
        Write-Warning -Message "Somehow there is not a valid token confirmed."
        Break
    }
    [PSCustomObject[]]$available_domains = Get-AutomateNOWDomain
    [string]$Instance = $anow_session.Instance
    [int32]$domain_count = $available_domains.Count
    If ($domain_count -gt 1) {
        [string]$available_domains_display = $available_domains.id -join ', '
        Write-Information -MessageData "The [$available_domains_display] domains are available on [$Instance]. Use Switch-AutomateNOWDomain to switch domains."
    }
    Else {
        [string]$available_domains_display = $available_domains.id
        Write-Information -MessageData "The [$available_domains_display] domain is available on [$Instance]."
    }
}

Function Switch-AutomateNOWDomain {
    <#
.SYNOPSIS
Switches the currently selected domain for the logged on user of an AutomateNOW! instance
 
.DESCRIPTION
The `Switch-AutomateNOWDomain` cmdlet does not actually communicate with the AutomateNOW! instance. It modifies the $anow_session and $anow_header global variables.
 
.PARAMETER Domain
Required string representing the name of the domain to switch to.
 
.INPUTS
None. You cannot pipe objects to Switch-AutomateNOWDomain.
 
.OUTPUTS
None except for Write-Information messages.
 
.EXAMPLE
Switch-AutomateNOWDomain -Domain 'Sandbox'
 
.NOTES
You must use Connect-AutomateNOW to establish the token by way of global variable.
#>

    [CmdletBinding()]
    Param(
        [string]$Domain
    )
    If ((Confirm-AutomateNOWSession -Quiet -IgnoreEmptyDomain) -ne $true) {
        Write-Warning -Message "Somehow there is not a valid token confirmed."
        Break
    }
    [string]$Instance = $anow_session.Instance
    If ($anow_session.domains -cnotcontains $Domain) {
        [string]$available_domains = $anow_session.domains -join ', '
        If ($anow_session.domains -contains $Domain) {
            Write-Warning -Message "The domains are case-sensitive. Please choose from [$available_domains]."
            Break
        }
        Write-Warning -Message "The domain [$Domain] is not on [$Instance]. Please choose from [$available_domains]."
        Break
    }
    $Error.Clear()
    Try {
        $anow_session.Remove('current_domain')
        $anow_session.Add('current_domain', $Domain)
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning "The Add/Remove method failed on `$anow_session` due to [$Message]."
        Break
    }
    $Error.Clear()
    Try {
        $anow_header.Remove('domain')
        $anow_header.Add('domain', $Domain)
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning "The Add/Remove method failed on `$anow_header` due to [$Message]."
        Break
    }
    Write-Information -MessageData "The [$Domain] domain has been selected for [$Instance]."
}

Function Get-AutomateNOWDomain {
    <#
    .SYNOPSIS
    Gets the details of the available domains from an AutomateNOW instance
     
    .DESCRIPTION
    The `Get-AutomateNOWDomain` cmdlet invokes the /domain/read endpoint to retrieve information about the available domains on the instance of AutomateNOW that you are connected to
     
    .INPUTS
    None. You cannot pipe objects to Get-AutomateNOWUser.
     
    .OUTPUTS
    An array of PSCustomObjects (1 for each available domain)
     
    .EXAMPLE
    Get-AutomateNOWDomain
     
    .NOTES
    You must use Connect-AutomateNOW to establish the token by way of global variable.
 
    There are no parameters yet for this function.
    #>

    [OutputType([array])]
    [Cmdletbinding()]
    Param(
    )
    If ((Confirm-AutomateNOWSession -Quiet) -ne $true) {
        Write-Warning -Message "Somehow there is not a valid token confirmed."
        Break
    }
    [string]$command = '/domain/read'
    [hashtable]$parameters = @{}
    $parameters.Add('Command', $command)
    $parameters.Add('Method', 'GET')
    If ($anow_session.NotSecure -eq $true) {
        $parameters.Add('NotSecure', $true)
    }    
    $Error.Clear()
    Try {
        [PSCustomObject]$results = Invoke-AutomateNOWAPI @parameters
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "Invoke-AutomateNOWAPI failed due to execute [$command] due to [$Message]."
        Break
    }
    [PSCustomObject[]]$domains = $results.response.data
    [int32]$domain_count = $domains.Count
    If ($domain_count -eq 0) {
        Write-Warning -Message "Somehow there are no domains available. Please look into this..."
        Break
    }
    Return $domains
}

Function Get-AutomateNOWNode {
    <#
    .SYNOPSIS
    Gets the nodes of all domains from an AutomateNOW! instance
     
    .DESCRIPTION
    The `Get-AutomateNOWNode` retrieves all of the nodes from the connected AutomateNOW! instance
     
    .INPUTS
    None. You cannot pipe objects to Get-AutomateNOWNode.
     
    .OUTPUTS
    An array of PSCustomObjects
     
    .EXAMPLE
    Get-AutomateNOWNode
     
    .NOTES
    You must use Connect-AutomateNOW to establish the token by way of global variable.
 
    There are no parameters yet for this function.
    #>

    [OutputType([array])]
    [Cmdletbinding()]
    Param(
    )
    If ((Confirm-AutomateNOWSession -Quiet) -ne $true) {
        Write-Warning -Message "Somehow there is not a valid token confirmed."
        Break
    }
    [string]$command = '/serverNode'
    [hashtable]$parameters = @{}
    $parameters.Add('Command', $command)
    $parameters.Add('Method', 'GET')
    If ($anow_session.NotSecure -eq $true) {
        $parameters.Add('NotSecure', $true)
    }    
    $Error.Clear()
    Try {
        [PSCustomObject]$results = Invoke-AutomateNOWAPI @parameters
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "Invoke-AutomateNOWAPI failed due to execute [$command] due to [$Message]."
        Break
    }
    [PSCustomObject[]]$nodes = $results.response.data
    [int32]$nodes_count = $nodes.Count
    If ($nodes_count -eq 0) {
        Write-Warning -Message "Somehow there are no nodes available. Is this a newly installed instance which has not been configured yet?"
        Break
    }
    Return $nodes
}

Function Get-AutomateNOWTag {
    <#
    .SYNOPSIS
    Gets the tags of all domains from an AutomateNOW! instance
     
    .DESCRIPTION
    The `Get-AutomateNOWTag` retrieves all of the tags from the connected AutomateNOW! instance
     
    .INPUTS
    None. You cannot pipe objects to Get-AutomateNOWTag.
     
    .OUTPUTS
    An array of PSCustomObjects
     
    .EXAMPLE
    Get-AutomateNOWTag
     
    .NOTES
    You must use Connect-AutomateNOW to establish the token by way of global variable.
 
    There are no parameters yet for this function.
    #>

    [OutputType([array])]
    [Cmdletbinding()]
    Param(
    )
    If ((Confirm-AutomateNOWSession -Quiet) -ne $true) {
        Write-Warning -Message "Somehow there is not a valid token confirmed."
        Break
    }
    [string]$command = '/tag/readAllDomains'
    [hashtable]$parameters = @{}
    $parameters.Add('Command', $command)
    $parameters.Add('Method', 'GET')
    If ($anow_session.NotSecure -eq $true) {
        $parameters.Add('NotSecure', $true)
    }    
    $Error.Clear()
    Try {
        [PSCustomObject]$results = Invoke-AutomateNOWAPI @parameters
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "Invoke-AutomateNOWAPI failed due to execute [$command] due to [$Message]."
        Break
    }
    [PSCustomObject[]]$domains = $results.response.data
    [int32]$domain_count = $domains.Count
    If ($domain_count -eq 0) {
        Write-Warning -Message "Somehow there are no domains available. Please look into this..."
        Break
    }
    Return $domains
}

Function Get-AutomateNOWUser {
    <#
    .SYNOPSIS
    Gets the details of the currently authenticated user
     
    .DESCRIPTION
    The `Get-AutomateNOWUser` cmdlet invokes the /secUser/getUserInfo endpoint to retrieve information about the currently authenticated user (meaning you)
     
    .INPUTS
    None. You cannot pipe objects to Get-AutomateNOWUser.
     
    .OUTPUTS
    A PSCustomObject
     
    .EXAMPLE
    Get-AutomateNOWUser
     
    .NOTES
    You must use Connect-AutomateNOW to establish the token by way of global variable.
 
    There are no parameters yet for this function.
    #>

    [OutputType([PSCustomObject])]
    [Cmdletbinding()]
    Param(
    )
    If ((Confirm-AutomateNOWSession -Quiet -IgnoreEmptyDomain ) -ne $true) {
        Write-Warning -Message "Somehow there is not a valid token confirmed."
        Break
    }
    [string]$command = '/secUser/getUserInfo'
    [string]$Instance = $anow_session.Instance
    [hashtable]$parameters = @{}
    $parameters.Add('Command', $command)
    $parameters.Add('Method', 'GET')
    $parameters.Add('Instance', $Instance)
    If ($anow_session.NotSecure -eq $true) {
        $parameters.Add('NotSecure', $true)
    }    
    $Error.Clear()
    Try {
        [PSCustomObject]$results = Invoke-AutomateNOWAPI @parameters
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "Invoke-AutomateNOWAPI failed due to execute [$command] due to [$Message]."
        Break
    }
    Return $results
}

Function Get-AutomateNOWTriggerLog {
    <#
    .SYNOPSIS
    Gets the trigger logs from the domain of an AutomateNOW! instance
     
    .DESCRIPTION
    The `Get-AutomateNOWTriggerLog` retrieves all of the trigger logs from the domain of an AutomateNOW! instance
     
    .INPUTS
    None. You cannot pipe objects to Get-AutomateNOWTriggerLog.
     
    .OUTPUTS
    An array of PSCustomObjects
     
    .EXAMPLE
    Get-AutomateNOWTriggerLog
     
    .NOTES
    You must use Connect-AutomateNOW to establish the token by way of global variable.
    There are no parameters yet for this function.
    #>

    [OutputType([array])]
    [Cmdletbinding()]
    Param(
    )
    If ((Confirm-AutomateNOWSession -Quiet) -ne $true) {
        Write-Warning -Message "Somehow there is not a valid token confirmed."
        Break
    }
    [string]$command = '/executeProcessingTriggerLog'
    [hashtable]$parameters = @{}
    $parameters.Add('Command', $command)
    $parameters.Add('Method', 'GET')
    If ($anow_session.NotSecure -eq $true) {
        $parameters.Add('NotSecure', $true)
    }    
    $Error.Clear()
    Try {
        [PSCustomObject]$results = Invoke-AutomateNOWAPI @parameters
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "Invoke-AutomateNOWAPI failed due to execute [$command] due to [$Message]."
        Break
    }
    [PSCustomObject[]]$nodes = $results.response.data
    [int32]$nodes_count = $nodes.Count
    If ($nodes_count -eq 0) {
        Write-Warning -Message "Somehow there are no trigger logs available. Is this a newly installed instance which has not been configured yet?"
        Break
    }
    Return $nodes
}

Function Set-AutomateNOWPassword {
    <#
    .SYNOPSIS
    Sets the password of the authenticated user of an AutomateNOW! instance
     
    .DESCRIPTION
    The `Set-AutomateNOWPassword` sets the password of the authenticated user of an AutomateNOW! instance
     
    .PARAMETER OldPasswd
    String representing the current password of the authenticated user (use -Secure for masked input)
 
    .PARAMETER NewPasswd
    String representing the new password of the authenticated user (use -Secure for masked input)
 
    .PARAMETER Secure
    Prompts for current and new passwords using Read-Host with the -MaskedInput parameter to hide the input
 
    .INPUTS
    None. You cannot pipe objects to Set-AutomateNOWPassword.
     
    .OUTPUTS
    None except for confirmation from Write-Information
     
    .EXAMPLE
    Set-AutomateNOWPassword -OldPasswd 'MyCoolPassword1!' -NewPasswd 'MyCoolPassword4#'
 
    Set-AutomateNOWPassword -Secure
     
    .NOTES
    You must use Connect-AutomateNOW to establish the token by way of global variable.
 
    #>

    
    [Cmdletbinding(DefaultParameterSetName = 'PlainText')]
    Param(
        [Parameter(Mandatory = $true, ParameterSetName = 'PlainText')]
        [string]$OldPasswd,
        [Parameter(Mandatory = $true, ParameterSetName = 'PlainText')]    
        [string]$NewPasswd,
        [Parameter(Mandatory = $true, ParameterSetName = 'Secure')]
        [switch]$Secure
    )
    #[string]$regex_passwd_reqs = '^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?#&])[A-Za-z\d@$!%*?#&]{8,}$' # Note: this needs to be confirmed
    If ($Secure -eq $true) {
        $Error.Clear()
        Try {
            [string]$OldPasswd = Read-Host -Prompt 'Enter current password' -MaskInput
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning -Message "Read-Host failed to receive the current password due to [$Message]."
            Break
        }
        If ($OldPasswd.Length -eq 0) {
            Write-Warning -Message "You must provide the current password. Please try again."
            Break
        }
        $Error.Clear()
        Try {
            [string]$NewPasswd = Read-Host -Prompt 'Enter new password' -MaskInput
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning -Message "Read-Host failed to receive the new password due to [$Message]."
            Break
        }
        If ($NewPasswd.Length -eq 0) {
            Write-Warning -Message "You must provide the new password. Please try again."
            Break
        }
    }
    [string]$regex_passwd_reqs = '.{8,}'
    If ($OldPasswd -notmatch $regex_passwd_reqs) {
        Write-Warning -Message "Somehow the current password does not meet complexity requirements (minimum 8 chars, 1 upper, 1 lower, 1 number, 1 special character). Please check the password that you supplied here."
        Break
    }
    If ($NewPasswd -notmatch $regex_passwd_reqs) {
        Write-Warning -Message "Somehow the new password did not meet complexity requirements (minimum 8 chars, 1 upper, 1 lower, 1 number, 1 special character). Please check the password that you supplied here."
        Break
    }
    If ((Confirm-AutomateNOWSession -Quiet) -ne $true) {
        Write-Warning -Message "Somehow there is not a valid token confirmed."
        Break
    }
    [string]$User = ($anow_session.User)
    Try {
        [string]$OldPasswordEncoded = [System.Net.WebUtility]::UrlEncode($OldPasswd)
        [string]$NewPasswordEncoded = [System.Net.WebUtility]::UrlEncode($NewPasswd)
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "Somehow the UrlEncode method of the [System.Net.WebUtility] class failed due to [$Message]"
        Break
    }
    [string]$Body = ('id=' + $User + '&oldPassword=' + $OldPasswordEncoded + '&newPassword=' + $NewPasswordEncoded + '&repeatPassword=' + $NewPasswordEncoded)    
    [string]$command = '/secUser/updatePassword'
    [hashtable]$parameters = @{}
    $parameters.Add('ContentType', 'application/x-www-form-urlencoded; charset=UTF-8')
    $parameters.Add('Command', $command)
    $parameters.Add('Method', 'POST')
    $parameters.Add('Body', $Body)
    If ($anow_session.NotSecure -eq $true) {
        $parameters.Add('NotSecure', $true)
    }
    [string]$parameters_display = $parameters | ConvertTo-Json -Compress
    Write-Verbose -Message $parameters_display
    $Error.Clear()
    Try {
        [PSCustomObject]$results = Invoke-AutomateNOWAPI @parameters
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "Invoke-AutomateNOWAPI failed to execute [$command] due to [$Message]."
        Break
    }
    If ($results.response.status -eq 0) {
        Write-Information -MessageData "Password successfully changed for $User"
        [string]$response_display = $results.response | ConvertTo-Json
        Write-Verbose -Message $response_display
    }
    ElseIf ($null -eq $results.response.status) {
        Write-Warning -Message "Somehow there was no response data. Please look into this."
        Break
    }
    Else {
        [string]$response_display = $results.response | ConvertTo-Json
        Write-Warning -Message "The attempt to change the password failed. Please see the returned data: $response_display"
        Break
    }
}