HornbillAPI.psm1

##############################
# Hornbill XMLMC API Azure Powershell Module
#
#.DESCRIPTION
# This module includes functions to allow your Powershell scripts to make and send API calls
# to your Hornbill instance, and process responses accordingly.
#
# Requires Powershell 4.0 or above.
#
#.NOTES
# See example scripts and function documentation for guidance on usage.
##############################

# Initialise the module-level variables
[string]$script:InstanceName = ""
[string]$script:InstanceZone = ""
[string]$script:APIKey = ""
[string]$script:XMLMCParams = ""
[string]$script:InstanceURL = ""

##############################
# .SYNOPSIS
# Allows you to define the Hornbill instance
#
#.DESCRIPTION
# MANDATORY - Allows your Powershell script to define the Hornbill instance to connect
# to, the zone in which it resides, and the API key to use for session generation.
#
#.PARAMETER Instance
# The (case-sensitive) instance name that you wish to connect to.
#
#.PARAMETER Zone
# The (case-sensitive) zone in which the Hornbill instance resides.
# If not supplied, script will work out endpoint from zoneinfo
#
#.PARAMETER Key
# The API key to use to generate authenticate against the Hornbill instance with.
#
#.EXAMPLE
# Set-HB-Instance "yourinstancename" "eur" "yourapikeygoeshere"
##############################
function Set-HB-Instance {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the name of the Instance to connect to (case sensitive)")]
        [ValidateNotNullOrEmpty()]
        [string]$Instance,

        [Parameter(Mandatory = $False, HelpMessage = "Specify the Zone in which the Instance is run. Defaults to 'eur' (case sensitive)")]
        [string]$Zone = "",

        [Parameter(Mandatory = $True, HelpMessage = "Specify the API Key to authenticate the session against")]
        [ValidateNotNullOrEmpty()]
        [string]$Key
    )
    $script:InstanceName = $Instance
    $script:APIKey = $Key

    if ($Zone -ne "") {
        $script:InstanceZone = $Zone
        $script:InstanceURL = "https://" + $script:InstanceZone + "api.hornbill.com/" + $script:InstanceName + "/xmlmc/"
    }
    else {
        $script:InstanceURL = Get-HB-Endpoint $script:InstanceName
    }
}

##############################
# .SYNOPSIS
# Returns the API endpoint information for a Hornbill instance
#
#.DESCRIPTION
# Allows your Powershell script to retrieve zone information for your Hornbill instance
#
#.PARAMETER InstanceID
# The (case-sensitive) Instance ID to retrieve zone information from.
#
#.EXAMPLE
# Get-HB-Endpoint "yourinstanceid"
##############################
function Get-HB-Endpoint {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the Instance ID (case sensitive)")]
        [ValidateNotNullOrEmpty()]
        [string]$InstanceID
    )
    $ZoneInfoURL = "https://files.hornbill.com/instances/" + $InstanceID + "/zoneinfo"
    $InstanceZoneInfo = Get-HB-Request-Endpoint $ZoneInfoURL
    if ($InstanceZoneInfo.Status -eq "ok") {
        $Endpoint = $InstanceZoneInfo.Endpoint
        return $Endpoint
    }
    else {
        $ZoneInfoURL = "https://files.hornbill.co/instances/" + $InstanceID + "/zoneinfo"
        $InstanceZoneInfo = Get-HB-Request-Endpoint $ZoneInfoURL
        if ($InstanceZoneInfo.Status -eq "ok") {
            $Endpoint = $InstanceZoneInfo.Endpoint
            return $Endpoint
        }
    }
}

