Force the Removal of a Specific macOS Configuration Profile

We’ve all done it.  We installed something without fully vetting it out and now we need to get it off – of all of our machines.  Whoops!

The other day, I received a question from a customer asking how he could remove a configuration profile from all of his machines at once – without having to log in to each machine.

Apple actually makes such a task quite easy as viewing, installing and removing a profile from a Mac is inherently built into the operating system itself.  Therefore, with a short script we can detect whether the profile is installed and then remove it if it is.

To manually check the status of a machine’s profiles, you can run, inside of Terminal, the following command.

View All Profiles

sudo /usr/bin/profiles -P

So doing should give you a report out of both the machine and user based profiles installed.

profiles -P.png

In the screenshot above, all 3 profiles are computer based profiles.  If I wanted to remove all of the profiles listed above, all I need to do is use the ‘profiles -D’ command and call the respective profileIdentifier.

Remove All Profiles

sudo /usr/bin/profiles -D

However, removing all profiles is probably a bit forceful, often a little precision can help us in the long run.  In our example above, we may choose to only remove one of the profiles instead of all of them.  To do this, we just need to specify that we’re removing a profile and what the profile identifier name is.

Remove a Single Profile

sudo /usr/bin/profiles -R -p com.landesk.profile

-R is the command to remove the profile and -p specifies we’re removing it by the identifier name.  There are actually quite a few other options available as well, so check out the man page for more info.  For example, you may need to add in the password to remove so the user doesn’t get prompted.  This switch is -z.

Automation

Now, let’s use LANDESK Management Suite to create a custom patch definition that will detect the machines that have a given profile and remove it if you choose to repair it. You can download the custom definition I built on my GitHub site here or build it yourself using the scripts below.

Custom Patch Detection Logic

Just change the variable profileIdentifier to match your desired profile identifier.

#!/bin/sh

# singleConfigurationProfileDetection.sh
# Created by Bennett Norton on 2/6/17.
# Detects the whether a specific profile exists on a machine

# Profile Identifier Name Variable
# Change this name to match the profile identifier you want to remove
# Find the name by typing sudo /usr/bin/profiles -P in Terminal

profileIdentifier="com.landesk.profile"

#  create an output variable with the the potential profile from the machine
#  grep filters all of the results to only show that which matches our desired configuration profile
#  awk allows us to pull just the data we're looking for from the command line

discoveredProfileIdentifier=( $( sudo /usr/bin/profiles -P | grep "$profileIdentifier" | awk '{print $4}') )


if [[ $profileIdentifier != $discoveredProfileIdentifier ]] ; then
 echo "Found: Configuration profile $profileIdentifier was not found on the machine."
 echo "Reason: $profileIdentifier not intalled."
 echo "Expected: $profileIdentifier to not exist."
 echo "Detected: 0"
 exit 0
else
 echo "Found: Configuration profile $discoveredProfileIdentifier was found on the machine."
 echo "Reason: $discoveredProfileIdentifier intalled."
 echo "Expected: $discoveredProfileIdentifier to not exist."
 echo "Detected: 1"
 exit 1
fi

Custom Definition Repair Script

Just as in the first script, you need to change the variable profileIdentifier to match your desired profile identifier.

#!/bin/sh

# singleConfigurationProfileDeletion.sh
# Created by Bennett Norton on 2/6/17.
# Deletes a specific profile on a machine

# Profile Identifier Name Variable
# Change this name to match the profile identifier you want to remove
# Find the name by typing sudo /usr/bin/profiles -P in Terminal

profileIdentifier="com.landesk.profile"

# Delete
sudo /usr/bin/profiles -R -p "$profileIdentifier"


 

Migrate a Mac’s Outlook Settings from an On Premise Server to a Hosted Office 365 Instance

Last week a customer approached me asking for some assistance in migrating their client devices from an on-premise server to their new Office 365 instance hosted in the cloud. In addition to migrating to a new Exchange server, the customer was also planning on moving all of the clients to Outlook 2016 from Outlook 2011.

Having not had the experience personally in doing this in a business environment, I was unsure what the entire process was going to look like.  Luckily, with only a few minutes of google searching, I discovered a post in which Talkingmoose outlined how a simple Applescript would do the trick.

