Gain Additional Inventory Insights by Manually Executing the OS X Inventory Scanner

Have you ever had a device that won’t update the core server with its latest inventory scan?  While there are a numerous reasons as to why a scan may not process, unless you execute the inventory scanner manually, or peer into the landesk.log, it can be pretty tough to figure out what the cause might be.

In addition to seeing a real-time view into the scanner, when executing the Inventory Scanner manually, you can add command line switches to customize the scanner.

When I am troubleshooting an inventory issue, I always use the -e -s switches.  The –e switch forces a full hardware and software scan.  By default, the inventory scanner sends up a delta scan (the difference between the current scan and the previous scan), unless being directed to re-baseline the machine.  Using the –e switch will ensure I am forcing a full scan.

The –s switch is signals to the core server that it is to accept this scan, regardless of what it was previously expecting.  Again, because the scanner sends up delta scans, if a device gets out of sync with the core, the sync switch forces the core to accept this scan as the new baseline for the device.

So, to run a scan manually, all you need to do is open up Terminal on a Mac and type:

sudo /Library/Application\ Support/LANDesk/bin/ldiscan -s -e

After hitting Return and providing your admin password, you should see the inventory scanner start posting  all of its actions to the Terminal window.  If the scanner is hanging, you’ll see what action it was on.

If the scanner is able to successfully finish, at the bottom of the output you should see a couple of lines to give additional insight as to why the server might be rejecting the scan.  Look for the lines:

Sending scan file to server: fullyqualfied.domain.name
Status from core = HTTP/1.1 503 Service Unavailable

Pay attention to the status from core line.  The Mac scanner is simply making a standard post to IIS on the core server, so all of your standard IIS return codes apply.  See https://community.landesk.com/docs/DOC-1086 for more information on the IIS status codes.

Also, just for added info, below is a list of all of the command line switches for the Mac inventory scanner.

ldscan [-v1|2|3] [-F ][-R][-c alt_ldms_core] [-l alt_ldappl_path][ -o alt_output_dir][ -t ] [-m mode][-w wait_time][ -h ]
   -c      <name>               - specify which core to send inventory to
   -D                       - force a delta scan.
   -e                       - force a full hardware and software scan
   -F                       - force a full inventory scan
   -h                       - this help screen
   -i                       - ignore user and server preference settings
   -l  <path>               - path to alternate ldappl.ini path
   -L                       - limit downloading of ldappl3.ini
   -m <mode>                - set the scan mode
   -N <name>                - use <name> as the device name in the scan
   -o <path>                - write the scan data to <path> instead of sending it to the core
   -p                       - output full product definitions
   -P                       - don't really scan, just display the scan settings.
   -R                       - reset scan database
   -s                       - perform a synchronization scan
   -T <file>                - send <file> to the core
   -t                       - perform a mini scan
   -u                       - unfiltered output (debugging)
   --v1|2|3                 - report formatted version information
   -V <n>                   - set verbosity level (debugging)
   -w <n>                   - wait <n> seconds  before continuing

Extending the LANDESK Management Suite Database to Accommodate Custom Inventory Information

Watch the How-To Video Here

In my previous blog post, I discussed how to capture a bunch of custom data attributes from a Mac, create a PLIST file and send that information up with the client’s inventory scanner.  This custom data collection process is very helpful as it can be tailored to your environment’s exact specifications.

In that post I used Data Analytics to extend my database.  Not everyone has access to the Data Analytics toolset, which is the purpose of this post.

Now, if you don’t mind placing the custom data you collect under the Custom Data inventory tree for the device, all you have to do on your core server is to unblock your “unknown items.” 

This is done by first sending in a scan from a client with your custom data.  It won’t be processed the first time, but it will help you get to your final goal.  The reason it won’t be processed is, that by default, LANDESK blocks the insertion of any unknown data objects.  It will build a list of those objects, allowing you to approve them, which is what you will need to do now that your first scan has been sent to the core server.

You approve your custom data or unknown items, by going to the Configure menu on the Core server, selecting Services, tapping on the Inventory tab, and then clicking on the “Unknown Items” button.  From there, you need to find your attributes from the list, highlight them and then click on the Allow button. 

UnkownItems

Once allowed, you’ll restart the inventory service and send up another scan from your client.  This time everything should process appropriately and you should see your information under the Custom Data section of the Inventory tree. 

Custom Data

