/build/static/layout/Breadcrumb_cap_w.png

Powershell Function for Kace REST API

Since there isn't official support for the API from Quest and the documentation provided is, well...lacking. I thought I would post what I use to interface with the API with examples of the endpoints I've been able to use successfully.

One of the biggest hurdles is incorrect paths in the documentation. For instance, /api/mi/managed_installs/add_to_machines is in the documentation, but the actual endpoint is /api/managed_install/managed_installs/add_to_machines. Some endpoints don't return the data they say they do, like /api/managed_install/machines/{MachineID}. it says it returns the MIs that the machine ID is a part of, but it just returns the machine information...which is useless (you could just use /api/inventory/machines).

Anyway, all of that aside, the below code (Powershell function) has examples that use the correct paths, and are tested and working on my 8.0 appliance. I use Powershell 5, and can't guarantee functionality for any previous Powershell versions. Also, be careful, as the API does not prompt for confirmation before processing, it just does it. The examples are in the comment-based help (for use with the Get-Help cmdlet). The function supports both single and multiple ORG appliances. For single org appliances, either omit the organization parameter or use -Organization Default. For multiple orgs, just use the name of the org with the Organization parameter. You can also log into the system org using -Organization System.

Please add comments below for any other paths that you have been able to use. Especially ones that differ from the documentation or aren't explained well in the documentation.

Note: Sorry the formatting is so crazy - it's cleaner in ISE.
#Requires -Version 5

#Initialize the WebSession hash table
if (!$Script:KaceREST) {

    $Script:KaceREST = @{}

}

#Force TLS 1.2 (required for K1000 8.0)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

