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