IPControl.psm1

#Using Module .\Classes.psm1
$InformationPreference = 'Continue'
$ErrorActionPreference = 'Stop'

Function Connect-IPControl {
    <#
    .SYNOPSIS
    Connects to the API of an IPControl instance
 
    .DESCRIPTION
    Connects to the API of an IPControl instance. The session details are then set to global variable $ipcontrol_session.
 
    .PARAMETER Instance
    Mandatory string thatspecifies the hostname of the IPControl instance. For example: ipcontrol.contoso.com
 
    .PARAMETER Port
    Optional int32 port of the IPControl instance. The default is 8443.
 
    .PARAMETER Proxy
    Optional string that specifies a proxy. The format should be: 'http://x.x.x.x:8888' where x.x.x.x is the ip or hostname of the proxy server. Proxy credentials are not supported yet. Caution: Using this setting will force all certificate checks to be disabled. It is equivalent to the -NotSecure function.
 
    .PARAMETER AccessToken
    Optionally specify the access token manually.
 
    .PARAMETER User
    Specifies the user connecting to the API only if you want to enter it on the command line manually. If you do not specify this, you will be prompted for it.
 
    .PARAMETER SecurePass
    Specifies the Secure Password for connecting to the API only if you want to enter it on the command line manually. If you do not specify this, you will be prompted for it. Use ConvertTo-SecureString to create the needed object for this parameter.
 
    .PARAMETER Quiet
    Switch parameter to silence the output of the session details upon login.
 
    .PARAMETER SkipPreviousSessionCheck
    Switch parameter to override the requirement to disconnect from a previous session before starting a new session on a different instance.
 
    .INPUTS
    None. You cannot pipe objects to Connect-IPControl.
 
    .OUTPUTS
    A table showing the pertinent details of your session will be sent to the host. Disable this with -Quiet.
 
    .EXAMPLE
    Standard logon. Be prompted for credential manually.
 
    Connect-IPControl -Instance 'ipcontrol.contoso.com'
 
    .EXAMPLE
    Connect with a secure password that you enter beforehand.
     
    $secure_pass = Read-Host -AsSecureString
    Connect-IPControl -Instance 'ipcontrol.contoso.com' -User 'username' -SecurePass $secure_pass
 
    .EXAMPLE
    Connect with an access token.
 
    Connect-IPControl -Instance 'ipcontrol.contoso.com' -AccessToken 'ey...'
 
    .NOTES
 
    #>

    [OutputType([string])]
    [CmdletBinding(DefaultParameterSetName = 'Credential')]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Instance,
        [Parameter(Mandatory = $true, ParameterSetName = 'AccessToken')]
        [string]$AccessToken,
        [Parameter(Mandatory = $true, ParameterSetName = 'Credential')]
        [string]$User,
        [Parameter(Mandatory = $true, ParameterSetName = 'Credential')]
        [SecureString]$SecurePass,
        [Parameter(Mandatory = $false)]
        [switch]$SkipPreviousSessionCheck,
        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 65535)]
        [int32]$Port = 8443,
        [Parameter(Mandatory = $false)]
        [ValidateScript({ $_ -match '^http[s]{0,}://.{1,}:[0-9]{2,5}$' })]
        [string]$Proxy,
        [Parameter(Mandatory = $false)]
        [switch]$Quiet
    )
    Function New-IPControlAuthenticationProperties {
        [OutputType([hashtable])]
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $true)]
            [string]$User,
            [Parameter(Mandatory = $true)]
            [securestring]$SecurePass,
            [Parameter(Mandatory = $false)]
            [string]$Proxy
        )
        [hashtable]$parameters = @{}
        If ($PSVersionTable.PSVersion.Major -ge 7) {
            [string]$encrypted_string = $SecurePass | ConvertFrom-SecureString -AsPlainText
        }
        Else {
            [string]$encrypted_string = ([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePass)))
        }
        [string]$Body = "username=$User&password=$encrypted_string"
        $parameters.Add('Body', $Body)
        Remove-Variable -Name Body
        [string]$login_url = ($Protocol + '://' + $Instance + ':' + $Port + '/inc-rest/api/v1/login')
        [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
            If (($null -eq ("TrustAllCertsPolicy" -as [type])) -and ($Protocol -eq 'http')) {
                [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 to add the custom certificate policy 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')
        If ($Proxy.Length -gt 0) {
            $parameters.Add('Proxy', $Proxy)
        }
        $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 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]"
                }
            }
            ElseIf ($Message -match 'The remote server returned an error: ') {
                $Error.Clear()
                Try {
                    [int32]$return_code = $Message -split '\(' -split '\)' | Select-Object -Skip 1 -First 1
                }
                Catch {
                    [string]$Message2 = $_.Exception.Message
                    Write-Warning -Message "Unable to extract the error code from [$Message] due to [$Message2]"
                }
            }
            ElseIf ($Message -eq 'An error occurred while sending the request.') {
                Write-Warning -Message "Received the error message '$Message' - This usually means something is wrong with the server! Contact your admins."
                Break
            }
            Else {
                [string]$ReturnCodeWarning = "Invoke-WebRequest failed for an unexpected reason due to [$Message]"
                Write-Warning -Message $ReturnCodeWarning
                Break
            }
            [string]$ReturnCodeWarning = Switch ($return_code) {
                401 { "You received HTTP Code $return_code (Unauthorized). DID YOU MAYBE ENTER THE WRONG PASSWORD? :-)" }
                403 { "You received HTTP Code $return_code (Forbidden). DO YOU MAYBE NOT HAVE PERMISSION TO THIS? [$command]" }
                404 { "You received HTTP Code $return_code (Page Not Found). ARE YOU SURE THIS ENDPOINT REALLY EXISTS? [$command]" }
                Default { "You received HTTP Code $return_code instead of '200 OK'. Apparently, something is wrong..." }
            }
            Write-Warning -Message $ReturnCodeWarning
            Break
        }
        [string]$content_json = $results.Content
        If ($content_json -notmatch '^{"') {
            Write-Warning -Message "The returned content does not appear to be valid. How did this happen?"
            Break
        }
        $Error.Clear()
        Try {
            [PSCustomObject]$token_properties = $content_json | ConvertFrom-Json
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning -Message "ConvertFrom-Json failed due to [$Message]."
            Break
        }
        Return $token_properties
    }
    If ($Instance -match '^http[s]{0,}://') {
        Write-Warning -Message "Please do not include https:// in the name of the instance. See Get-Help Connect-IPControl -Examples for examples."
        Break
    }
    If ($NotSecure -eq $true) {
        [string]$protocol = 'http'
    }
    Else {
        [string]$protocol = 'https'
    }
    If (($ipcontrol_session.ExpirationDate -is [datetime]) -and ($SkipPreviousSessionCheck -ne $true)) {
        [datetime]$current_date = Get-Date
        [datetime]$expiration_date = $ipcontrol_session.ExpirationDate
        [timespan]$TimeRemaining = ($expiration_date - $current_date)
        [int32]$SecondsRemaining = $TimeRemaining.TotalSeconds
        If ($SecondsRemaining -gt 60) {
            [string]$AlreadyConnectedInstance = ($ipcontrol_session.Instance)
            If ($Instance -eq $AlreadyConnectedInstance) {
                Write-Warning -Message "Please use Disconnect-IPControl to disconnect from $AlreadyConnectedInstance before connecting to $Instance (Use -SkipPreviousSessionCheck to override this)"
            }
            Else {
                Write-Warning -Message "Please use Disconnect-IPControl to disconnect your active connection to $AlreadyConnectedInstance (It still has [$SecondsRemaining] seconds remaining) (Use -SkipPreviousSessionCheck to override this)"
            }
            Break
        }
        Else {
            If ($expiration_date -gt (Get-Date -Date '1970-01-01 00:00:00')) {
                If ($SecondsRemaining -lt -172800) {
                    [int32]$DaysRemaining = ($SecondsRemaining * -1) / 86400
                    Write-Warning -Message "Your previous token expired about $DaysRemaining days ago on $expiration_date. Cleaning up the previous session."
                }
                ElseIf ($SecondsRemaining -lt -7200) {
                    [int32]$HoursRemaining = ($SecondsRemaining * -1) / 3600
                    Write-Warning -Message "Your previous token expired about $HoursRemaining hours ago on $expiration_date. Cleaning up the previous session."
                }
                ElseIf ($SecondsRemaining -lt -300) {
                    [int32]$MinutesRemaining = ($SecondsRemaining * -1) / 60
                    Write-Warning -Message "Your previous token expired about $MinutesRemaining minutes ago on $expiration_date. Cleaning up the previous session."
                }
                Else {
                    [int32]$SecondsRemaining = $SecondsRemaining * -1
                    Write-Warning -Message "Your previous token expired about $SecondsRemaining seconds ago on $expiration_date. Cleaning up the previous session."
                }
            }
            $Error.Clear()
            Try {
                Remove-Variable -Name ipcontrol_session -Scope Global -Force
            }
            Catch {
                [string]$Message = $_.Exception.Message
                Write-Warning -Message "Remove-Variable failed to remove the expired `$ipcontrol_session variable due to [$Message]."
                Break
            }
        }
    }
    If ($AccessToken.Length -eq 0) {
        If ($User.Length -eq 0 ) {
            $Error.Clear()
            Try {
                [string]$User = Read-Host -Prompt 'Please enter username (e.g. jsmith)'
            }
            Catch {
                [string]$Message = $_.Exception.Message
                Write-Warning -Message "Read-Host failed to receive the current username due to [$Message]."
                Break
            }
            If ($User.Length -eq 0) {
                Write-Warning -Message 'You needed to specify a username. Please try again.'
                Break
            }
        }
        If ($SecurePass.Length -eq 0 ) {
            $Error.Clear()
            Try {
                [SecureString]$SecurePass = Read-Host -Prompt 'Please enter password (e.g. ******)' -AsSecureString
            }
            Catch {
                [string]$Message = $_.Exception.Message
                Write-Warning -Message "Read-Host failed due to [$Message]."
                Break
            }
        }
        [hashtable]$parameters = @{}
        $parameters.Add('User', $User)
        $parameters.Add('SecurePass', $SecurePass)
        If ($Proxy.Length -gt 0) {
            $parameters.Add('Proxy', $Proxy)
        }
        $Error.Clear()
        Try {
            [PSCustomObject]$auth_response = New-IPControlAuthenticationProperties @parameters
        }
        Catch {
            [string]$Message = $_.Exception.Message
            Write-Warning -Message "New-IPControlAuthenticationProperties failed due to [$Message]."
            Break
        }
        [string]$AccessToken = $auth_response.access_token
    }
    [datetime]$current_date = Get-Date
    [datetime]$expiration_date = $current_date.AddHours(1)
    [hashtable]$header = @{'Authorization' = "Bearer $AccessToken"; }
    [hashtable]$ipcontrol_session = @{}
    $ipcontrol_session.Add('User', $User)
    $ipcontrol_session.Add('Instance', $Instance)
    If ($NotSecure -eq $true) {
        $ipcontrol_session.Add('NotSecure', $True)
    }
    If ($Proxy.Length -gt 0) {
        $ipcontrol_session.Add('Proxy', $Proxy)
    }
    $ipcontrol_session.Add('ExpirationDate', $expiration_date)
    $ipcontrol_session.Add('AccessToken', $AccessToken)
    $ipcontrol_session.Add('Header', $Header)
    $ipcontrol_session.Add('Port', $Port)
    $Error.Clear()
    Try {
        New-Variable -Name 'ipcontrol_session' -Scope Global -Value $ipcontrol_session -Force
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "New-Variable failed to create the session properties object due to [$Message]"
        Break
    }
    Write-Verbose -Message 'Global variable $ipcontrol_session.header has been set. Refer to this as your authentication header.'
    $ipcontrol_session.Add('Protocol', $Protocol)
    [PSCustomObject]$ipcontrol_session_display = [PSCustomObject]@{ Protocol = $protocol; Instance = $Instance; Port = $Port; TokenExpires = $expiration_date; User = $User; AccessToken = ($AccessToken.SubString(0, 5) + '..' + $AccessToken.SubString(($AccessToken.Length - 5), 5)) }
    If ($Quiet -ne $true) {
        Format-Table -InputObject $ipcontrol_session_display -AutoSize -Wrap
    }
}