tell application "Microsoft Outlook"
    set server of exchange account 1 to "https://outlook.office365.com/"
end tell

With the AppleScript in hand, we just needed to put together a process to use LANDESK or what is now rebranded as Ivanti.  The Ivanti Mac agent itself, unfortunately, doesn’t support AppleScript directly.  That means that rather than creating an AppleScript file, I needed to write a shell script and simply change the shebang line environment to be osascript as opposed to the standard shell.

#!/usr/bin/env osascript

And that’s it.  I pushed out the script using LANDESK Management Suite, and even with the Outlook client open on the test device, the server URL was appropriately changed.

You can download the script from GitHub, or as I typically do, the full script is below. You’ll see it’s very short.

#!/usr/bin/env osascript
# changeOutlookServer.sh
# Created by Bennett Norton on 1/25/17.

tell application "Microsoft Outlook"
 set server of exchange account 1 to "https://outlook.office365.com/"
end tell

Remember, after you save your file, set the execute permissions on it by opening terminal and running the command below and then copy it to your package server share.

chmod +x /path/to/your/script.sh

With the script ready, you now need to build the software package to distribute to the Macs.  To do this, follow these steps below:

  • From the LANDESK Console, open Tools > Distribution > Distribution Packages
  • Inside the menu tree, highlight My Packages or Public Packages and then select the New Package button on the menubar and select New > Macintosh > Macintosh Agent
  • Give the package a name, description and point the primary file to the .sh file created previously
  • Fill out the Metadata details if desired
  • Save the package

In a true migration environment, I would create a bundle package that pushes out both the Office upgrade as well as the script to change the Exchange server address.  See my previous blog post on how to build an Office 2016 package.

 

 

How to Customize and Silently Deploy Cisco AnyConnect 4.x

If you’re an existing 3.x consumer of Cisco AnyConnect you may be surprised when you load up the 4.x installer and find your users have a plethora of additional features; like Web Security, System Scan, Roaming Security and AMP Enabler.ciscoanyconnect-all

If these are features you’re planning on using, great, you’re all set.  If you were just hoping to leverage the VPN, well, all is not lost.  You can easily customize the installer, providing it a set of instructions indicating what modules you want installed and what modules you want blocked – and it’s all done via a simple PLIST file.

I’ve included the entire example PLIST I used to block everything but the VPN as well as the installer script for LANDESK on my GitHub site here.

Let’s discuss the PLIST first.  Here is an example of one of the dictionary objects found in the PLIST file, in this case it is for the VPN feature.

 <dict>
    <key>attributeSetting</key>
    <integer>1</integer>
    <key>choiceAttribute</key>
    <string>selected</string>
    <key>choiceIdentifier</key>
    <string>choice_vpn</string>
 </dict>

Basically we have a dictionary of values that define the module and tell the installer of whether it should be installed by setting the Integer value to 1.  If you want to block the given module, set the integer value to 0.  Look at the string value to figure out what module you’re enabling or disabling.

You shouldn’t need to adjust any of the other settings.  So go ahead and download the example PLIST file I have on my GitHub site and finish customizing it for your environment.  My PLIST file has everything disabled  outside of the VPN.  If you were to manually install the application, you’d see the window below.  manualinstallciscooptions

Now that you have your PLIST ready to go, you need to save it as ‘ChoiceChangesCiscoAnyConnect.plist’ so it can be properly referenced in our installer script.  If you save it as something else, just make sure you make the appropriate change in your installer script.

OK, we’re now ready to create the installer script.  For simplicity, I’m going to build my installer script to do both the downloading of the AnyConnect package file, the PLIST file we just created and then it’ll kick off the installer – all using SDClient so I can take advantage of the bandwidth and throttling settings.

The entire installer script is pasted below.  In your environment, you’ll likely only need to change the plistFilePath and packageFilePath variables.  The script copies everything to a folder titled CiscoAnyConnect inside of sdcache.  I  purposely chose to put everything in the sdcache folder as the script doesn’t purge anything when finished, letting the standard sdcache cleanup process take care of that at the appropriate time.