function Send-KaceAPIRequest {
<#
.SYNOPSIS
    This function provides an easy(ish) method to interface with the K1000 REST API. 
.DESCRIPTION
    THe function will automatically log in to the appliance and establish a web session for the specified organiztion.
    Multiple organizations can be used at once, and the function will maintain a separate web session for each org.
    Data is returned from the function as the content from the full HtmlWebResponseObject that has been converted from JSON into a PSObject using ConvertFrom-Json.
    The -FullContent parameter can be used to return the original HtmlWebResponseObject in its entirety.
    
    Note: 
      The Kace REST API documentation is rife with incorrect paths. If the paths in the below examples differ from the documentations, then (at least at this point), the documentation is wrong.
      I use Powershell 5. I have done no testing with any previous version of Powershell, nor do I plan to.
      My K1000 is on version 8.0. This function has only been tested with this version.
      Use at your own risk.

.PARAMETER K1000Url
    The Url of the K1000. Must begin with either http:// or https://

.PARAMETER Method
    HTTP verb to be used in the request. Must be one of the following: GET, POST, PUT, DELETE, OPTIONS, PATCH

.PARAMETER URI
    The path to the API endpoint. Must begin with either /api or /ams. Ex. -URI /api/inventory/machines

.PARAMETER Query
    Query to be added the request. Must begin with a ?. If there are spaces, then the parameter must be enclosed in double or single quotes. Ex. -Query '?filtering=name eq MACHINE1'

.PARAMETER Organization
    The organization to send the request. Defaults to "Default"

.PARAMETER Body
    Only available if the Method is PUT, POST, or PATCH. Accepts a hash table @{Property1=Value1;Property2=Value2}.

.PARAMETER FullContent
    Switch that tells the function to return the full HtmlWebResponseObject instead of the default PsObject. Use for extra troubleshooting information.

.EXAMPLE
Get all machines (limited to 50 devices by default)
    Send-KaceAPIRequest -K1000Url https://K1000 -Method GET -URI /api/inventory/machines

.EXAMPLE
Get all machines (no limit) Note: ALL is case sensitive, but 0 can also be used
    Send-KaceAPIRequest -K1000Url https://K1000 -Method GET -URI /api/inventory/machines -Query '?paging=limit ALL'
    Send-KaceAPIRequest -K1000Url https://K1000 -Method GET -URI /api/inventory/machines -Query '?paging=limit 0'

.EXAMPLE
Get machines based on filters

    Machines with Win 7 enterprise x64 - full machine data -uses Organization called Test
        Send-KaceAPIRequest -K1000Url https://K1000 -Method GET -URI /api/inventory/machines -Query '?shaping=machine all&paging=limit all&filtering=os_name eq Microsoft Windows 7 Enterprise x64' -Organization Test

    Machines with softwre that contains "Chrome" installed - limited machine and software data
        Send-KaceAPIRequest -K1000Url https://K1000 -Method GET -URI /api/inventory/machines -Query '?shaping=machine limited,software limited&paging=limit all&filtering=software.display_name co Chrome'

    Machines in a label named Test - Note: if using label ID (label.id) to filter for devices in a SMART label, know that the ID is based on the manual label that the smart label is associated with, not the ID of the smart label
        Send-KaceAPIRequest -K1000Url https://K1000 -Method GET -URI /api/inventory/machines -Query '?shaping=machine all&paging=limit ALL&filtering=label.name eq Test'

.EXAMPLE
Add machine to Managed Install

    Get machines in set of names
        $Machines = Send-KaceAPIRequest -K1000Url https://K1000 -Method GET -URI /api/inventory/machines -Query '?shaping=machine limited&paging=limit ALL&filtering=name in MACHINE1;MACHINE2'

    Add machines from previous query to managed install with ID 111 in Org Test
    Note: defining the machine IDs as an array is necessary in case only one machine is being added
        Send-KaceAPIRequest -K1000Url https://K1000 -Method PUT -URI /api/managed_install/managed_installs/111/add_to_machines -Organization Test -Body @{Machines=[array]$Machines.Machines.Id}

.EXAMPLE
Remove machines from Managed Install
    
    Remove same machines from above add request
        Send-KaceAPIRequest -K1000Url https://K1000 -Method PUT -URI /api/managed_install/managed_installs/111/remove_from_machines -Organization Test -Body @{Machines=[array]$Machines.Machines.Id}

.EXAMPLE
Run a script with ID 15 on machines in a label named Test and check the status after 5 seconds

    Get machines and format a string for the below post request (IDs must be separated by commas)
    $Machines = Send-KaceAPIRequest -K1000Url https://K1000 -Method GET -URI /api/inventory/machines -Query '?paging=limit 0&shaping=machine limited&filtering=label.name eq Test'
        
    [string]$MachineIDs = $null

    foreach ($MachineID in $Machines.Machines.Id) {

        $MachineIDs += ',' + $MachineID

    }

    $MachineIDs = $MachineIDs.Substring(1)
    
    Note: Returns 0 if failed
    $ScriptRun = Send-KaceAPIRequest -K1000Url https://K1000 -Method POST -URI /api/script/15/actions/run?machineIDs=$MachineIDs

    if ($ScriptRun[0] -ne 0) {

        Start-Sleep -Seconds 5

        #Check Status of request
        Send-KaceAPIRequest -K1000Url $K1Url -Method GET -URI /api/script/runStatus/$ScriptRun

    }

    else {

        $ScriptRun[1]

    }

.LINK
    https://support.quest.com/download/downloads?id=6083693

.LINK
    http://support.quest.com/technical-documents/kace-sma/8.0/api-reference-guide/
#>

    Param (

        [Parameter(Position=0,Mandatory=$true,HelpMessage='Must begin with ''http://'' or ''https://''')]
        [ValidateScript({$_ -like "http://*" -or $_ -like "https://*"})]
        [String]
        $K1000Url,
        
        [Parameter(Position=1,Mandatory=$true,HelpMessage='Insert a valid HTTP verb: GET, OPTIONS, POST, PUT, DELETE, or PATCH')]
        [ValidateSet('GET','POST','PUT','DELETE','OPTIONS','PATCH')]
        [String]
        $Method='GET',

        [Parameter(Position=2,Mandatory=$true,HelpMessage='Must begin with ''/api'' or ''/ams''')]
        [ValidateScript({$_ -like "/api*" -or $_ -like "/ams*"})]
        [String]
        $URI,

        [Parameter(Position=3)]
        [ValidateScript({$_ -like "?*"})]
        [String]
        $Query,

        [Parameter(Position=4)]
        [String]
        $Organization='Default',

        [Parameter(Position=6)]
        [ValidateScript({$Script:KaceREST.Keys -contains $Organization})]
        [Switch]
        $ResetCookies=$false,

        [Parameter(Position=7)]
        [Switch]
        $FullContent=$false


    )

    DynamicParam {

        #Define Body paramter if method is PUT, POST, or PATCH
        if ($Method -in ('POST','PUT','PATCH')) {

            #Create parameter attributes
            $BodyParamAttributes = New-Object -Type System.Management.Automation.ParameterAttribute -Property @{

                Position=5
                ParameterSetName='_AllParameterSets'
                HelpMessage='Accepts a hastable with the required properties that will be converted to JSON'

            }

            #Add parameter attributes to collection
            $BodyParamAttributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $BodyParamAttributeCollection.Add($BodyParamAttributes)

            #Create parameter
            $BodyParam = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Body", [Hashtable],$BodyParamAttributeCollection)

            #Define and return parameter
            $BodyParamDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
            $BodyParamDictionary.Add("Body",$BodyParam)
            Return $BodyParamDictionary

        }

    }

    Begin {

        if ($ResetCookies) {

            $Script:KaceREST.Remove($Organization)

        }

        if (!$Script:KaceREST."$Organization") {

            #Get credentials for login and create login JSON request
            #HTTPS is recommended since credentials are sent in plain text
            #If the password needs to be stored to be able to run API requests for automation, then please read the following articles for doing this as securely as possible.
            #https://blog.kloud.com.au/2016/04/21/using-saved-credentials-securely-in-powershell-scripts/
            #https://stackoverflow.com/questions/29103238/accessing-windows-credential-manager-from-powershell
            #https://www.powershellgallery.com/packages/CredentialManager/2.0 (requires Powershell 5)
            try {
            
                $RestCredentials = Get-Credential -Message "Type in the K1000 REST Username/Password for Org $Organization"

                $RESTUserName = $RestCredentials.UserName

                if (!$RestCredentials -or $RestCredentials.Password.Length -eq 0) {
                
                    Throw "Operation cancelled by user or invalid credential object"
                    
                }

                $RESTPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR(($RestCredentials.Password)))

                $LoginBody = ConvertTo-Json -InputObject ([ordered]@{'password'=$RESTPassword;'userName'=$RESTUserName;'organizationName'=$Organization})

            }

            catch {

                if ($_.Exception -eq 'Operation cancelled by user or invalid credential object') {

                    Write-Error -Message "Unable to create login request for $K1000Url`: Invalid credential object or operation cancelled by user."

                    Exit

                }
                
                Write-Error -Message "$_`nUnable to create login request for $K1000Url.`nPlease check that the credentials are correct."

                Exit

            }

            #Log in to the API

            try {

                #Create key in the hash table for specified ORG to store Web session
                $script:KaceREST.Add($Organization,(New-Object Microsoft.PowerShell.Commands.WebRequestSession))
            
                #Post login request
                $LoginRequest = Invoke-WebRequest -Uri ("$K1000Url" + "/ams/shared/api/security/login") -Method Post -ContentType 'application/json' -Headers @{'Accept'='application/json';'x-dell-api-version'=1} -Body $LoginBody -WebSession $script:KaceREST.$Organization -ErrorAction Stop

                #Add CSRF token to web session
                $script:KaceREST.$Organization.Headers.Add('x-dell-csrf-token',$LoginRequest.Headers.'x-dell-csrf-token')

            }

            catch {
                
                #If login failed, clear the web session from the table
                $Script:KaceREST.Remove("$Organization")

                Write-Error -Message "$_`nUnable to log in to REST API at $K1000Url.`nPlease check that the Url and credentials are correct, and that mobile access is enabled for the specified user."

                Exit

            }
            
        }

    }

    Process {

        try {
            
            #Build the request URL
            $RequestUri = "$K1000Url" + "$Uri" + "$Query"

            #Convert the body hash table to JSON
            if ($PSBoundParameters.Body) {

                $Body = ConvertTo-Json -InputObject $PSBoundParameters.Body

            }
            
            #Send the request. -ContentType and Body parameters are required for PUT/POST requests. They are here for all requests because it simplifies coding and are ignored for the GET method anyway.
            $Response = Invoke-WebRequest -Uri $RequestUri -Method $Method -WebSession $Script:KaceREST.$Organization -ContentType 'application/json' -Body $Body -ErrorAction Stop

        }

        catch {

            Write-Error -Message "$_`n$Method request to `'$RequestUri`' for the $Organization org failed.`nPlease check that the API path is correct and mobile access is enabled for the specified user.`nUse -ResetCookies if you think that the token has expired."
            
            Exit

        }

    }

    End {

        #Return the response in friendly format, which is the content from the original HtmlWebResponseObject, converted from JSON to a PSObject (ConvertFrom-JSON). 
        #Use -FullContent to return the original HtmlWebResponseObject
        if ($Response.GetType().Name -eq 'HtmlWebResponseObject') {

            if ($FullContent) {

                $Response

            }

            else {

                ConvertFrom-JSON -InputObject $Response.Content

            }

        }

        else {

            Write-Error -Message "Unable to return HTML response"

        }

    }

}