Function ConvertTo-QueryString {
    <#
    Credit for this function: https://www.powershellgallery.com/packages/MSIdentityTools
    #>

    [CmdletBinding()]
    [OutputType([string])]
    Param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Specialized.OrderedDictionary]$InputObject
    )
    Process {
        $QueryString = New-Object System.Text.StringBuilder
        ForEach ($Item in $InputObject.GetEnumerator()) {
            If ($QueryString.Length -gt 0) { [void]$QueryString.Append('&') }
            [string]$ParameterName = $Item.Key
            If ($Item.value -is [boolean]) {
                If ($Item.value -eq $true) {
                    [void]$QueryString.AppendFormat('{0}={1}', $ParameterName, [System.Net.WebUtility]::UrlEncode('true'))
                }
                Else {
                    [void]$QueryString.AppendFormat('{0}={1}', $ParameterName, [System.Net.WebUtility]::UrlEncode('false'))
                }
            }
            Else {
                [void]$QueryString.AppendFormat('{0}={1}', $ParameterName, [System.Net.WebUtility]::UrlEncode($Item.value))
            }
        }
        [string]$Result = $QueryString.ToString()
        Write-Output $Result
    }
}

Function Invoke-IPControlAPI {
    <#
    .SYNOPSIS
    Invokes the API of an IPControl instance
 
    .DESCRIPTION
    The `Invoke-IPControlAPI` cmdlet sends API commands (in the form of HTTPS requests) to an instance of IPControl. 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: '/Gets/getDeviceByIPAddr'
 
    .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
    The Body string. Use ConvertTo-QueryString to URL-encode your body parameters.
 
    .PARAMETER ContentType
    Specifies the content type of the body (only needed if a body is included)
 
    .PARAMETER Instance
    Specifies the name of the IPControl instance. For example: ipcontrol.contoso.com
 
    .PARAMETER JustGiveMeJSON
    Switch parameter to return the results in a JSON string instead of a PSCustomObject
 
    .INPUTS
    None. You cannot pipe objects to Invoke-IPControlAPI (yet).
 
    .OUTPUTS
    The reponse is provided in a PSCustomObject.
 
    .EXAMPLE
    Invoke-IPControlAPI -Command '/Gets/getDeviceByIPAddr' -Method GET -Body getDeviceByIPAddr?ipAddress=1.2.3.4
 
    .NOTES
    You must use Connect-IPControl 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,
        [Parameter(Mandatory = $false)]
        [string]$Body,
        [Parameter(Mandatory = $false)]
        [string]$ContentType = 'application/x-www-form-urlencoded; charset=UTF-8', # 'application/json',
        [Parameter(Mandatory = $false)]
        [string]$Instance,
        [Parameter(Mandatory = $false)]
        [switch]$JustGiveMeJSON
    )
    If ($ipcontrol_session.Instance.Length -eq 0) {
        Write-Warning -Message "Please use Connect-IPControl to establish your access token."
        Break
    }
    ElseIf ($ipcontrol_session.header.Authorization -notmatch '^Bearer [a-zA-Z-_:,."0-9]{1,}$') {
        [string]$malformed_token = $ipcontrol_session.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: /?)."
        Break
    }
    [int32]$Port = $ipcontrol_session.Port
    If ($Instance.Length -eq 0) {
        [string]$Instance = $ipcontrol_session.Instance
    }
    [hashtable]$parameters = @{}
    If ($NotSecure -eq $true) {
        [string]$protocol = 'http'
    }
    Else {
        [string]$protocol = 'https'
    }
    [int64]$ps_version_major = $PSVersionTable.PSVersion.Major
    $parameters.Add('UseBasicParsing', $true)
    If ($ps_version_major -gt 5) {
        If ($protocol -eq 'http') {
            $parameters.Add('SkipCertificateCheck', $true)
        }
    }
    If ($ps_version_major -lt 5) {
        Write-Warning -Message "Please use either Windows PowerShell 5.x or PowerShell Core. This module is not compatible with Windows PowerShell below version 5."
        Break
    }
    [string]$api_url = ($Protocol + '://' + $Instance + ':' + $Port + '/inc-rest/api/v1' + $command)
    If ($Body.Length -gt 0) {
        Write-Verbose -Message "Sending body: $Body"
        If ($Method -eq 'GET') {
            [string]$api_url = $api_url + '?' + $Body
        }
        Else {
            $parameters.Add('Body', $Body)
        }
    }
    $parameters.Add('Uri', $api_url)
    $parameters.Add('Method', $Method)
    $parameters.Add('ContentType', $ContentType)
    If ($null -ne $ipcontrol_session.Header) {
        $parameters.Add('Headers', $ipcontrol_session.Header) # @($ipcontrol_session.Header)) , @{'Accept' = 'application/json'}
    }
    Else {
        Write-Warning -Message "How is it that the `$ipcontrol_session global variable does not contain a header?"
        Break
    }
    If ($ipcontrol_session.Proxy.Length -gt 0) {
        $parameters.Add('Proxy', $ipcontrol_session.Proxy)
        [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
            If (($null -eq ("TrustAllCertsPolicy" -as [type])) -and ($Protocol -eq 'http')) {
                [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 to add the custom certificate policy 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
        }
    }
    [string]$parameters_debug_display = $parameters | ConvertTo-Json
    Write-Debug -Message "Sending the following parameters to $api_url -> $parameters_debug_display."
    [string]$parameters_verbose_display = $parameters | Select-Object -ExcludeProperty $Headers | ConvertTo-Json
    Write-Verbose -Message "Sending the following parameters to $api_url -> $parameters_verbose_display."
    $ProgressPreference = 'SilentlyContinue'
    $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]"
                Break
            }
        }
        ElseIf ($Message -match 'The remote server returned an error: ') {
            $Error.Clear()
            Try {
                [int32]$return_code = $Message -split '\(' -split '\)' | Select-Object -Skip 1 -First 1
            }
            Catch {
                [string]$Message2 = $_.Exception.Message
                Write-Warning -Message "Unable to extract the error code from [$Message] due to [$Message2]"
                Break
            }
        }
        Else {
            [string]$ReturnCodeWarning = "Invoke-WebRequest failed for unknown reasons under Invoke-IPControlAPI due to [$Message]. This is not normal (at this point) to fail like this so you should check for possible performance problems on the IPControl instance or something else unexpected. Here are the parameters that were sent: $parameters_debug_display"
            Write-Warning -Message $ReturnCodeWarning
            Break
        }
        If ($return_code -gt 0) {
            [string]$ReturnCodeWarning = Switch ($return_code) {
                401 { "You received HTTP Code $return_code (Unauthorized). HAS YOUR TOKEN EXPIRED? ARE YOU ON THE CORRECT DOMAIN? :-)" }
                403 { "You received HTTP Code $return_code (Forbidden). DO YOU MAYBE NOT HAVE PERMISSION TO THIS? [$command]" }
                404 { "You received HTTP Code $return_code (Page Not Found). ARE YOU SURE THIS ENDPOINT REALLY EXISTS? [$command]" }
                Default { "You received HTTP Code $return_code instead of '200 OK'. Apparently, something is wrong..." }
            }
            Write-Warning -Message $ReturnCodeWarning
            Break
        }
    }
    $ProgressPreference = 'Continue'
    [string]$content = $results.Content
    If ($JustGiveMeJSON -eq $true) {
        Return $content
    }
    $Error.Clear()
    Try {
        [PSCustomObject]$content_object = $content | ConvertFrom-JSON
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "ConvertFrom-JSON failed to convert the returned results due to [$Message]."
        Break
    }
    Return $content_object
}

