Video Screencast Help

At Last! Custom Inventory for Macintosh!

Created: 05 Sep 2008 • Updated: 08 Sep 2008
Language Translations
dougj's picture
+1 1 Vote
Login to vote

For years, we thought that the Macintosh platform did not allow for custom inventory. Finally, a solution has been found.

Using a combination of Inventory Solution for Macintosh, Software Delivery for Macintosh and a couple of shell scripts, we can now do custom inventory on a Mac.

Inventory Solution for Macintosh does not include a built-in function to collect custom inventory, as does Inventory Solution for Windows and Inventory Solution for Unix. This functionality is in increasing demand. As such, the following method is provided as a workaround for creating custom inventory in a Mac environment.

This method utilizes the following:

  • Custom-built shell scripts
  • Software Delivery for Macintosh (prerequisite)
  • Aex-collector from Inventory Solution for Macintosh (prerequisite)

This process was tested on the following solutions:

  • Altiris Agent 6.2.1302
  • Inventory for Macintosh 6.2.1054
  • Software Delivery for Macintosh 6.1.2032

Process Overview

The following is a high-level overview of the process to create and deploy custom inventory scripts to Macintosh clients.

  1. Create a top-level shell script to
    1. execute the nested custom inventory script(s)
    2. run aex-collector, which is part of Inventory Solution for Macintosh
  2. Create the nested shell script(s) - one for each data class - to gather the desired data and create properly formatted NSI file(s).
  3. Archive the above scripts on a Mac client.
  4. Create a Software Delivery for Macintosh package containing the archived file, a program for the package and a Software Delivery task on the Notification Server.

Note: The shell scripts must have the Macintosh line endings in order to run on a Macintosh client. This line ending is [CR] only. There are several free or open source Windows and Mac-based editors that have the functionality to change line endings.

Process Details

1. Create a top-level shell script

The top-level shell script will be run by the Software Delivery task. The script is fairly simple. It sets the shell environment, runs one or more nested custom inventory scripts that create nsi files, sets an environment variable and runs aex-collector from the Inventory Solution package directory.

Note that there are multiple ways to setup these scripts. This method was chosen for its simplicity, to create a managing top-level script and to separate each desired custom inventory data class into a separate nested script and corresponding nsi file.

An example follows:

#!/bin/sh
# This script runs custom inventory scripts from a Software Delivery for Mac package directory 
# and runs aex-collector from the Inventory for Mac package directory.
#
# Get agent install dir 
AGENT_INSTALL_DIR=`aex-helper query path 2>/dev/null`
if [ -z "$AGENT_INSTALL_DIR" ] ; then
	AGENT_INSTALL_DIR="/Applications/Utilities/Altiris/NSAgent"
Fi
# Get queue dir for nsi files
EVENT_QUEUE_DIR=`aex-helper agent -g Configuration event_queue_dir 2>/dev/null`
if [ -z "$EVENT_QUEUE_DIR" ] ; then
	EVENT_QUEUE_DIR="$AGENT_INSTALL_DIR/.var/queue"
fi
# Get Inventory package dir for aex-collector
INV_PACKAGE_DIR=`aex-helper agent -g Configuration package_storage_path 2>/dev/null`
if [ -z "$INV_PACKAGE_DIR" ] ; then
	INV_PACKAGE_DIR="$AGENT_INSTALL_DIR/.var/packages"
