/build/static/layout/Breadcrumb_cap_w.png

How to use the K1000 5.4 Inventory API (WSAPI) with PowerShell

NEW:  An updated article with a new script compatible for K1000 6.4 is now available: http://www.itninja.com/blog/view/how-to-use-the-k1000-6-4-inventory-api-wsapi-with-powershell


Inspired from the request of a customer and challenged by the post of the user jknox [ http://www.itninja.com/question/has-anyone-written-a-script-to-utilize-the-inventory-api-in-k1000-v5-4 ]and from this other nice post about generate inventory data for ESXi 5 machines[ http://www.itninja.com/blog/view/uploading-inventory-data-for-esxi-5-machines ] I decided to explore the possibility to use PowerShell to use the new K1000 Inventory API functionality that is available in the version 5.4 of the appliance.

First of all what is this Inventory API in a nutshell?

The Inventory API is a way that allows the user to submit to the K1000 an XML file that contains the inventory of a machine.
This is very interesting and useful because it is possible to build programmatically an XML file (the format is explained in the K1000 documentation) and send it to the K1000

This is really useful if we want to keep track of devices where we do not want or we cannot (not supported OS, company policies restrictions etc etc...) install a K1000 Agent.

The inventory API endpoint is this one: http://k1000-hostname/service/wsapi.php

The documentation explains us that we can basically perform three operations with it:

  1. Ask for a session key (a challenge key): this is fundamental and we will need it for all our subsequent requests
  2. Ask the K1000 to generate for us a KUID: this is very useful if we do not have a way to generate a KUID for an agentless machine. A KUID is at the end a GUID and we can even obtain it with this simple PowerShell line $guid = ([guid]::NewGuid()).Guid.toupper() but if we want the K1000 can do the job for us.
  3. Submit an XML file that contains the inventory information. The documentation is generous about this point: in the K1000 help file is explained in detail the format of this file. If you want to obtain a more generous blueprint of it you can even use this command line from the Kace agent directory KInventory -machine -out myNiceFile.xml

In the documentation there is even a nice Perl script but due to the fact I'm allergic to it (I hate the needs of external libraries) I thought to write an example in PowerShell for the benefit of all the Microsoft lovers.

Some notes about it:

  • This script is given AS IS. Please revise it CAREFULLY before to execute it.
  • You're welcome to improve it and send me comments to this post.
  • To run an unsigned PowerShell script you need to ''relax'' your Execution Policy or sign it. There is a nice article about how to sign a PowerShell script here: http://www.msz.it/?p=284

And now the script. You will need to pass to it three parameters: the file to send (better if included in " " ), the K1000 host name or ip and the API password.
Invoking it without parameters (or incomplete parameters) will prompt a couple of usage line.
I tried to comment the script but if something is not clear please do not hesitate to write me.

<# 
 TITLE:       TestWSAPIv3.ps1
 VERSION:      3.0
 DESCRIPTION: Demostrates how to use the K1000 INVENTORY API
 AUTHOR:      By Marco S. Zuppone for DELL KACE
 NOTES:       You will need at least PowerShell 2.0
 WARNING:     This script is given AS IS. Use it at your own risk! Revise it carefully before to use it.
 USAGE:       TestWSAPI.ps1 [file-to-send.xml] [K1000 Hostname] [API Password]
#>

function MD5_String($s) { #This function calculates the MD5 of a string and returns the hexacecimal representation of it in lowercase.
    $result2 = ""
    $algo = [System.Security.Cryptography.HashAlgorithm]::Create("MD5")
    $hashByteArray2=$algo.ComputeHash($([Char[]]$s))
    foreach ($byte in $hashByteArray2)
    { $result2 += “{0:x2}” -f $byte }
    $result2}
if ($args.Length -lt 3)
    {
    Write-Host "USAGE: TestWSAPI.ps1 [file-to-send.xml] [K1000 Hostname] [API Password]" -ForegroundColor Green
    Write-Host "Example: .\TestWSAPI.ps1 ""d:\myfile.xml"" k1000.mycompany.org myPassword"-ForegroundColor Green
    Break
    }    
    else
    {
    if (-not (Test-Path $args[0] -PathType Leaf))
        {
            Write-Host "the file "$args[0]" does not exist or is not accessible"
            Break
        }
    }

New-Variable -Name password -Value $args[2] -Option ReadOnly #This is the API password you specified in the Security Settings
New-Variable -Name K1000Url -Value ("http://"+$args[1]) -Option ReadOnly #This is the K1000 url. It needs to be in the form "http://hostname"
<#First request
In this first request we ask to the K1000 to provide us a session key.
We will need to save the cookies that the K1000 will send us back to re-use them in the subsequent requests.
#>
$uploadstring=$K1000Url+"/service/wsapi.php?keyreq=true"
$req = [System.Net.WebRequest]::Create($uploadstring);
$myCookiesContainer=New-Object System.Net.CookieContainer #We need to grab the cookies from the first request to mantain the session.
$req.Method ="GET";
$req.ContentType = "text/xml";
$req.useragent="User-Agent: Mozilla/4.0 (compatible; MSIE 7.0;)" #Let try to resemble a real browser
$req.CookieContainer=$myCookiesContainer #We need to grab the cookies from the first request to maintain the session.
$stOut = new-object System.IO.StreamWriter($req);
$resp = $req.GetResponse();
$reader = new-object System.IO.StreamReader($resp.GetResponseStream());
$session_string=$reader.ReadToEnd();
Write-Host "Session string returned from K1000: " $session_string

$passwordMD5=MD5_String($password)
$tokenMD5=MD5_String($session_string+"|"+$passwordMD5) #I calculate the reply token as specified in the K1000 help file.
Write-Host "Token to be used to reply to the K1000: " $tokenMD5

<#Second request
Now we ask the K1000 to generate for us a KUID.
This can be used to upload the inventory of a new agentless machine.
In this example we will not use it but if this request will be succesfull it means that our password is good and we calculated the reply in a good way.
So it is a good test to see if we are doing the right request.
#>

$downloadString=$K1000Url+"/service//wsapi.php?req=newuuid&key="+$tokenMD5
$req = [System.Net.WebRequest]::Create($downloadString);
$req.CookieContainer=$myCookiesContainer #We send back the cookies obtained in the first session
$req.Method ="GET";
$req.ContentType = "text/xml";
$req.useragent="User-Agent: Mozilla/4.0 (compatible; MSIE 7.0;)"
$resp = $req.GetResponse();
$reader = new-object System.IO.StreamReader($resp.GetResponseStream());
Write-Host "The new KUID generated by K1000 using WSAP call is " $reader.ReadToEnd();

<#Third request
Now the tricky part :-) !!!
We send to the K1000 an XML file with the format described in the documentation.
To ease the test I'm using the file I found in the documentation of the K1000
In this example we will not specify in the URI the KUID parameter because it is already specified in the XML file.
#>
$sendFileUri=$K1000Url+'/service/wsapi.php?req=loadxml&key='+$tokenMD5+'&version=5.4'
$req = [System.Net.WebRequest]::Create($sendFileUri);
$req.Method ="POST";
$req.ContentType = "text/xml";
$req.useragent="User-Agent: Mozilla/4.0 (compatible; MSIE 7.0;)"
$req.CookieContainer=$myCookiesContainer #We send back the cookies obtained in the first session
$req.ProtocolVersion="1.0"