##############################
# .SYNOPSIS
# Returns zone information for a Hornbill instance
#
#.DESCRIPTION
# Allows your Powershell script to retrieve zone information for your Hornbill instance
#
#.PARAMETER FilesURL
# The (case-sensitive) URL to retrieve zone information from.
#
#.EXAMPLE
# Get-HB-ZoneInfo "https://files.hornbill.co/instances/yourinstanceid/zoneinfo"
##############################
function Get-HB-Request-Endpoint {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the Files URL of the Instance to retrieve zone information from (case sensitive)")]
        [ValidateNotNullOrEmpty()]
        [string]$FilesURL
    )
    $ResultObject = New-Object PSObject -Property @{
        Status         = ""
        Error          = ""
        Endpoint       = ""
        EndpointSource = "legacy"
    }

    try {
        # Build HTTP request headers
        $headers = @{}
        $headers["Accept"] = "application/json"

        # Invoke HTTP request
        if ($script:ProxyURI -and $script:ProxyURI -ne "") {
            if ($script:ProxyCreds) {
                $script:ProxyCreds
                $r = Invoke-RestMethod -Uri $FilesURL -UseBasicParsing -Method Get -Headers $headers -ErrorAction:Stop -Proxy $script:ProxyURI -ProxyCredential $script:ProxyCreds
            }
            else {
                $r = Invoke-RestMethod -Uri $FilesURL -UseBasicParsing -Method Get -Headers $headers -Proxy $script:ProxyURI -ProxyUseDefaultCredentials
            }
        }
        else {
            $r = Invoke-RestMethod -Uri $FilesURL -UseBasicParsing -Method Get -Headers $script:headers
        }
        
        # Read and process response
        $ResultObject.Endpoint = $r.zoneinfo.endpoint + "xmlmc/"
        if ("apiEndpoint" -in $r.zoneinfo.PSobject.Properties.Name ) {
            $ResultObject.Endpoint = $r.zoneinfo.apiEndpoint
            $ResultObject.EndpointSource = "apiEndpoint"
        }
        $ResultObject.Status = "ok"
    }
    catch {
        # HTTP request failed - return exception in response
        $ResultObject.Error = $_.Exception
        $ResultObject.Status = "fail"
    }
    return $ResultObject
}

##############################
# .SYNOPSIS
# Define proxy details to connect through
#
#.DESCRIPTION
# If you are using a proxy to connect to the internet, then this function allows you to define
# the proxy address and authentication details (where applicable)
#
#.PARAMETER ProxyAddress
# Mandatory - URI of the Proxy server to connect through
#
#.PARAMETER ProxyCredentials
# Either:
# - String containing the username to authenticate against the proxy with (will prompt for password)
# - A PSCredential object, such as one generated by the Get-Credential cmdlet.
#
# By not providing this parameter, the module will use the credentials of the current Windows user
#
#.EXAMPLE
# Set-HB-Proxy "http://yourproxyaddress:80/"
# The above will route the requests through your proxy, and will authenticate using the current user details
#
# Set-HB-Proxy "http://yourproxyaddress:80/" "DOMAIN01\User01"
# The above will route the requests through your proxy, and will authenticate using "DOMAIN01\User01" account
##############################
function Set-HB-Proxy {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the address of your Proxy")]
        [ValidateNotNullOrEmpty()]
        [string]$ProxyAddress,
        [Parameter(Mandatory = $False, HelpMessage = "Specify the credentials to authenticate against your Proxy")]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $ProxyCredentials
    )
    $script:ProxyURI = $ProxyAddress
    if ($ProxyCredentials) {
        $script:ProxyCreds = $ProxyCredentials
    }
}