However, more likely than not, you’re going to want to place your custom data in different locations within the inventory tree.  If that is the case, we need to extend the LANDESK database and create the custom tables and columns.

Extending the LANDESK database is actually quite simple when using the built in tool to LANDESK Management Suite, called CoreDBUtil.exe. 

While adding new tables and columns is easy, undoing a mistake is not easy at all.  Please make sure you test on a non-production core server.  Have a snapshot you can revert to or a backup of your database for when things go wrong.  Even a tiny misspelled word can cause a lot of grief, so plan ahead.  At times it takes me two or three tries to get everything correct, so I always test in my lab before I take my changes to production.

So, to properly leverage CoreDBUtil.exe to extend the database, we need to create a custom XML file that will define the tables and/or columns we want to create.  Luckily for us, LANDESK has a blueprint of what it used to create the database during the install, called datamart.xml.  All we need to do is copy a like table from the datamart.xml file and tweak for our own needs.

If you’re just going to be adding a new column to an existing table, all you need to do is find the table you’re going to add a new column to, add in the line for the column names and save the file out.  You can choose to leave or remove all of the other existing columns, it doesn’t matter either way.

To do this, make a copy of the datamart.xml file, located under Program Files\LANDESK\Management Suite and paste it somewhere easy to access, I typically put it on my desktop.  From there, you need to open the XML file with a text file editor, I prefer to use Notepad ++ on my Windows machines. 

The two most critical lines you need to copy from the datamart.xml file are the first two.  These two lines specify the XML structure for the file as well as the version of the LANDESK database that you’re currently on.  Create a new file in Notepad ++, or whatever editor you’re using, and then copy these two lines from your file and paste them into a new text file.  They should be similar to the two lines below, but may vary slightly in the second line depending on the version of LANDESK Management Suite that you’re on.

<?xml version="1.0" encoding="utf-8"?>
<schema version="10.0.0.0" type=“tables">

Once you’ve added those two lines, it’s easiest for me to remember to close out the schema tag right now, so I’ll add in the closing tag </schema> at this time.  My file now looks like this:

<?xml version="1.0" encoding="utf-8"?>
<schema version="10.0.0.0" type=“tables">

</schema>

Now I go back to the LANDESK blueprint and find a like table to copy and paste to.  If adding to an exiting table, find it, copy it to your custom file and make your small tweaks.

In my example today, I’m going to be collecting all of the package receipts on a Mac and therefore need an entirely new table.  Since these package receipts are related to Software installed, I want to put all of this information under the Software inventory tree, but in a new section titled Package Receipts.  Therefore, I’m going to search the datamart for computer.software to fine a like table that I can use as my baseline. 

The first parentRelation that is solely “Computer.Software” I found in the datamart.xml file was one for the table Data_Files.  I’m going to copy everything from the opening <table> tag to the closing </table> tag.  Typically this will be between 10 and 20 lines of text, but may be more. 

<table name="Data_Files" desc="" metaAdd="Yes" equiJoin="No" displayName="Data Files" parentRelation="Computer.Software" parent="Computer" tableType="26" image="Software.bmp" rollupIdentity="Yes" >
  <column name="Computer_Idn" type="Int" null="No" />
  <column name="Data_Files_Idn" type="Int" identity="Yes" null="No" />
  <column name="FilePath" type="Varchar(255)" null="No" displayName="Path" PK="Yes" />
  <column name="FileName" type="Varchar(255)" displayName="File Name" />
  <column name="ModDate" type="DateTime" displayName="File Date" />
  <column name="FileSize" type="Int" displayName="File Size" displayMask="%d KB" />

  <primaryKey name="XPKDATAFiles">
    <primaryKeyColumn column="Computer_Idn" />
    <primaryKeyColumn column="FilePath" />
  </primaryKey>

  <foreignKey name="R_DATA_Files" foreignTable="Computer">
    <foreignKeyColumn column="Computer_Idn" foreignColumn="Computer_Idn" />
  </foreignKey>

  <index name="XIFDATA_FilesId">
    <indexColumn column="Data_Files_Idn" />
  </index>

</table>

From here, I need to make this table my own.  I’m going to change the table name to “PackageReceipt”, change the display name to “Package Receipt” with a space and then leave the other information as is.  If you don’t like the image associated based on the table you copied, look in the datamart for other values and use one that suits you.

My table name tag now looks like the following:

<table name="PackageReceipt" desc="" metaAdd="Yes" equiJoin="No" displayName="Package Receipt" parentRelation="Computer.Software" parent="Computer" tableType="1" image="Software.bmp" rollupIdentity="Yes" >

I’m now going to add in my own column names based on the information I’m collecting from my custom script I wrote to collect package receipt data.  Therefore, I’m going to create columns for the PackageID, Version, Volume, Location and Install Time.  Because these are all string values, I’m just going to use a type of Varchar.  You may want to use an Int or DateTime value, so look at the datamart.xml for examples.

I’m also going to add into the tag an attributetype=99.  This will allow the data to persist, even when an inventory scan is sent up without package receipt information.  It will only be overwritten by new data types, not a blank value.

I also need to define what column is going to be the primary key.  In my script, I want PackageID to be my primary key, with all of the other attributes being written to it.  As such, I’m going to add PK=“yes” and null=“No” to this column tag. 

<column name="Computer_Idn" type="Int" null="No" />
<column name="PackageReceipt_Idn" type="Int" identity="Yes" null="No"  />
<column name="PackageID" type="Varchar(255)" displayName="Package ID" attributeTableType="99" PK="Yes" null="No"  />
<column name="Version" type="Varchar(255)" displayName="Version" attributeTableType="99" />
<column name="Volume" type="Varchar(255)" displayName="Volume" attributeTableType="99" />
<column name="Location" type="Varchar(255)" displayName="Location" attributeTableType="99" />
<column name="Install_Time" type="Varchar(255)" displayName="Install Time" attributeTableType="99" />

You can see that I left the Computer_IDN from the Data_Files table I copied over but removed the rest.  This is so I can link the Package Receipt to the appropriate computer.  I’ve also created a PackageReceipt_Idn that will be a unique value counter, just to ensure I can handle multiple PackageIDs with the same name, yet still have a unique object in the database.

Now I need to adjust the Primary Key section of the table.  I’m going to make sure that Computer_Idn is set as a Primary Key, PackageReceipt_Idn and PackageID.  So I’ll remove the FilePath Primary Key from the template I copied and add the two aforementioned. 

In addition to adding in the Primary Key Columns, I need to adjust the PrimaryKeyName to match that of my table name, leaving XPK.  In my template, the primaryKey name value was XKPDataFiles.  I named my table PackageReceipt so I’m going to change the primaryKeyName to be XPKPackageReceipt. 

<primaryKey name="XPKPackageReceipt">
  <primaryKeyColumn column="Computer_Idn" />
            <primaryKeyColumn column="PackageReceipt_Idn" />
            <primaryKeyColumn column="PackageID" />
</primaryKey>

I’m going to remove the foreign keys completely because I don’t need them.

As for the indexes to set, my two solely unique values in this table are the computer_idn and the packagereceipt_idn.  Since computer_idn is being indexed under the computer table, I’m going to use packagereceipt_idn as my index.  Change the index name to match the column name, leaving XIF.  In my example, I changed index name=“XIFDATA_FilesId” to “XIFPackageReceipt_Idn”.

 <index name="XIFPackageReceipt_Idn">
            <indexColumn column="PackageReceipt_Idn" />
 </index>

With those changes, I’m now done.  Just make sure you still have your closing </table> tab as well as your closing </schema> tag.  My entire Package_Receipt.xml file looks as follows:

<?xml version="1.0" encoding="utf-8"?>
<schema version="10.0.0.0" type="tables">

    <table name="PackageReceipt" desc="" metaAdd="Yes" equiJoin="No" displayName="Package Receipt" parentRelation="Computer.Software" parent="Computer" tableType="1" image="Software.bmp" rollupIdentity="Yes" >
        <column name="Computer_Idn" type="Int" null="No" />
        <column name="PackageReceipt_Idn" type="Int" identity="Yes" null="No"  />
        <column name="PackageID" type="Varchar(255)" displayName="Package ID" attributeTableType="99" PK="Yes" null="No"  />
        <column name="Version" type="Varchar(255)" displayName="Version" attributeTableType="99" />        <column name="Volume" type="Varchar(255)" displayName="Volume" attributeTableType="99" />
        <column name="Location" type="Varchar(255)" displayName="Location" attributeTableType="99" />
        <column name="Install_Time" type="Varchar(255)" displayName="Install Time" attributeTableType="99" />
        
        <primaryKey name="XPKPackageReceipt">
            <primaryKeyColumn column="Computer_Idn" />
            <primaryKeyColumn column="PackageReceipt_Idn" />
            <primaryKeyColumn column="PackageID" />
        </primaryKey>

        <index name="XIFPackageReceipt_Idn">
            <indexColumn column="PackageReceipt_Idn" />
        </index>
    </table>
