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



Automating the Mundane…Scripting for VMWare Fusion Efficiency

Growing up, it was not uncommon to hear someone quote the phrase “you tell me what you think about when you do not have to think, and I’ll tell you what you are.”  It’s a quote that can be deep, philosophical and ignite some serious introspection.

For me, the answer is easy.  It’s efficiency.  My brain is constantly chewing on how to make whatever insufferable process I’ve been experiencing in the last hour, day, week, etc; more palatable, more efficient, less…well, less sufferable.

Now, I know that declaring war on inefficiency should not be humanity’s primary purpose in life; not everything is a race to the finish line with as limited wasted movement as possible, but tell that to some other guy who’s brain doesn’t dissect every tiny amount of wasted time as potential opportunity for improvement.

When a former employer was asked by a potential new employer what my biggest weakness was, he responded with “well, I don’t know if this is exactly a weakness, but Bennett will constantly be looking for a way to improve a process, to make things more streamlined, even if he spends weeks devising a plan to save a minute’s worth of work during an 8 hour work day.”

Personally I hear that and I think, “wow, look how inefficient I’m being on spending such time to save so little.”

Nonetheless, hear I sit, a self-proclaimed efficiency hunter ready to tell you how I spent last week efficiency hunting within my daily workflow, to save not only time, but frustration and to improve the consistency of my work.

My daily routine as a LANDESK Sales Engineer consists of demonstrating one or more of our products to potential customers.  Due to the number of different products desired to be seen and the potential use cases from the customer that may require an integration demo, I may have up to 8 different virtual machines that need to be spun up.

In order to maintain consistency in my demo environment, I will take snapshots of each VM so I can demo the full capabilities of the software products and quickly reset back to square one for the next demo.

As such, I’m often starting a number of machines, stopping a number of machines, snapshotting them, reverting to snapshots, deleting old snapshots and even just needing to see what snapshots I’ve created.  All of this can be a bit cumbersome when you do it many times a day.

So when I received my new MacBook Pro last week, my efficiency brain kicked in, it was time to automate the entire process.  Luckily for me, VMWare has a utility that allows command line access to do all of the starting and stopping of the machines, as well as the snapshot management.

Perfect right?  Well almost, I didn’t want to write my own script, I wanted to just borrow someone else’s.  Alas, I scoured page one and even page 2 of Google (I know, page 2!) for a number of different search criteria hoping to find a hit, only to come away empty handed.

My efficiency brain wouldn’t let me just quit and give up though.  So I went to work and spent from 9 PM to 3 AM writing my own script and am now making that script available to you for your own efficiency pleasure.

Enjoy!  All you should have to do is fill out the variables at the top and you’ll soon be on your way to VMWare Fusion Management efficiency bliss.  I’ve saved my script as a .command file so I can execute directly from my desktop.


# Version history 
# v1.0 - initial release

# Add the paths to all of your VMs
declare -a arrayPathForVMs=("Documents/VMs/LDSERVER/LDSERVER.vmx" "Documents/VMs/OSX/OSX.vmx" "Documents/VMs/Win8/Win8.vmx")

# Create a directory location variable
vmrunPath="/Applications/VMware Fusion.app/Contents/Library/vmrun"

while :
 cat <<EOF
 VM Fusion Management
 Please enter your choice:
 (1) Start VMs
 (2) Revert to a Snapshot
 (3) Create a Snapshot
 (4) Delete a Snapshot
 (5) List Snapshots
 (6) Suspend all VMs
 (7) Exit
 read -n1 -s
 case "$REPLY" in

 echo "Starting your VMs..."
 cd "${setScriptPath}"
 for i in "${arrayPathForVMs[@]}"; 
 echo "Launching" "$i"
 "${vmrunPath}" -T fusion start "${i}";
 echo "All VMs started."
 Sleep 2
 exit 0

 echo -n "Which snapshot do you want to revert to?"
 read snapshotName
 cd "${setScriptPath}"
 for i in "${arrayPathForVMs[@]}"; 
 echo "Reverting to" "$snapshotName" "on" "$i"
 "${vmrunPath}" -T fusion revertToSnapshot "${i}" "${snapshotName}";
 echo "All VMs reverted."
 Sleep 2
 exit 0

 echo -n "What will the snapshot name be?"
 read newSnapshotName
 cd "${setScriptPath}"
 for i in "${arrayPathForVMs[@]}"; 
 echo "Creating" "$newSnapshotName" "on" "$i"
 "${vmrunPath}" -T fusion snapshot "${i}" "${newSnapshotName}";
 echo "Snapshots created for all VMs"
 Sleep 2
 exit 0

 echo -n "Are you sure you want to delete a snapshot (y/n)? "
 read answer
 if echo "$answer" | grep -iq "^y" ;then
 echo -n "What snapshot do you want to delete?"
 read snapshotToDelete
 cd "${setScriptPath}"
 for i in "${arrayPathForVMs[@]}"; 
 echo "Deleting" "$snapshotToDelete" "on" "$i"
 "${vmrunPath}" -T fusion deleteSnapshot "${i}" "${snapshotToDelete}";
 echo "Snapshot deleted for all VMs"
 read -p "Press [Enter] to close."
 exit 0
 echo "No changes have been made."
 read -p "Press [Enter] to close."
 exit 0

 echo "Listing your snapshots..."
 for i in "${arrayPathForVMs[@]}"; 
 echo "Snapshots for" "$i"
 "${vmrunPath}" -T fusion listSnapshots "${setScriptPath}/""${i}";
 echo "All snapshots displayed"
 read -p "Press [Enter] to close."
 exit 0
 echo "Suspending your VMs..."
 cd "${setScriptPath}"
 for i in "${arrayPathForVMs[@]}"; 
 echo "Suspending" "$i"
 "${vmrunPath}" -T fusion suspend "${i}";
 echo "All VMs suspended."
 Sleep 2
 exit 0

 echo "Exiting..."
 exit 0


 *) echo invalid option;;

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.


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:


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.


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
     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"

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