##############################
# .SYNOPSIS
# Add a parameter to the XMLMC request
#
#.DESCRIPTION
# Add a parameter to the XMLMC request
#
#.PARAMETER ParamName
# Mandatory - the name of the parameter
#
#.PARAMETER ParamValue
# Mandatory - the [string] value of the parameter
#
#.PARAMETER ParamAllowEmpty
# Boolean, allow empty string to be passed as a parameter value
#
#.PARAMETER ParamAttribs
# Any attributes to add to the XMLMC request
#
#.EXAMPLE
# Add-HB-Param "application" "com.hornbill.servicemanager"
# Add-HB-Param "h_class" "computer" "onError=""omitElement"" "
#
# Note the escaped double-quotes in the ParamAttribs string.
##############################
function Add-HB-Param {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the name of the Parameter to add")]
        [ValidateNotNullOrEmpty()]
        [string]$ParamName,
        [Parameter(Mandatory = $False, HelpMessage = "Specify the Value of the Parameter")]
        [string]$ParamValue = "",
        [Parameter(Mandatory = $False, HelpMessage = "Specify attributes to add to the Parameter XML node")]
        [boolean]$ParamAllowEmpty = $False,
        [Parameter(Mandatory = $False, HelpMessage = "Specify attributes to add to the Parameter XML node")]
        [string]$ParamAttribs = ""
    )
    if ($ParamName.length -eq 0) {
        return "Parameter name length needs to be greater than zero"
    }
    if (-not $ParamAllowEmpty -And $ParamValue -eq "") {
        return
    }

    $EncodedParamVal = ""
    if ($ParamValue -ne "") {
        $EncodedParamVal = [System.Security.SecurityElement]::Escape($ParamValue)
    }

    $CurrentParam = "<" + $ParamName
    if ($ParamAttribs -and $ParamAttribs.length -gt 0) {
        $CurrentParam = $CurrentParam + " " + $ParamAttribs
    }
    $CurrentParam = $CurrentParam + ">" + $EncodedParamVal + "</" + $ParamName + ">"
    $script:XMLMCParams = $script:XMLMCParams + $CurrentParam
}

##############################
# .SYNOPSIS
# Open a new XML element
#
#.DESCRIPTION
# Allows for the building of complex XML
#
#.PARAMETER Element
# The name of the complex element to open
#
#.EXAMPLE
# Open-HB-Element "primaryEntityData"
##############################
function Open-HB-Element {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the name of the Parameter to add")]
        [ValidateNotNullOrEmpty()]
        [string]$Element
    )
    $script:XMLMCParams = $script:XMLMCParams + "<" + $Element + ">"
}

##############################
# .SYNOPSIS
# Close an already open XML element
#
#.DESCRIPTION
# Allows for the building of complex XML
#
#.PARAMETER Element
# The name of the complex element to close
#
#.EXAMPLE
# Close-HB-Element "primaryEntityData"
##############################
function Close-HB-Element {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the name of the Parameter to add")]
        [ValidateNotNullOrEmpty()]
        [string]$Element
    )
    $script:XMLMCParams = $script:XMLMCParams + "</" + $Element + ">"
}

##############################
# .SYNOPSIS
# Return XML parameters
#
#.DESCRIPTION
# Returns XML string of parameters that have been added by Add-HB-Params, Open-HB-Element or Close-HB-Element
#
#.EXAMPLE
# Get-HB-Params
##############################
function Get-HB-Params {
    if ($script:XMLMCParams.length -gt 0) {
        return "<params>" + $script:XMLMCParams + "</params>"
    }
    return ""
}

##############################
# .SYNOPSIS
# Clear existing XML parameters
#
#.DESCRIPTION
# Clears any existing XMLMC parameters that have been added
#
#.EXAMPLE
# Clear-HB-Params
##############################
function Clear-HB-Params {
    $script:XMLMCParams = ""
}

##############################
# .SYNOPSIS
# Base64 encode a string
#
#.DESCRIPTION
# Returns a Base64 encoded string from a given UTF8 string
#
#.PARAMETER StringVal
# The string to encode
#
#.EXAMPLE
# ConvertTo-HB-B64Encode "encode this please"
##############################
function ConvertTo-HB-B64Encode {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the string to Base-64 encode")]
        [ValidateNotNullOrEmpty()]
        [string]$StringVal
    )
    $UnencodedBytes = [System.Text.Encoding]::UTF8.GetBytes($StringVal)
    $EncodedText = [Convert]::ToBase64String($UnencodedBytes)
    return $EncodedText
}

##############################
# .SYNOPSIS
# Decode a Base64 encoded string
#
#.DESCRIPTION
# Returns a UTF8 string from a given Base64 endcoded string
#
#.PARAMETER StringVal
# The string to decode
#
#.EXAMPLE
# ConvertTo-HB-B64Decode "ZW5jb2RlIHRoaXMgcGxlYXNl"
##############################
function ConvertTo-HB-B64Decode {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the Base-64 string to decode")]
        [ValidateNotNullOrEmpty()]
        [string]$B64Val
    )
    $DecodedString = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($B64Val))
    return $DecodedString
}