</schema>

Save your XML file in an easy to access location.  Again, I’m going to use my Desktop.  Now, on my core server, I’m going to open up a command prompt and browse to “\Program Files\LANDesk\ManagementSuite” and hit Enter.

CoreDBUtil

Now, type in CoreDBUtil.exe /xml=path/to/your/filename.xml.  Include parentheses if you have spaces.  To make my life simple, I type in CoreDBUtil.exe /xml= and then I drag the file onto the command prompt.  Click Enter and you should see a GUI popup.  If you’ve built your XML file correctly, you should be able to hit the Build Components button and the utility will create your tables and or columns. 

If you run into any problems or if the utility throws an error, go to C:\Program Files\LANDesk\ManagementSuite\log and examine the coredbutil.exe.log.

Assuming all went well for you, after the coredbutil.exe utility finishes,  you need to restart your services that connect to the database or just restart the server. 

Once your server is back online, send in your custom inventory scan.  Everything should process without having to approve any unknown items, as your script should be writing all of the data to the tables and columns you just created.  Go and browse your inventory tree for the computer scan you just sent in and validate your work.  That’s it, you’re all finished.

PackageReceipts

To assist you in your testing efforts, below is the Package_Receipt collection script I used in this example as well.

#!/bin/bash
##################################################################
###   Part 1 - Create an Output File with the Package Receipts ###
##################################################################
# Create the Output file with the appropriate XML structure so the inventory scanner can read the data
# Set the Output file path, do not change
outputFile="/Library/Application Support/LANDesk/Data/ldscan.core.data.plist"

# Detect if there is an existing plist file, make a backup and delete the original to ensure clean data is sent up
if [ -e "$outputFile" ]
then
cp "$outputFile" "$outputFile.old"
rm "$outputFile"
fi

# specifying the plist structure
echo '<?xml version="1.0" encoding="UTF-8"?>'  >> "$outputFile"
echo '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' >> "$outputFile"
echo '<plist version="1.0">' >> "$outputFile"
echo "<dict>" >> "$outputFile"

packageReceipts=( $( pkgutil --pkgs ) )
for receipt in "${packageReceipts[@]}"
do

# create the variables for the different attributes
    IFS=$'\n'
    packageID=( $(pkgutil --pkg-info $receipt | grep "package-id" | sed 's/package-id: //' ) )
    version=( $(pkgutil --pkg-info $receipt | grep "version" | sed 's/version: //' ) )
    volume=( $(pkgutil --pkg-info $receipt | grep "volume" | sed 's/volume: //' ) )
    location=( $(pkgutil --pkg-info $receipt | grep "location" | sed 's/location: //' ) )
    installTime=( $(pkgutil --pkg-info $receipt | grep "install-time" | sed 's/install-time: //' ) )
    unset IFS

    echo "<key>Software - Package Receipt - (Package ID:$packageID) - Package ID</key>" >> "$outputFile"
    echo "<string>$packageID</string>" >> "$outputFile"

    echo "<key>Software - Package Receipt - (Package ID:$packageID) - Version</key>" >> "$outputFile"
    echo "<string>$version</string>" >> "$outputFile"

    echo "<key>Software - Package Receipt - (Package ID:$packageID) - Volume</key>" >> "$outputFile"
    echo "<string>$volume</string>" >> "$outputFile"

    echo "<key>Software - Package Receipt - (Package ID:$packageID) - Location</key>" >> "$outputFile"
    echo "<string>$location</string>" >> "$outputFile"

    echo "<key>Software - Package Receipt - (Package ID:$packageID) - Install Time</key>" >> "$outputFile"
    echo "<string>$installTime</string>" >> "$outputFile"

done

# close the plist file
echo "</dict>" >> "$outputFile"
echo "</plist>" >> "$outputFile"

#################################################################
###   Part 2 - Force an Inventory Scan to Run                 ###
#################################################################
# -e forces a hardware and software scan and -s forces a sync
# this step is optional
/Library/Application\ Support/LANDesk/bin/ldiscan -e -s