Creating an XML File to Report Custom Inventory Information

Have you ever needed to collect an additional piece of information, not collected by LANDESK by default, from one of your managed Macs?  The answer to that question will very likely be yes simply because every business environment is unique.

Often, the additional information that needs to be collected is simply a matter of enabling the LANDESK inventory scanner to pickup a new file type.   These types of changes are easy and can be added by leveraging the Manage Software List tool within the LANDESK console.  For more information on this method, see the LANDESK 9.6 Help file link.

However, if you want to collect the state of an object on the device, it may require a script to be written to query OS X itself.  The response from the OS will then need to be captured  in a format the LANDESK inventory scanner can then pick up and transmit back.

By default, the LANDESK inventory scanner can parse an XML or PLIST file placed in the “/Library/Application Support/LANDesk/CustomData” directory.  As such, we just need to write a script that places the response from the OS query into an XML or PLIST file and save it to the CustomData folder.  So doing will allow you to collect any type of information that may be relevant in your environment

The script below, written by Steve Goodrich for a LANDESK Interchange session, is an example script in which the OS is queried for the S.M.A.R.T. status after which the result is written to an output XML file.

This article will walk through each step of the script to explain what is going on, however, it will not be covering the details of XML and BASH.   I’ve posted some links below to research additional information on XML or BASH.


Bash Scripting

OK, let’s create our script.  The first thing we need to do is open an editor.  On OS X, I prefer to use Xcode.  However there are a number of editors out there, like TextWrangler.  Just choose one that fits your preference.  The first line in the script needs to specify it’ll be using BASH.  As such, write:

#! bin/bash

Now we need to specify the output file variable that’ll be called throughout the script along with it’s associated file path.  Because we want the LANDESK inventory scanner to pick up the information, we’ll write out output file to the CustomData folder and give it a name of SmartHardDriveStatus.xml.  You can name your file whatever makes sense for the information you’re collecting.

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

Next we’ll specify inside the output file, the XML structure we’ll be creating.  The echo command will place the text after the quotes into our XML file.  The carrot “>” before the “$OUTPUT_FILE” is telling the echo command where to write and the “$OUTPUT_FILE” is referring the variable created previously.

echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" > "$OUTPUT_FILE" 

With the output file created and the XML structure specified, we need to create the opening XML tag in which we’ll save the SMART status inside of.

echo "<SMART_info>" >> "$OUTPUT_FILE"

OK, now we’re to the fun part.  We need to query OS X and obtain the status of the hard drive.  This can be accomplished using the diskutil command.  Most likely, the custom information you want to obtain will require a different command to query the OS.  Use the internet to search for the command you need and replace it with what is below.  Just make sure you echo your result and write it to an XML tag.  The example below has the results from the variables DRIVE and STATUS being written to an XML tag named from the DRIVE variable with the STATUS being written as the information inside the XML tag.

diskutil list  | egrep "^/" | while read drive

     DRIVE=`basename $drive`
     STATUS=`diskutil info $drive | grep SMART | awk '{ $1=$2="" ;print $0 }'`
     echo "<$DRIVE> $STATUS " >> "$OUTPUT_FILE"

The final step to the script requires us to close out the first SMART_info tag we created.  It’s a pretty simple command.

echo "</SMART_info>" >> “$OUTPUT_FILE"

That’s it. You can save your script as a .sh file.  Completed, it’ll look similar to what I have below:


# Create the Output file with the appropriate XML structure so the inventory scanner can read the data

# The output file path

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

# specifying the XML structure and telling to return the string to the output file

echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" > "$OUTPUT_FILE"

# creating the XML tag for the SMART info. Change this to match your custom inventory

echo "<SMART_info>" >> "$OUTPUT_FILE"

# command to list all of the drives, capture the status and insert the status as XML output in the output file

diskutil list | egrep "^/" | while read drive

     DRIVE=`basename $drive`
     STATUS=`diskutil info $drive | grep SMART | awk '{ $1=$2="" ;print $0 }'`
           echo "<$DRIVE> $STATUS </$DRIVE>" >> "$OUTPUT_FILE"


# close the XML tag for SMART Info

echo "</SMART_info>" >> "$OUTPUT_FILE" 

Screen Shot 2015-06-29 at 9.45.55 AM

To test it out, modify the properties of the file to make it executable.  To do this, you’ll need to open up Terminal and browse to the path of where the script has been saved.  You’ll then need to run the command:

chmod a+x scriptfilename.sh

Once the executable properties have been given to the file, you can manually run it on your test machine by calling it in terminal.

sudo ./scriptfilename.sh

In my next post I’ll walk through how we can use the pkgbuild command to create a package out of our script, deploying it the Apple-esque way as opposed to the UNIX method of deploying a .sh file.