fi
INV_PACKAGE_DIR="$INV_PACKAGE_DIR/{DEC68AD9-46F6-4139-9793-AE0E1B050BFC}"
#
# Get the list of custom scripts from the current Software Delivery package directory
# File names for custom inventory scripts must match the pattern "custom-invagent*", e.g., "custom-invagent-uname.sh"
CUSTOM_SCRIPT_LIST=`ls custom-invagent* 2>/dev/null`
#
# Run custom scripts
mkdir -p outdir
rm -f outdir/* >/dev/null 2>&1
for CUSTOM_SCRIPT in $CUSTOM_SCRIPT_LIST ; do
	OUTFILE=`basename "$CUSTOM_SCRIPT" 2>/dev/null`.nsi
	echo "Running custom script: $CUSTOM_SCRIPT..."
	sh "$CUSTOM_SCRIPT" > "outdir/$OUTFILE"
	if [ $? -ne 0 ]; then
		echo "Warning: custom script $CUSTOM_SCRIPT did not return normal exit code."
		echo "Inventory data for this custom script may not be available."
	fi
done
#
# save existing inventory nsi files for future delta inventories - no need to send them with custom inventory data
mkdir -p backup
rm -f backup/* >/dev/null 2>&1
mv "$EVENT_QUEUE_DIR"/*.nsi backup >/dev/null 2>&1
#
# copy custom inventory nsi files to queue dir
cp outdir/*.nsi "$EVENT_QUEUE_DIR" >/dev/null 2>&1
#
# set environment & launch collector
. "$AGENT_INSTALL_DIR/.bin/aex-env"
"$INV_PACKAGE_DIR/aex-collector"
#
# restore inventory nsi files
rm -f "$EVENT_QUEUE_DIR"/*.nsi >/dev/null 2>&1
mv backup/*.nsi "$EVENT_QUEUE_DIR" >/dev/null 2>&1

Essentially, this script:

  1. Determines the path for the required directories - the NS Agent, queue and Inventory for Unix package directories.
  2. Runs all "custom-invagent*.sh" script in the current Software Delivery for Unix package directory. Each of these scripts should create a single nsi file in a subdirectory of the current software delivery package directory. In this example, the subdirectory is named 'output'.
  3. Backs up (moves) existing Inventory Solution nsi files to a temporary location - in this case, a subdirectory named 'backup'.
  4. Copies the custom inventory nsi files to the queue directory.
  5. Runs aex-collector to create an nse file and send it to the Notification Server.
  6. Moves inventory nsi files back to the queue for delta comparisons during future inventory processes.

Of course, this script is just one example of how to accomplish this task.

The queue stores all nsi files that are to be combined into an NSE file. The NSE file is then sent to the Notification Server and loaded into the Altiris database by aex-collector. The NSE file is temporarily housed in this queue. If the client.conf hold_queue value is set to 1, then the NSE file will not be sent to the Notification Server and can be viewed from this directory. Setting hold_queue back to 0 will allow NSE files to be sent to the Notification Server the next time aex-collector is ran.

Aex-collector is the process that gathers the NSI files, or 'inventory fragments', combines them into a single NSE, sends the NSE file to the Notification Server where the data class and table are created, if not already done, and the data is loaded into the database. It requires certain environment variables, which are set by running 'aex-env'.

Note that aex-collector will combine all NSI files in the queue regardless of whether they have been previously collected or not. Therefore, when a custom inventory process runs, any hardware, software and configuration inventory nsi files will also be sent to the Notification Server - unless they are moved from the queue directory prior to running aex-collector. This is not really much of an issue. Note that if the data in the Inventory nsi files has been sent previously, the notification server will recognize this and will not repopulate that same data into the database. Any new or updated data will be populated into the database. So, the only impact of leaving all Inventory nsi files in the queue directory is slightly increased network traffic.

2. Create a nested shell script

The nested shell script will actually gather the desired data and create the actual NSI file. The script can be written in any manner desired but it must put out an nsi file in the proper format with no extraneous characters or other output included. The NSI file format is very specific and must be followed exactly. If a command is ran to gather required data and there is the possibility of returning extraneous data, that data must be redirected elsewhere so it does not appear in the NSI file.

This nested script is executed by the top-level script using this command: sh "$CUSTOM_SCRIPT" > "outdir/$OUTFILE". It simply uses the shell redirection to populate the NSI file.

Sample nested script:

echo "<InventoryClasses>"
echo "<InventoryClass name='MacUname' manufacturer='Altiris' description='' version='1.0' platform='Mac' mifGroup=''      mifClass='Altiris|MacUname|1.0'>"
echo "  <xml xmlns:s=\"uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882\" xmlns:dt=\"uuid:C2F41010-65B3-11d1-A29F-00AA00C14882\" xmlns:rs=\"urn:schemas-microsoft-com:rowset\" xmlns:z=\"#RowsetSchema\">"
echo "    <s:Schema id=\"RowsetSchema\">"
echo "      <s:ElementType name=\"row\" content=\"eltOnly\" rs:updatable=\"true\">"
echo "        <s:AttributeType name=\"c0\" rs:name=\"ComputerName\" rs:number=\"1\" rs:keycolumn=\"true\" mifAttrId=\"1\">"
echo "          <s:datatype dt:type=\"string\" dt:maxLength=\"64\" />"
echo "        </s:AttributeType>"
echo "        <s:AttributeType name=\"c1\" rs:name=\"OtherStuff\" rs:number=\"2\" rs:keycolumn=\"true\" mifAttrId=\"2\">"
echo "          <s:datatype dt:type=\"string\" dt:maxLength=\"64\" />"
echo "        </s:AttributeType>"
echo "      </s:ElementType>"
echo "    </s:Schema>"
echo "    <rs:data>"
echo "      <z:row" 
echo "       c0='`uname -n`'"
echo "       c1='other stuff'"
echo "      />"
echo "    </rs:data>"
echo "  </xml>"
echo "</InventoryClass>"
echo "</InventoryClasses>"

There are values in the NSI file that should be unique. This is not referring to the format but to the values. A line-by-line description follows.

Line 1: echo "<InventoryClasses>"
This is a standard xml tag used in Inventory Solution nsi files and should not be changed
Line 2: echo "<InventoryClass name='MacUname' manufacturer='Altiris' description='' version='1.0' platform='Mac' mifGroup='' mifClass='Altiris|MacUname|1.0'>"
InventoryClass name="MacUname" - This specifices the name of the dataclass and database table for this set of custom inventory data. The dataclass name will be exactly as it appears between the quotes. The table name will be "inv_mac_<inventoryclass name>".
Manufacturer - If this value is something other than 'Altiris', it will be used in the table name, e.g., inv_mac_<Manufacturer>_<inventoryclass name>. This helps categorize tables a bit.
Description - Anything you want. It will appear in the NS console data class description.
Version - Anything you want. Not sure if it appears anywhere.
Platform - leave this value is 'Mac'.
mifGroup - Not used.
mifClass - Not used by anything I do. I just update it for consistency.
Line 3: echo " <xml xmlns:s=\"uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882\" xmlns:dt=\"uuid:C2F41010-65B3-11d1-A29F-00AA00C14882\" xmlns:rs=\"urn:schemas-microsoft-com:rowset\" xmlns:z=\"#RowsetSchema\">"
Leave this line exactly as it appears. All entries and values are required for proper data class creation and data loading.
Lines 4-5: echo " <s:Schema id=\"RowsetSchema\">"
echo " <s:ElementType name=\"row\" content=\"eltOnly\" rs:updatable=\"true\">"
Leave these lines exactly as they appear.
Lines 6-8: echo " <s:AttributeType name=\"c0\" rs:name=\"ComputerName\" rs:number=\"1\" rs:keycolumn=\"true\"
mifAttrId=\"1\">"

echo " <s:datatype dt:type=\"string\" dt:maxLength=\"64\"" />"
echo " </s:AttributeType>"
These lines define a column in the database. All of these can and should be modified. In this script, the double-quotes are escaped to avoid conflicting with the double-quotes used by the echo command. There are other elements available which are not used or listed here, but I wont cover them in this doc as they are not necessary.

name=\"c0\" -identifies the internal variable name that will eventually hold data for this column. These do not have to be c0, c1, etc. They can be anything.
rs:name=\"ComputerName\" - the name of the column in the database table.
rs:number=\"1\" - the sequential order of the columns in the database table. This must begin with 1 and increment sequentially through all attributetypes.
rs:keycolumn=\"true\" - Values are "true" and "false". This is used internally and does NOT create a key column in the database table.
midattrId=\"1\" - should match the value in rs:number.
dt:type=\"string\" - defines the datatype. The column in the databse will become a varchar. String and int are valid dt:types.
dt:maxLength=\"64\" - defines the column length. This is variable up to about 2000 bytes.

Lines 9-11: These lines define a second column in this data class/table.
Lines 12-13: echo " </s:ElementType>"
echo " </s:Schema>"
These are closing tags.
Lines 14-19: echo " <rs:data>"
echo " <z:row"
echo " c0='`uname -n`'"
echo " c1='other stuff'"
echo " />"
echo " </rs:data>"
This is where the data is actually populated in the NSI file. It begins and ends with the <rs:data> tags. Within this is the <z:row ... /> tag. And, within this are the actual assignments of values to each of the AttributeType variables.
Each "<z:row ... /> tag" defines one row in the database. Be sure to close this tag prior to the closing "</rs:data>" tag.

It is possible to gather all data anywhere within this script. It can be inline, as specified here, within the <rs:data> section or anywhere else. The logic can contain any valid shell scripting syntax necessary to gather the desired data and populate the attribute variables in the rs:data section. Be sure that no extraneous data is captured by the nsi output file.

Note that the NSI file format is very specific. Any output from system commands must be captured and redirected to avoid including such output in the NSI file. If extraneous output is included in the NSI file, either the collector will fail to create an NSI file or the data loading process on the Notification Server will fail to load the data into the database.

3. Create an archive file containing the shell scripts

Using Finder on a Mac client computer, archive the scripts into a single archive file. By default, this file will be named 'Archive.zip'. It is acceptable - and recommended - that this file be renamed to something more pertinent.

Be sure to include all scripts into a single archive file.

After archiving these files, the archive file needs to be copied to a new package directory on the Notification Server (or other appropriate package location).

4. Create a Software Delivery for Macintosh Package, Program and Task

Follow the standard procedure for creating a Software Delivery for Macintosh package, program and task. This information is documented in the Symantec/Altiris documentation, in the Knowledge Base (kb.altiris.com) and will soon be on Juice (www.symantec.com/connect).

Note that the archive file will be automatically recognized by the Software Delivery for Macintosh 'Program' definition screen and the 'pkg-rollout' command, a Symantec utility, will automatically be placed on the command line. You will need to add to this command line so that the top-level shell script is executed after the archive file is unzipped on the client. A sample command line would look like:

pkg-rollout -nr -f mycustinv_archive.zip && sh toplevelscript.sh

In the above example, the non-bold portion is the suggested command, created by the NS system. The bold portion needs to be added manually.

Create a Software Delivery for Macintosh task; add the appropriate collection, package, program and schedule and it's ready to go.

Once the data is loaded, you will need to create an appropriate report on the Notification Server. Please follow the standard report building process of your choice.