##############################
# .SYNOPSIS
# Invokes the XMLMC API call
#
#.DESCRIPTION
# Takes the API Service and Method as inputs to this function, and any parameters
# added with Add-HB-Param, Open-HB-Element or Close-HB-Element, and invokes an API call using
# the instance details defined with the Set-Instance function.
#
# Returns a Powershell Object containing:
# .status - the status of the API call or HTTP response
# .params - any returned parameters from the API
# .error - any returned errors if the HTTP request or API call fails
#
#.PARAMETER XMLMCService
# The Service that contains the API on the Hornbill instance
#
#.PARAMETER XMLMCMethod
# The API Method
#
#.EXAMPLE
# $xmlmcCall = Invoke-HB-XMLMC "session" "getSessionInfo"
#
# If successful This would return:
# $xmlmcCall.status = "ok"
# $xmlmcCall.params = A PSObject containing all output parameters returned by the
# session::getSessionInfo API
##############################
function Invoke-HB-XMLMC {
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Specify the XMLMC Service")]
        [ValidateNotNullOrEmpty()]
        [string]$XMLMCService,
        [Parameter(Mandatory = $True, HelpMessage = "Specify the XMLMC Method")]
        [ValidateNotNullOrEmpty()]
        [string]$XMLMCMethod
    )
    $responseStatus = ""
    $responseParams = ""
    $responseError = ""

    try {
        # Build XMLMC call
        $mcParamsXML = Get-HB-Params
        $bodyString = '<methodCall service="' + $XMLMCService + '" method="' + $XMLMCMethod + '">' + $mcParamsXML + '</methodCall>'
        $body = [XML]$bodyString

        # Build HTTP request headers
        $headers = @{}
        $headers["Content-Type"] = "text/xmlmc"
        $headers["Cache-control"] = "no-cache"
        $headers["Authorization"] = "ESP-APIKEY " + $script:APIKey
        $headers["Accept"] = "application/json"

        # Build URI for HTTP request
        $URI = $script:InstanceURL + $XMLMCService + "/?method=" + $XMLMCMethod

        # Invoke HTTP request

        if ($script:ProxyURI -and $script:ProxyURI -ne "") {
            if ($script:ProxyCreds) {
                $script:ProxyCreds
                $r = Invoke-RestMethod -Uri $URI -UseBasicParsing -Method Post -Headers $headers -ContentType "text/xmlmc" -Body $body -ErrorAction:Stop -Proxy $script:ProxyURI -ProxyCredential $script:ProxyCreds
            }
            else {
                $r = Invoke-RestMethod -Uri $URI -UseBasicParsing -Method Post -Headers $headers -ContentType "text/xmlmc" -Body $body -ErrorAction:Stop -Proxy $script:ProxyURI -ProxyUseDefaultCredentials
            }
        }
        else {
            $r = Invoke-RestMethod -Uri $URI -UseBasicParsing -Method Post -Headers $headers -ContentType "text/xmlmc" -Body $body -ErrorAction:Stop
        }

        # Read and process response
        $responseStatus = $r | Select-Object -ExpandProperty '@status'
        $responseStatusString = 'ok'
        if ($responseStatus -eq $False) {
            $responseError = $r.state.error
            $responseStatusString = 'fail'
        }
        else {
            $responseParams = $r.params
        }
    }
    catch {
        # HTTP request failed - return exception in response
        $responseError = $_.Exception
        $responseStatusString = 'fail'
    }

    # Clear the XMLMC parameters now ready for the next API call
    Clear-HB-Params

    # Return an object of the results.
    $resultObject = New-Object PSObject -Property @{
        Status = $responseStatusString
        Params = $responseParams
        Error  = $responseError
    }
    # Return result object
    return  $resultObject
}

# Export the functions available to the script importing this module
Export-ModuleMember -Function '*'