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:
- Ask for a session key (a challenge key): this is fundamental and we will need it for all our subsequent requests
- 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.
- 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
Regards,
Marco - StockTrader - StockTrader 9 years ago
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
your script is very interesting! Thanks for posting it :-)
Regards,
StockTrader - StockTrader 11 years ago
Also, I just updated it, as I noticed an error with the MYSQL function.
Ah, testing.
Cheers,
Nicholas - ndelo 11 years ago
StockTrader. - StockTrader 11 years ago
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
Marco - StockTrader 10 years ago
See the post on my website : http://www.guismai.fr/2014/11/kace-k1000-wsapi-csharp/ - Greg Mérot 10 years ago
<#
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