Once you have your paths set, you should be ready to go.  You’ll notice that SDClient is used, with several arguments, to download the files.  The -noinstall argument is quite obvious, it tells SDClient to just download and not do anything else.  The -package is telling SDClient where to obtain the file and the -destdir argument is telling SDClient where to copy the file to.

/Library/Application\ Support/LANDesk/bin/sdclient -noinstall -package "$packageToCopy" -destdir "$destinationLocation"

As mentioned before, the script copies down the PLIST file and the Cisco installer.  If you have a DMG version of the Cisco Installer, just mount it on a Mac and copy out the AnyConnect.pkg from the DMG.  I copied mine directly to my standard SWD file share.

Then the scripts wraps up by executing the AnyConnect.pkg installer with a few arguments as well.  We tell macOS we want it to run a pkg intsaller and point to where that file is.  In my example, it’s in the /Library/Application Support/LANDesk/sdcache/CiscoAnyConnect folder.  I leave the -target path empty, basically letting the installer put where it’s designed to.  I then add the flag -applyChoiceChangesXML so that it knows customization is to be performed and I supply the path to that name/file.

installer -pkg "$destinationLocation"/"$packageName" -target / -applyChoiceChangesXML "$destinationLocation"/"$plistName"

And that’s it.  You now have everything you need to deploy your package.  Save out your installer script, I named mine ciscoAnyConnect4.3Deploy.sh but you can name yours whatever you want.

As always, once saved, make sure you give the script the execution permissions by opening Terminal and running:

sudo chmod +x /path/to/script.sh

Now copy your installer script to your LANDESK SWD file share.  I used the same path for my script, PLIST file and AnyConnect.pkg installer.

Once you’ve copied up your script, you just need to create your LANDESK Mac package so you can target and silently deploy Cisco AnyConnect with all of your customizations.

Creating LANDESK Management Suite Mac Packages

  1. Open the LANDESK Console
  2. Navigate to the top menu bar, select Tools > Distribution > Distribution Packages.
  3. In the lower left menu tree, highlight My Packages or Public Packages from within the Distribution Packages window
  4. On the Distribution menu bar, press the New Package button and select New Macintosh Agent package.
  5. Give the package a name
  6. Provide a description as well as any metadata information desired
  7. Set the primary file to the script file you previously transferred to your package share
  8. Fill out the Metadata details if desired, specifically supplying a logo so it shows up properly in the portal
  9. Save the package

Creating a Scheduled Mac Software Distribution Task

  1. Right click on the Mac software distribution package created and select Create Scheduled Task
  2. From the network view, select and drag the desired machine(s), user(s) or query(ies) and drop them onto the task
  3. Now, right click on the task and select properties
  4. Set the desired Task type under Task Settings as to whether you want a push, a policy or a hybrid of the two types in a policy-supported push
  5. Set the radio button in the Portal Settings to either Recommended or Optional if you desire to put the package into Workspaces.  If you’d like to automatically deploy the app, select Run automatically
  6. Change the Reboot Settings or Distribution and Patch settings if desired
  7. Set the schedule task settings with the appropriate start time

PLIST File

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
 <dict>
 <key>attributeSetting</key>
 <integer>1</integer>
 <key>choiceAttribute</key>
 <string>selected</string>
 <key>choiceIdentifier</key>
 <string>choice_vpn</string>
 </dict>
 <dict>
 <key>attributeSetting</key>
 <integer>0</integer>
 <key>choiceAttribute</key>
 <string>selected</string>
 <key>choiceIdentifier</key>
 <string>choice_websecurity</string>
 </dict>
 <dict>
 <key>attributeSetting</key>
 <integer>0</integer>
 <key>choiceAttribute</key>
 <string>selected</string>
 <key>choiceIdentifier</key>
 <string>choice_fireamp</string>
 </dict>
 <dict>
 <key>attributeSetting</key>
 <integer>0</integer>
 <key>choiceAttribute</key>
 <string>selected</string>
 <key>choiceIdentifier</key>
 <string>choice_dart</string>
 </dict>
 <dict>
 <key>attributeSetting</key>
 <integer>0</integer>
 <key>choiceAttribute</key>
 <string>selected</string>
 <key>choiceIdentifier</key>
 <string>choice_posture</string>
 </dict>
 <dict>
 <key>attributeSetting</key>
 <integer>0</integer>
 <key>choiceAttribute</key>
 <string>selected</string>
 <key>choiceIdentifier</key>
 <string>choice_iseposture</string>
 </dict>
 <dict>
 <key>attributeSetting</key>
 <integer>0</integer>
 <key>choiceAttribute</key>
 <string>selected</string>
 <key>choiceIdentifier</key>
 <string>choice_nvm</string>
 </dict>
 <dict>
 <key>attributeSetting</key>
 <integer>0</integer>
 <key>choiceAttribute</key>
 <string>selected</string>
 <key>choiceIdentifier</key>
 <string>choice_umbrella</string>
 </dict>