#Define K1 URL amd Organization name
$K1Url = "https://K1000"
$OrgName = "Default"

#Get all machines (limited to 50 devices by default)
$Machines = Send-KaceAPIRequest -K1000Url $K1Url -Method GET -URI /api/inventory/machines -Organization $OrgName

Comments

  • Thank you, ClayVan. I just received word back that the API instructions will be corrected in SMA v 8.1. - KenG 6 years ago
  • Thank you, great work! - chrpetri 6 years ago
  • what creditnal are you using for this? - jgarcia29 6 years ago
    • I use my own personal LDAP credentials - the function will pop up a box for username/password, but there is a note in the comments about specifying credentials in the script for automation (see below) - just use the user name and password of an account with appropriate permissions. Also make sure that mobile access is enabled for the user.

      #Get credentials for login and create login JSON request
      #HTTPS is recommended since credentials are sent in plain text
      #If the password needs to be stored to be able to run API requests for automation, then please read the following articles for doing this as securely as possible.

      #https://blog.kloud.com.au/2016/04/21/using-saved-credentials-securely-in-powershell-scripts/
      #https://stackoverflow.com/questions/29103238/accessing-windows-credential-manager-from-powershell
      #https://www.powershellgallery.com/packages/CredentialManager/2.0 (requires Powershell 5) - anonymous_129525 6 years ago
  • How would you use this script to get ticket information? - benlrivera 5 years ago
    • Currently, this is not possible. Here's a uservoice feature request for it: https://kace.uservoice.com/forums/82699-sma-k1000/suggestions/33298606-service-desk-api - JasonEgg 5 years ago
      • Thank you for the reply. I checked the api and did not see anything for tickets. I was hoping someone here had a solution. - benlrivera 5 years ago
      • Thank you for the reply. I checked the api and did not see anything for tickets. I was hoping someone here had a solution. - benlrivera 5 years ago
  • For anyone also interested in this, I've published a powershell module that interacts with the SMA as well:

    https://www.itninja.com/blog/view/i-wrote-an-open-source-powershell-module-for-the-kace-sma-api - isudothings 5 years ago
This post is locked
 
This website uses cookies. By continuing to use this site and/or clicking the "Accept" button you are providing consent Quest Software and its affiliates do NOT sell the Personal Data you provide to us either when you register on our websites or when you do business with us. For more information about our Privacy Policy and our data protection efforts, please visit GDPR-HQ