Function Get-IPControlDevice {
    <#
     
    .SYNOPSIS
    Gets a device (a.k.a. an IP address) from an IPControl instance
 
    .DESCRIPTION
    Gets a device (a.k.a. an IP address) from an IPControl instance
 
    .PARAMETER IPAddress
    Mandatory string representing the device (IP address) to look up.
 
    .INPUTS
    None. You cannot pipe objects to Invoke-IPControlAPI (yet).
 
    .OUTPUTS
    The reponse is provided in a PSCustomObject.
 
    .EXAMPLE
    Invoke-IPControlAPI -Command '/Gets/getDeviceByIPAddr' -Method GET -Body getDeviceByIPAddr?ipAddress=1.2.3.4
 
    .NOTES
    You must use Connect-IPControl to establish the token by way of global variable.
 
    #>

    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    Param(
        [ValidateScript({ $_ -match '^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$' })]
        [Parameter(Mandatory = $true)]
        [string]$IPAddress
    )
    [string]$Command = '/Gets/getDeviceByIPAddr'
    [string]$Method = 'GET'
    [hashtable]$parameters = @{}
    [System.Collections.Specialized.OrderedDictionary]$BodyMetaData = [System.Collections.Specialized.OrderedDictionary]@{}
    $BodyMetaData.Add('ipAddress', $IPAddress)
    [string]$Body = ConvertTo-QueryString -InputObject $BodyMetaData
    $parameters.Add('Body', $Body)
    $parameters.Add('Method', $Method)
    $parameters.Add('Command', $Command)
    $Error.Clear()
    Try {
        [PSCustomObject[]]$results = Invoke-IPControlAPI @parameters
    }
    Catch {
        [string]$Message = $_.Exception.Message
        Write-Warning -Message "Invoke-IPControlAPI failed to execute [$Command] on [$Instance] due to [$Message]."
        Break
    }
    If ($results.Count -gt 0) {
        Return $results
    }
}