</array>
</plist>

Installer Script

#!/bin/sh

# ciscoAnyConnect4.3Deploy.sh
# Created by Bennett Norton on 11/30/16.


#File to copy
#change this to match your hosted path, it needs to be http
plistName="ChoiceChangesCiscoAnyConnect.plist"
packageName="AnyConnect.pkg"
plistFilePath="http://yourFileShareServer/Path/To/PLIST"
packageFilePath="http://yourFileShareServer/Path/To/CiscoAnyConnect"
packageToCopy="$packageFilePath"/"$packageName"
plistToCopy="$plistFilePath"/"$plistName"

#Location to copy file to
#change this to match your destination path
destinationLocation='/Library/Application\ Support/LANDesk/sdcache/CiscoAnyConnect'

#Check to see if destination exists and if not, create it
if [ ! -d "$destinationLocation" ]; then
echo "Location doesn't exist. Creating directory"
mkdir $destinationLocation
echo "$destinationLocation created"
fi

#Download and execute command
#You shouldn't need to make any changes here
#-noinstall ensure the package does not get executed when downloaded
#-package is the source url path
#-destdir is the destination url path
/Library/Application\ Support/LANDesk/bin/sdclient -noinstall -package "$packageToCopy" -destdir "$destinationLocation"
/Library/Application\ Support/LANDesk/bin/sdclient -noinstall -package "$plistToCopy" -destdir "$destinationLocation"
installer -pkg "$destinationLocation"/"$packageName" -target / -applyChoiceChangesXML "$destinationLocation"/"$plistName"

Recover Hard Drive Space by Purging the LANDESK SDCache Folder

Whenever a Mac is told to perform a software distribution or patch task, the LANDESK agent will download the binaries for that task and store them in the sdcache folder found under /Library/Application Support/LANDesk. By default, the LANDESK agent will purge any file older than 45 days, so in most scenarios, there is little need to pay attention to what is in that folder.

full-sdcache

However, if you find that your Mac is short on hard drive space, perhaps due to the GB’s worth of patch binaries that were placed on it after having recently updated to the 64-bit version of Microsoft Office, you might find the little purgeSDCache.sh script available on my GitHub site or pasted below a beneficial tool to have in ready in your arsenal of LANDESK packages.

#!/bin/sh

# purgeSDCache.sh
# Created by Bennett Norton on 9/16/16.
# This script will delete all non-standard files/folders from the LANDESK sdcache folder
# Change the path variables


#Script Variables
#change these variables to match your token and desired destination paths
landeskPath="/Library/Application Support/LANDesk/sdcache"