$uploadFilePath=((split-path $args[0] -Resolve) + "\" +(split-path $args[0] -Leaf))
$FileContens = [System.IO.File]::ReadAllBytes($uploadFilePath) #I need a byte array. Potentially is the same as Get-Content $uploadFilePath -Raw -Encoding Byte
$stOut = new-object System.IO.StreamWriter($req.GetRequestStream())
$stOut.Write($FileContens,0,$FileContens.Length)
$stOut.Flush()
$stOut.Close()
$resp = $req.GetResponse();
$reader = new-object System.IO.StreamReader($resp.GetResponseStream());
Write-Host "Optional reply of the K1000: "$reader.ReadToEnd();
Write-Host "Status code and Status description of the HTTP reply from the K1000:" $resp.StatusCode " " $resp.StatusDescription
$resp.close()
$resp.Dispose()
Remove-Variable K1000Url -Force #very good idea if you want to use the powershell debugger in the ISE
Remove-Variable password -Force

I want to say a big Thank You to Jeff and Mark from the developer team that put me on the right track giving me precious suggestions!

StockTrader


Comments

  • I want to say a big Thank You to Jeff and Mark from the developer team that put me on the right track giving me precious suggestions! - StockTrader 11 years ago
    • Thanks for the script! Has anyone updated it for the K1000, ver.6.2? - jgutshall 9 years ago
      • Not yet. I plan to try it with ver 6.3 and amend it if needed. - StockTrader 9 years ago
      • I have it now...look my last comment. Now it works for me in version 6.4.
        Regards,
        Marco - StockTrader - StockTrader 9 years ago
  • Awesome!

    I was stuck at the same exact place - that is, uploading the machine.xml via .Net.

    I've incorporated your suggestions into a script of my own that will accomplish the following --

    1) Read inventory.xml files in from a centralized directory.
    2) Query KACE via MYSQL to see if the host is already in inventory before requesting a new UUID from the K1000 API.

    I did this so my *nix admins didnlt have to muck about with perl, and could just dump their XMLs to a CIFS share on a scheduled basis. I just run my script once a day from task scheduler to update the K1000.

    This script assumes the following -

    1) The host that runs it has an ODBC connection to your K1000.
    2) The machine.xml files do not include MAC tags. These are generated on-the-fly by the script when it discovers or request a new uuid.
    3) Each machine that generates an xml file uniquely names it (something like hostname.xml so they do not over write each other).

    As always, please review, edit and test this script before using. I'm still in the testing phase myself.

    [CODE]
    # user edited variables

    $path_to_machine_xml_files = "$pwd\MACHINE_XMLS"
    $MSQL_ODBC_SYSTEM_DSN_NAME = "KACE_MYSQL"
    $k1000 = "your_k1000_host_name"
    $protocol = "https" # http or https
    $p = "your_k1000_password"

    # function to return hashed auth strings

    function GetMD5Hash([string]$inputString){

    $cryptoServiceProvider = [System.Security.Cryptography.MD5CryptoServiceProvider];
    $hashAlgorithm = new-object $cryptoServiceProvider
    $hashByteArray1 = $hashAlgorithm.ComputeHash([Char[]]$inputString);
    foreach ($byte in $hashByteArray1) { $result1 += "{0:x2}" -f $byte}
    return $result1
    }

    # function to check MYSQL DB for existing hosts' KUIDs

    function CheckforKUID([string]$hostname, [string]$DSN) {

    $sql_command = "select * from ORG1.MACHINE where NAME='$hostname'"
    $obj_connection = New-Object -comobject ADODB.Connection
    $obj_connection.Open($DSN)
    $obj_recordset = New-Object -comobject ADODB.RecordSet
    $obj_recordset.Open($sql_command,$obj_connection)
    $uuid = $obj_recordset.Fields.Item("KUID").Value

    return $uuid

    }

    # script exectuion begins here

    # process each XML file in our XML file directory

    $machine_xml_files = get-childItem $path_to_machine_xml_files

    if ($machine_xml_files -ne $null) {

    foreach ($file in $machine_xml_files) {

    # initial web connection with K1000 and request session key
    $url = "$protocol`://$k1000/service/wsapi.php?keyreq=true"
    $CookieContainer = New-Object System.Net.CookieContainer
    [net.httpWebRequest] $req = [net.webRequest]::create($url)
    $req.CookieContainer = $CookieContainer
    $req.method = "POST"
    $req.useragent = "User-Agent: Mozilla/4.0 (compatible; MSIE 7.0;)"
    $req.ProtocolVersion = "1.0"

    [net.httpWebResponse] $res = $req.getResponse()
    $resst = $res.getResponseStream()
    $sr = new-object IO.StreamReader($resst)
    $result = $sr.ReadToEnd()
    $sr.close()
    $res.close()

    # create hashed our auth string
    $hashed_p = GetMD5Hash($p)
    $concatAuthString = $result + "|" + $hashed_p
    $hashedAuth = GetMD5Hash($concatAuthString)

    # read current xml file
    [xml]$machine_xml_file = Get-Content $file.fullname

    # read hostname from xml
    $hostname = $machine_xml_file.machinestruct.name

    write-host "Processing maxhine XML file for $hostname . . . "

    # check MYSQL DB to see if this is a pre-existing host
    $uuid = CheckforKUID $hostname $MYSQL_ODBC_SYSTEM_DSN_NAME

    # if this is a new machine for to be added to inventory, ask K1000 for new KUID
    if ($uuid -eq $null) {

    $web_client = new-object System.Net.WebClient
    $web_client.Headers.add("Cookie", $res.Headers["Set-Cookie"])
    $web_client.headers.add("Content-Type", "text/xml")

    $url = "$protocol`://$k1000/service/wsapi.php?req=newuuid&key=$hashedAuth"

    $uuid = $web_client.downloadString($url)

    write-host "Got UUID $uuid from K1000"

    } else {

    write-host "Using UUID $uuid from inventory.xml"

    }

    # add KUID to XML file
    $element = $machine_xml_file.createElement("MAC")
    $element.set_InnerText($uuid)
    $machine_xml_file.MachineStruct.appendChild($element) | out-null

    # save xml file
    $machine_xml_file.save($file.fullname)

    # post XML file to K100 WSAPI
    $url = "$protocol`://$k1000/service/wsapi.php?req=loadxml&key=$hashedAuth&KUID=$uuid&version=5.4"
    $req = [System.Net.WebRequest]::Create($url);
    $req.Method = "POST";
    $req.ContentType = "text/xml";
    $req.useragent = "User-Agent: Mozilla/4.0 (compatible; MSIE 7.0;)"
    $req.CookieContainer = $CookieContainer
    $req.ProtocolVersion = "1.0"
    $uploadFilePath = $file.fullname

    $FileContens = [System.IO.File]::ReadAllBytes($uploadFilePath)
    $stOut = new-object System.IO.StreamWriter($req.GetRequestStream())
    $stOut.Write($FileContens,0,$FileContens.Length)
    $stOut.Flush()
    $stOut.Close()

    $resp = $req.GetResponse();
    $reader = new-object System.IO.StreamReader($resp.GetResponseStream());

    Write-Host "Optional reply of the K1000: "$reader.ReadToEnd();
    Write-Host "Status code and Status description of the HTTP reply from the K1000:" $resp.StatusCode " " $resp.StatusDescription

    write-host "Done."

    $resp.close()

    # remove XML file
    remove-item $file.fullname

    }

    }

    [/CODE] - ndelo 11 years ago
    • Hello ndelo,

      your script is very interesting! Thanks for posting it :-)
      Regards,
      StockTrader - StockTrader 11 years ago
  • No problem.

    Also, I just updated it, as I noticed an error with the MYSQL function.

    Ah, testing.

    Cheers,

    Nicholas - ndelo 11 years ago
  • The script v3 can be downloaded from here as well: https://dl.dropbox.com/u/93688199/TestWSAPIv3.ps1
    StockTrader. - StockTrader 11 years ago
  • Nowadays with K1000 v5.5 the script needs to be edited slightly. The KUID-parameter now is mandatory.

    Replace
    $sendFileUri=$K1000Url+'/service/wsapi.php?req=loadxml&key='+$tokenMD5+'&version=5.4'

    with
    $sendFileUri=$K1000Url+'/service/wsapi.php?req=loadxml&key='+$tokenMD5+'&KUID=11111111111111111111111111111111&version=5.5'

    The KUID passed with the URI just needs to be valid, you may upload a different KUID in your XML file.

    And another hint:
    K1000 5.5 does NOT like XMLs encoded in UTF-8 --with-- BOM, so use UTF-8 --without-- BOM instead or try plain ASCII if everything else fails.


    Best regards,
    Christian - chrpetri 10 years ago
    • Thanks for you comment. Very interesting :-),
      Marco - StockTrader 10 years ago
  • I transform this powershell script into a small C# application (.NET 4) and somes features : add proxy configuration, use correctly the KUID, manage exceptions and return exit codes.

    See the post on my website : http://www.guismai.fr/2014/11/kace-k1000-wsapi-csharp/ - Greg Mérot 10 years ago
  • I have an updated version of the script. This version works well with K1000 6.4:

    <#
    TITLE: TestWSAPIv4.ps1
    VERSION: 4.0
    .DESCRIPTION
    Demostrates how to use the K1000 INVENTORY API
    AUTHOR: By Marco S. Zuppone for DELL KACE
    .NOTES
    You will need PowerShell 2.0
    WARNING: This script is given AS IS. Use it at your own risk! Revise it carefully before to use it.
    .EXAMPLE
    TestWSAPI.ps1 [file-to-send.xml] [K1000 Hostname] [API Password]
    #>

    function MD5_String($s) { #This function calculates the MD5 of a string and returns the hexacecimal representation of it in lowercase.
    $result2 = ""
    $algo = [System.Security.Cryptography.HashAlgorithm]::Create("MD5")
    $hashByteArray2=$algo.ComputeHash($([Char[]]$s))
    foreach ($byte in $hashByteArray2)
    { $result2 += “{0:x2}” -f $byte }
    $result2}
    if ($args.Length -lt 3)
    {
    Write-Host "USAGE: TestWSAPI.ps1 [file-to-send.xml] [K1000 Hostname] [API Password]" -ForegroundColor Green
    Write-Host "Example: .\TestWSAPI.ps1 ""d:\myfile.xml"" k1000.mycompany.org myPassword"-ForegroundColor Green
    Break
    }
    else
    {
    if (-not (Test-Path $args[0] -PathType Leaf))
    {
    Write-Host "the file "$args[0]" does not exist or is not accessible"
    Break
    }
    }

    New-Variable -Name password -Value $args[2] -Option ReadOnly #This is the API password you specified in the Security Settings
    New-Variable -Name K1000Url -Value ("http://"+$args[1]) -Option ReadOnly #This is the K1000 url. It needs to be in the form "http://hostname"
    <#First request
    In this first request we ask to the K1000 to provide us a session key.
    We will need to save the cookies that the K1000 will send us back to re-use them in the subsequent requests.
    #>
    $uploadstring=$K1000Url+"/service/wsapi.php?keyreq=true"
    $req = [System.Net.WebRequest]::Create($uploadstring);
    $myCookiesContainer=New-Object System.Net.CookieContainer #We need to grab the cookies from the first request to mantain the session.
    $req.Method ="GET";
    $req.ContentType = "text/xml";
    $req.useragent="User-Agent: Mozilla/4.0 (compatible; MSIE 7.0;)" #Let try to resemble a real browser
    $req.CookieContainer=$myCookiesContainer #We need to grab the cookies from the first request to maintain the session.
    $stOut = new-object System.IO.StreamWriter($req);
    $resp = $req.GetResponse();
    $reader = new-object System.IO.StreamReader($resp.GetResponseStream());
    $session_string=$reader.ReadToEnd();
    Write-Host "Session string returned from K1000: " $session_string

    $passwordMD5=MD5_String($password.ToString())
    $tokenMD5=MD5_String($session_string+"|"+$passwordMD5) #I calculate the reply token as specified in the K1000 help file.
    Write-Host "Token to be used to reply to the K1000: " $tokenMD5

    <#Second request
    Now we ask the K1000 to generate for us a KUID.
    This can be used to upload the inventory of a new agentless machine.
    In this example we will not use it but if this request will be succesfull it means that our password is good and we calculated the reply in a good way.
    So it is a good test to see if we are doing the right request.
    #>

    $downloadString=$K1000Url+"/service//wsapi.php?req=newuuid&key="+$tokenMD5
    $req = [System.Net.WebRequest]::Create($downloadString);
    $req.CookieContainer=$myCookiesContainer #We send back the cookies obtained in the first session
    $req.Method ="GET";
    $req.ContentType = "text/xml";
    $req.useragent="User-Agent: Mozilla/4.0 (compatible; MSIE 7.0;)"
    $resp = $req.GetResponse();
    $reader = new-object System.IO.StreamReader($resp.GetResponseStream());
    $theNewKUID=$reader.ReadToEnd();
    Write-Host "The new KUID generated by K1000 using WSAP call is " $theNewKUID;

    <#Third request
    Now the tricky part :-) !!!
    We send to the K1000 an XML file with the format described in the documentation.
    To ease the test I'm using the file I found in the documentation of the K1000
    In this example we will not specify in the URI the KUID parameter because it is already specified in the XML file.
    #>
    $sendFileUri=$K1000Url+'/service/wsapi.php?req=loadxml&key='+$tokenMD5+'&KUID='+$theNewKUID+'&version=6.0'
    $req = [System.Net.WebRequest]::Create($sendFileUri);
    $req.Method ="POST";
    $req.ContentType = "text/xml";
    $req.useragent="User-Agent: Mozilla/4.0 (compatible; MSIE 7.0;)"
    $req.CookieContainer=$myCookiesContainer #We send back the cookies obtained in the first session
    $req.ProtocolVersion="1.0"

    $uploadFilePath=((split-path $args[0] -Resolve) + "\" +(split-path $args[0] -Leaf))
    $FileContens = [System.IO.File]::ReadAllBytes($uploadFilePath) #I need a byte array. Potentially is the same as Get-Content $uploadFilePath -Raw -Encoding Byte
    $stOut = new-object System.IO.StreamWriter($req.GetRequestStream())
    $stOut.Write($FileContens,0,$FileContens.Length)
    $stOut.Flush()
    $stOut.Close()
    $resp = $req.GetResponse();
    $reader = new-object System.IO.StreamReader($resp.GetResponseStream());
    Write-Host "Optional reply of the K1000: "$reader.ReadToEnd();
    Write-Host "Status code and Status description of the HTTP reply from the K1000:" $resp.StatusCode " " $resp.StatusDescription
    $resp.close()
    $resp.Dispose()
    Remove-Variable K1000Url -Force #very good idea if you want to use the powershell debugger in the ISE - StockTrader 9 years ago
This post is locked

Don't be a Stranger!

Sign up today to participate, stay informed, earn points and establish a reputation for yourself!

Sign up! or login

Share

 
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