#Check to see if destination path exists and if it does, delete the files older than x number of days old
#The +10 after the -mtime switch tells the command to delete everything older than 10 days. You can adjust that number.
if [ -d "$landeskPath" ]; then
 echo "LANDESK Agent present, deleting and recreating the sdcache folder. "
 find "$landeskPath"/* -mtime +10 -exec rm -rf {} \;
fi

So what does this script do?  It is quite simple really, the script does a search inside the SDCache folder and deletes any and all files older than “10 days.” You can easily adjust age of the files to keep and there is no reason you can’t set that value to 0 days and essentially purge everything.  Just adjust the number after the -mtime switch to whatever suites you.

Now you just have to create the package and deploy to the machines that are short on hard drive space.  Just remember to set the execute permissions on your script prior to copying it to your file share.  You do that by opening Terminal and running the command below:

sudo chmod +x /path/to/script.sh

Creating LANDESK Management Suite Mac Packages

  1. Open the LANDESK Console
  2. Navigate to the top menu bar, select Tools > Distribution > Distribution Packages.
  3. In the lower left menu tree, highlight My Packages or Public Packages from within the Distribution Packages window
  4. On the Distribution menu bar, press the New Package button and select New Macintosh Package.
  5. Give the package a name
  6. Provide a description as well as any metadata information desired
  7. Set the primary file to the script file you previously transferred to your package share
  8. Fill out the Metadata details if desired, specifically supplying a logo so it shows up properly in the portal
  9. Save the package

Creating a Scheduled Mac Software Distribution Task

  1. Right click on the Mac software distribution package created and select Create Scheduled Task
  2. From the network view, select and drag the desired machine(s), user(s) or query(ies) and drop them onto the task
  3. Now, right click on the task and select properties
  4. Set the desired Task type under Task Settings as to whether you want a push, a policy or a hybrid of the two types in a policy-supported push
  5. Set the radio button in the Portal Settings to either Recommended or Optional if you desire to put the package into Workspaces.  If you’d like to automatically deploy the app, select Run automatically
  6. Change the Reboot Settings or Distribution and Patch settings if desired
  7. Set the schedule task settings with the appropriate start time

SSH LANDESK Agent Installer Script

The other day I was working with a customer in which the LANDESK Console was unable to successfully push an agent to a Mac device; despite having SSH access when using Terminal.

As a result, I wrote a script that was used to deploy the agent without leveraging the LANDESK console.  I figured the script could be useful for other new customers that may already have Apple Remote Desktop or some other software distribution tool in place and could use this script to deploy the LANDESK agent in their environment.

The entire script can be downloaded from on GitHub inside the Custom SSH Agent Install folder.

The script is quite simple.  The first section establishes the variables.  Replace the path for your core with the appropriate IP address and change the name of the agent to match your unique name.  Remember to properly handles spaces if need be.  And that should it be all you need to change in this script.  Everything else should work in your environment as written.

#!/bin/bash 
## replace "http://192.168.29.13/ldlogon/mac/" with your core server FQDN or IP
## replace "BaseMacAgentnoav.dmg" with your agent name, remembering to appropriately handle spaces in the name if applicable.
CORE=http://192.168.29.13/ldlogon/mac/
AGENTNAME=BaseMacAgentnoav.dmg

The subsequent section checks to see if the LANDESK folder already exists on the Mac.  If it does, the script will exit out.  This will ensure you’re not re-installing an agent on a machine that is under management.

detect if the agent is already installed
if [ -d "/Library/Application Support/LANDESK" ]; then
 echo 'The LANDESK Agent is Installed'
 exit 1
 
 else echo 'The LANDESK Agent Needs to be Installed'

If the LANDESK folder path does not exist, we will use curl to download the agent from the defined variables above.

 ## download the agent
 curl -o $AGENTNAME $CORE/$AGENTNAME

Once the agent DMG is downloaded, we need to mount it and kick off the installer.

## mount the dmg
 hdiutil attach $AGENTNAME

## install the agent
 sudo -S installer -pkg /Volumes/LDMSClient/ldmsagent.pkg -target /

With the agent installed, we’ll detach the volulme, remove the agent, and close out our if statement that determined if the folder path existed or not.

 ## delete the files downloaded
 hdiutil detach /volumes/LDMSClient
 rm $AGENTNAME
 exit 0
fi

There you have it, hopefully that will assist you in getting the LANDESK agent installed remotely, without having to use the LANDESK console.

 

UEMB260 – Define and Maintain a Desired State Configuration on your Macs Using Custom Scripts and LANDESK Patch

Slides: https://appleintheenterprise.files.wordpress.com/2016/05/desired-state-management-os-x-interchange-16.pptx

GitHub: https://github.com/northice/LDMS-Scripts

 

 

Is Your Custom Created PLIST Unreadable and in the Incorrect Format?

Recently I’ve been building all sorts of custom scripts; writing the outputs to a PLIST file. Some of the custom scripts are simple with only a line or two of outputs being written, but other PLIST files have been much more complex with hundreds of output lines.

Despite my best efforts in paying attention to all of the small details, such as ensuring I have properly typed in the correct syntax for each key, occasionally I’ll run into an error when I try to open the PLIST file with Xcode.

Data Couldn't Be Read

The Data Couldn’t Be Read Because it isn’t in the Correct Format

I should mention that TextWrangler will open and display the file just fine.  So using Xcode is a good way to validate your PLIST file has been properly built.

Luckily, OS X includes a utility, plutil, that will give you the exact line number and hopefully an insightful error code as to what the issue is.  To use the utility, all you need to do is follow the 2 steps below.

  1. Launch Terminal
  2. Type in plutil /path/to/your/file/filename.plist

PLUTIL CommandI like to just drag my file into the Terminal window and let the path properly populate.  It’s up to you, you can drag in your file or you can type it out manually. After so doing, you should receive some information similar to the text below.

Unknown character '/' (0x2f) in <integer> on line 12

Knowing my error was on line 12, using TextWrangler I opened the file to see what I was writing to line 12 in my output file.  In this specific example, line 12 shows that I forgot to close out the integer key.

<integer>18518638592/integer>

BadOutputPlistA quick fix to add in the “<” bracket to my script and now my PLIST is fully functioning and when the LANDESK scanner processes it, I can be more confident the attributes will properly insert.

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

The 3 Step Process of Bundling Scripts with Pkgbuild for a Payload-less Package Deployment

We all love Terminal and its simplicity, right?  After all, that’s why we’ve written scripts to help us in our enterprise Mac management, just so we can spend more time in Terminal.

Can you sense my sarcasm?

While Terminal is definitely simple, it’s quite probable that it’s not your love unless you’re a programmer or have a programming background. For the rest of us, Terminal is the place we go when we’re Googling how to accomplish some task; because the instructions we find have been written by a Terminal lover — despite the fact there is quite likely a fantastic graphical interface method to use.

Well, today is no different.  Chances are you’re a LANDESK administrator and found this page because you want to figure out how to deploy a script you’ve written and the easiest way to do that is to push a package inside of LANDESK’s Management Suite.

To convert our script into a package, we’re going to use Terminal and Apple’s pkgbuild command line tool.

Should I preface now there are graphical options to accomplish what we’re going to do? See Iceberg.  Apple, however, has deprecated their graphical package builder, PackageMaker, relegating it to the far flung corners of their developer website, making it difficult to even find.

Since there are a number of blogs explaining the granular details around pkgbuild available on the Internet, The Grey Blog being one of them, I’m only going to focus on the basics of creating a payload-less package using the -nopayload option on pkgbuild.

As outlined in the man pages for pkgbuild, the -nopayload option “indicates that the package will contain only scripts, with no payload.”  In other words, we’re not installing bits, we’re simply going to invoke some commands the OS can interpret as prescribed in our scripts bundled inside the package.

Using the example from the previous blog post, we’re going to take the script we wrote to create an XML file that the LANDESK inventory scanner can pick up and deliver to the LANDESK core server.  However, any script will work with the steps below, so manipulate as needed.

Our 3 step process is as follows:

  1. Create the folder structure and build script for pkgbuild
  2. Write your script
  3. Create the package
Step 1 – Create the Folder Structure and Build Script for PkgBuild

Alright, let’s create a folder to house our scripts.  In my example, using Finder, I’m going to create a new folder on my Desktop called SMART HD Detection.  Secondly, I’m going to create a second folder inside my SMART HD Detection folder titled ‘scripts’.

Using TextWrangler or Xcode or some other text editor, I need to create a blank file called postinstall and save it to the scripts folder.  We’ll come back to it later.

Now I need to create a my pkgbuild script.  Again, using TextWrangler or Xcode, create a file  titled package_the_script.sh and save it to the primary folder you created, in my example, the SMART HD Detection.

SMARTHDDetection

We will now add the requisite pkgbuild code to create a payload-less inside the package_the_script.sh script.  This script will be quite generic and essentially reusable for all of the packages you need to create now and in the future.

The code is:


PKG_ID=Gather_SMART_Disk_Status

sudo pkgbuild --identifier com.appleintheenterprise.$PKG_ID --nopayload --scripts ./scripts ./$PKG_ID.pkg

Alright, let’s break this down by the line of code.  Hopefully the #!/bin/bash is straight forward for you.

PKG_ID=Gather_SMART_Disk_Status is specifying a variable, PKG_ID, and it will be used as the name of the package we’re going to create.  You’ll want to replace Gather_SMART_Disk_Status with the desired name of your package.

The sudo pkgbuild has several arguments, which will break down individually. Please note, however, there are a number of other arguments you may want to add.  Again, refer to the man pages.

The –identifier is specifying you as the creator, so add in your domain or unique identifier bundled with the package ID.  The OS X Installer recognizes a package as being an upgrade to an already-installed package only if the package identifiers match, so it is advisable to set a meaningful, consistent identifier when you build the package.

The –nopayload option tells pkgbuilder we will be creating a payload-less package.

The –scripts ./scripts identifies the location of our script files that we want bundled up.

Now we can wrap everything up and name it ./$PKG_ID.pkg. Using the name variable allows us to reuse the same command line over and over.

Lastly, I need to save the script and mark it for execution by opening Terminal and browsing to my folder location.  In my instance, it’ll be ~/Desktop/“SMART HD Detection”  Once inside the folder structure, I’ll run the command “sudo chmod a+x package_the_script.sh”.

As can be seen, I didn’t sign this package.  If you need your package to work with Gatekeeper introduced in OS X 10.8, you’re going to need to use the –sign argument.

Step 2 – Write Your Script

Again, using TextWrangler or Xcode, I’m going to open the postinstall I created earlier and write my custom script into it.  My script is going to detect the status of the SMART HD.  However, you can write scripts to install printers, run software updates, reboot the machine, or any other number of tasks.  Just write or copy your script into the postinstall file and save it.

#!/bin/bash

OUTPUT_FILE="/Library/Application Support/LANDesk/CustomData/SmartHardDriveStatus.xml"

echo "&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;" &gt; "$OUTPUT_FILE"

echo "&lt;SMART_info&gt;" &gt;&gt; "$OUTPUT_FILE"

diskutil list | egrep "^/" | while read drive
do
     DRIVE=`basename $drive`
     STATUS=`diskutil info $drive | grep SMART | awk '{ $1=$2="" ;print $0 }'`
     echo "&lt;$DRIVE&gt; $STATUS &lt;/$DRIVE&gt;" &gt;&gt; "$OUTPUT_FILE"
done

echo "&lt;/SMART_info&gt;" &gt;&gt; "$OUTPUT_FILE"
Step 3 – Create the Package

OK, we’re nearly there.  All I need to do now is to execute the script ‘package_the_script.sh’ and it will run pkgbuild and create the package for us.

To do this, again open Terminal and browse to your folder structure you created.  Again, mine will be ~/Desktop/“SMART HD Detection”

Once there, type “sudo ./package_the_script.sh”.  So doing should create a package inside your folder with the name you provided under the PKG_ID variable.

That’s it, we’re all done.  We can now double click on our package inside of Finder and it will execute.

If you desire to push the script with LANDESK Management Suite to one or more machines, you just will need to zip the pkg file and copy it to your software distribution share.

To build the LANDESK software package that’ll distribute your shiny new package, do the following:

  • From the LANDESK Console, open Tools > Distribution > Distribution Packages
  • Inside the menu tree, highlight My Packages or Public Packages and then select the New Package button on the menubar and select New Macintosh Package
  • Give the package a name, description and point the primary file to the zip file created previously
  • Fill out the Metadata details if desired
  • Save the package

To create a task to deploy your LANDESK package, walk through the steps below:

  • Right click on the package created and select Create Scheduled Task
  • Target the desired machine(s), user(s) or query(ies)
  • Right click on the task and select properties
  • Set the desired Task type under Task Settings
  • If you desire the end user to be able to initiate the task, set the radio button in the Portal Settings to either Recommended or Optional, otherwise set it to Required and it will automatically begin the upgrade during the next maintenance window
  • Change the Reboot Settings on the task to one that forces a reboot
  • Schedule the task