Archive

Author Archive

Using AWS Lambda To Relay Jamf Pro Webhooks to Slack

January 8, 2019 Leave a comment

I recently got interested in utilizing webhooks in Jamf Pro but had no idea where to start. I went and watched Bryson Tyrrell’s (https://twitter.com/bryson3gps) presentation from JNUC 2017 Webhooks Part Deaux! and then went over to take a peek at Jackalope on the Jamf Marketplace. I read the docs, I tried to figure out how to do this in AWS ElasticBeanstalk, but I just couldn’t get it going. Just too much going on to devote enough time to it. So, I went over to Zapier and signed up for their free account so I could get this going. I got it working, but I quickly got throttled because I decided to enable the “ComputerCheckIn” webhook to make sure it worked. I think we flooded the 100 connection limit within 30 seconds and wound up having thousands of items in Zapier.

Well, that wasn’t going to work, so I changed it to “ComputerAdded” and waited for my month of Zapier to renew so I’d get 100 new “zaps”. That worked, until we went over the 100 limit again and had to wait. There had to be a better way that wasn’t going to cost me a ton of money. So I went Googling and came across an article on how to use AWS Lambda to do what I wanted to do: AWS Lambda For Forwarding Webhook To Slack.

I walked through the steps outlined on the page to setup the function in Lambda and everything worked great until I got to the part where I was making requests out to Slack. Lambda had a problem with the request method. Specifically this line of code:

 var post_req = https.request(post_options, function(res) {

So another round of Googling and I came up with the Node.js docs page on HTTPS and I figured out how to properly make the call:

const req = https.request(post_options,
(res) => res.on("data", () => callback(null, "OK")))
req.on("error", (error) => callback(JSON.stringify(error)));
req.write(post_data);
req.end();
view raw gistfile1.txt hosted with ❤ by GitHub

Once I was able to get past the https connection issues, I was able to utilize the rest of Patrick’s example to get my webhook from Jamf feeding into a Slack channel. We uploaded a custom emoji to our Slack channel and used the Slack documentation on basic message formatting and on attachments to get the notification to look how we wanted.

Ultimately we created two Lambda functions, one for ComputerAdded and another for ComputerInventoryComplete, each feeding into their own channel in our Slack. This was fairly easy to accomplish, the next step is to find a way to feed DataDog, or some other service, the ComputerCheckIn webhook so I can get a count of how many check-ins we have each day.

The code we used is below, but I wanted to point out one or two things. Where I got hung up the most was how to pull things like Computer Name or Serial Number from the JSON we were getting from the Jamf Pro server. Since the JSON contains two arrays, “webhook” and “event”, it took me a little bit to understand how to grab that data. To be honest, my skills here are lacking considerably so it took me longer than it should. Ultimately I figured out that you just have to dot walk to get the data you want. So to get the Computer Name it’s:

body.event.deviceName

“body” is the JSON object that we parse the webhook into. Once I figured that out I was all set to grab whatever data from the event, or webhook, array that I needed. Hopefully my head banging will help others not stumble quite so much.

Here’s the Node.js code we used as the template:

var https = require('https');
exports.handler = (event, context, callback) => {
console.log("MYLOG" + JSON.stringify(event))
var body = JSON.parse(event.body);
var name = body.event.deviceName;
var sernum = body.event.serialNumber;
var user_name = body.event.username;
var building = body.event.building;
var curr_time = Math.floor(new Date() / 1000);
var post_data = JSON.stringify({
"username": "Jamf Pro Server",
"icon_emoji": ":balloon:",
"channel": "#<yourslackchannelname>",
"text": "Computer Enrolled",
"attachments": [
{
"color": "good",
"fields": [
{
"title": "Computer: " + name,
"value": "Serial number: " + sernum
+ "\nUser name: " + user_name
+ "\nBuilding: " + building,
"short": false
}
],
"footer": "Jamf Webhook",
"footer_icon": ":balloon:",
"ts": curr_time
}
]
});
var post_options = {
host: 'hooks.slack.com',
port: '443',
path: '/services/YOURWEBHOOK',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(post_data)
}
};
const req = https.request(post_options,
(res) => res.on("data", () => callback(null, "OK")))
req.on("error", (error) => callback(JSON.stringify(error)));
req.write(post_data);
req.end();
var details = {
"status": "OK"
}
var response = {
'statusCode': 200,
'headers': { 'Content-Type': 'application/json' },
'body': JSON.stringify(details)
}
console.log("LOG:: " + JSON.stringify(response))
callback(null, response);
};
view raw gistfile1.txt hosted with ❤ by GitHub

 

 

Categories: Jamf Pro, Tech Tags: , ,

JNUC 2018 – Slides And Scripts

October 24, 2018 Leave a comment

The slides, scripts, images, and video from my presentation “10 Things Not To Do in a Large Scale Deployment” are available on GitHub now:

JNUC 2018

Categories: Casper, Jamf Pro Tags: ,

Using lpoptions To Identify Printer Options

October 20, 2018 1 comment

In my previous post on adding printers via script I mentioned using lpoptions to identify the different option settings for a printer. Let’s open up Terminal and get started with identifying the options. You’ll need to have the printer already installed on the system, so if it isn’t installed go follow my previous post and get it installed.

First, let’s find the name of the printer. For that we will use lpstat -a:

Screen Shot 2018-10-20 at 3.46.45 PM

Now that we know the name, Wayne_HOLD, let’s figure out what the options are that we can set. For that we’ll use lpoptions -p Wayne_HOLD -l. Now this list is way too much information to post here, so I’ll just cut it off at a few lines:

Screen Shot 2018-10-20 at 3.48.56 PM

Wow, and there’s plenty more information beyond this. This part of setting the options can be a bit trial and error. We probably aren’t going to want to set everything,  but we will want to add any options like the “Fiery Graphic Arts Package” that is installed, or the “Output option”, or perhaps the “Xerox high capacity feeder”.

One way we can figure out what option we need to set is by using grep along with the lpoptions command. For example, to know which option sets the “Fiery Graphic Arts Package” we might try this:

lpoptions -p Wayne_HOLD -l | grep -i "graphic arts"

This gives us the following:

Screen Shot 2018-10-20 at 3.53.52 PM

That’s great, but what does “GA2” and “GA1” mean? Open up the Options & Supplies window for the printer by going to System Preferences -> Printers & Scanners -> click on the printer and then click on the Options & Supplies button. For this printer, we can see that “GA1” is the “Fiery Graphics Arts Package”.

Screen Shot 2018-10-20 at 3.55.54 PM

What happens when we change that in the GUI to:

Screen Shot 2018-10-20 at 3.57.26 PM

This is what we see from Terminal:

Screen Shot 2018-10-20 at 3.57.41 PM

So now we know that the “GA2” option is the “Fiery Graphics Arts Package, Premium Edition”.

For other settings we need to do some investigation in the Print pane when printing a document. For most printers we’ll want to see what the default view is in the Print dialog window and then make the change in the Terminal using lpoptions and finally go back to the Print dialog window to see what that change did.

In our case we want to set the Output option and the High Capacity Feeder option from their default settings:

Screen Shot 2018-10-20 at 4.09.10 PM

To use a high capacity paper source, and to put the output in a different tray:

Screen Shot 2018-10-20 at 4.09.41 PM

Notice that in the second screen shot we now have Tray 6 available to us. We can acheive this using the lpadmin command to set the settings (note: the lpoptions command works most of the time, but I have far more success using lpadmin).

Screen Shot 2018-10-20 at 4.12.46 PM

By figuring out which settings we want to use, we can now configure all of the options for a printer from a script. One more example would be setting a color printer to default to B&W and duplex print. This is often done as a cost savings measure.

lpa='/usr/sbin/lpadmin'
${lpa} -p CopyThat -E -o printer-is-shared=false -v lpd://10.89.170.5 \
-P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 7855.gz"
${lpa} -p CopyThat -o Duplex=DuplexNoTumble -o XROutputColor=PrintAsGrayscale
view raw gistfile1.txt hosted with ❤ by GitHub

I hope this has inspired you to dive further into setting up printers via script.

Categories: Casper, Jamf Pro, Tech Tags: , ,

Identify EFI Fiery Driver

October 20, 2018 1 comment

In this post I talked about how we can use the lpadmin command to add a printer via script. In this post we will cover how we can identify the driver for an EFI Fiery RIP. Note: all Fiery RIPs do not use the same driver, so you will want to follow this process for any Fiery you may have in your environment.

Open your favorite web browser and enter the IP address (or DNS name) for the printer. This should take you to the Fiery RIP webpage.

Screen Shot 2018-10-20 at 3.22.16 PM

Now, click on the Configure tab, then on the “Check for product updates” link.

Screen Shot 2018-10-20 at 3.22.30 PM

This will open up a new tab in your browser that will take you to the EFI Fiery live update page. From here you’ll want to click on the Printer Drivers tab and then scroll to locate the latest printer driver.

Screen Shot 2018-10-20 at 3.23.07 PM

We’ve scrolled down to locate the latest driver that handles macOS 10.14 Mojave.

Screen Shot 2018-10-20 at 3.23.16 PM

We can then click the Download link to download the latest version of the Fiery driver for our specific model of Fiery.

One Last Thing

Now that you have your driver downloaded, I would strongly suggest heading over to Foigus’ post, Trial By Fiery, to find out how to use AutoPKG to create a driver package that will install via a management tool, and not have update dialogs popping up.

Categories: Casper, Jamf Pro, Tech Tags: , , ,

Deploying Printers via Script

October 20, 2018 2 comments

Deploying printers on the Mac in an enterprise environment, or heck, just in a small office environment, can be done in multiple ways. If you don’t have a management tool, or ARD, you’re going to be running around doing it by hand. If you have a management tool, like Munki or Jamf, then you can deploy printers in a more automated fashion. My preferred method is to use a Bash script to deploy printers because it provides me a little more flexibility

Identify The Driver

The first thing to do is to identify the printer and the driver that is required. For most printers this is pretty simple, just navigate to the IP address of the printer to verify the make and model, then head over to the vendor’s website to download the latest driver. Once you have the driver, install it on your machine and then go find the driver file on your system. On the Mac, most printer drivers are stored in:

/Library/Printers/PPDs/Contents/Resources

If your printer has a “RIP”, or “Raster Image Processor“, identifying those drivers can be a little trickier. Head over to this post on how to identify the driver, and download it, on an EFI Fiery RIP. Drivers for an EFI Fiery or other RIP are usually stored in:

/Library/Printers/PPDs/Contents/Resources/<localization folder>

For us here in North America, that folder path would be:

/Library/Printers/PPDs/Contents/Resources/en.lproj

Once you have identified the driver, copy the full path of the driver file (Option-Command-C, or hold down Command then Edit->Copy as Pathname, or right click while holding Command) to your clipboard.

Build The Command

The next thing we need to do is figure out the command to run to add the printer. Fire up Terminal and let’s figure out the commands to use. We will utilize the lpadmin command to get the printer on the system. For this post this is what our command will look like:

sudo lpadmin -p <name> -E -o printer-is-shared=false -v ipp://1.1.1.1 -D "<name>" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"

That looks a little daunting, so let’s break it down a little bit.

-p <name>  This flag sets the name of the printer as seen by the cups process. Use a name with no spaces, or substitute underscores for the space.

-E  This flag enables the printer to accept jobs

-o printer-is-shared=false  The -o flag allows us to pass options to the printer. In this case we are making sure the printer is not shared on the network.

-v ipp://1.1.1.1  The -v flag sets the URI of the printer.

-D <name>  Where -p set the name the cups process saw, the -D flag sets what I call the “friendly” name, the name that is visible in the GUI.

-P <driver path>  Pretty self explanatory, the -P flag sets the path to the driver.

Now that we have everything, run the command in Terminal to add the printer. You can verify the printer added by using the lpstat -a command. With the printer added, open up a program and send a test print to the printer. It is important for us to do this step so that we know our handy work is working properly.

Put It In A Script

Let’s get to the script to add the printer. Open your favorite code editor (TextMate or Sublime Text for me) and start a new script. I utilize Bash, but you could just as easily do this in Python if you prefer. First we want to make sure the proper driver is on the system, and if it isn’t we want to install it.

if [[ ! -f "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz" ]]; then
/usr/local/bin/jamf policy -trigger xeroxGenericDriver
fi
view raw gistfile1.txt hosted with ❤ by GitHub

If the driver file is not on the system, we call the jamf binary to trigger our install policy. Adjust this to fit your management toolset.

Now with the driver check complete, we use a case statement to choose the printer (or printers) to install. We pass the choice to the script using Script Parameters in our Jamf Pro server policy. Here’s an example showing how we can install one printer, or multiple printers in an office.

lpa='/usr/sbin/lpadmin'
case $printer in
Printer1)
${lpa} -p Printer1 -E -o printer-is-shared=false -v ipp://10.1.1.1/ipp/print \
-D "Printer1" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"
;;
Printer2)
${lpa} -p Printer2 -E -o printer-is-shared=false -v ipp://10.1.1.2/ipp/print \
-D "Printer2" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"
;;
OfficePrinters)
${lpa} -p Printer1 -E -o printer-is-shared=false -v ipp://10.1.1.1/ipp/print \
-D "Printer1" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"
${lpa} -p Printer2 -E -o printer-is-shared=false -v ipp://10.1.1.2/ipp/print \
-D "Printer2" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"
;;
esac
view raw gistfile1.txt hosted with ❤ by GitHub

You can hopefully see the flexibility this provides us for using one script to install multiple printers. Sure, we still have multiple policies in the JPS, but rather than have multiple printers or multiple scripts as well, we can do this with just the one. And, when a printer needs to change, we just edit the script.

Bonus Round

What about adding printer options, like paper trays or output trays or setting a printer to B&W instead of color? We can use lpoptions to figure out what those options are and to set them. Since that can be a daunting task, head over to this post about using lpoptions to identify the settings.

Hopefully this post has helped you evaluate the use of a script to add printers and has given you a new tool for your toolbox.

Categories: Jamf Pro, Tech Tags: , , ,

Custom Theme Fonts Office 2016 Mac

March 10, 2018 Leave a comment

I was given the task of deploying a custom theme for Office 2016 the other day. Not having done this before, I did what I always do: I searched Google for the answer. I was able to figure out where to place the theme file, so I went about my merry way packaging up the theme (along with a script to move it into place) and the fonts that were part of the them, and then I deployed it.

Today I started getting reports that the fonts were not working in the theme. It turns out that when you use custom fonts in a theme, you have to deploy a special XML file that describes those fonts. I headed back over to Google only to find out that Office 2016 on the Mac does not support the creation of themes with fonts in them. Well, it doesn’t give you an easy way to customize the fonts

Fortunately, with a little searching, I found a web site that explained how to hack together a Theme Font file: XML Hacking Font Themes. So I started off and created a new XML file to get the fonts into the theme.

The easiest thing to do, as is explained in that article, is to grab an XML file out of the app bundle:

Just grab one of those XML files and copy it to your desktop. Once you’ve done that, edit the file, replacing the font inside the <a:latin> bracket with the name of the font you need. Do this for the Major and Minor font sections, then save the file. Name the new file the name of the font you are using to make it simple to find. My completed XML file looks like this:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<a:fontScheme name="Arial" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"&gt;
<a:majorFont>
<a:latin typeface="Montserrat"/>
<a:ea typeface="" />
<a:cs typeface="" />
</a:majorFont>
<a:minorFont>
<a:latin typeface="Montserrat"/>
<a:ea typeface="" />
<a:cs typeface="" />
</a:minorFont>
</a:fontScheme>
view raw gistfile1.txt hosted with ❤ by GitHub

Now that you have your XML file and your theme, you need to place them in the correct folder. Office 2016 keeps themes inside the user’s home folder in the following path:

/Users/<user>/Library/Group Containers/UBF8T346G9.Office/User Content/

We’re going to throw the theme into the Themes folder in that path, and the font XML file will go into the Theme Fonts folder in that path:

Voila! You now have a working theme in Office 2016 that can be used in PowerPoint, Word, or Excel.

Categories: Uncategorized

Pre-Loading AWS S3 Buckets With Jamf Pro

November 4, 2017 Leave a comment

It’s been far too long and way too much has been going on. I took a new position back in February with our corporate office with the lofty goal of migrating 15,000 endpoints into a centralized Jamf Pro instance. We worked with Jamf and with our internal resources to determine where to place the infrastructure and finally settled on AWS. I’ll go into more detail in other posts because for now, I wanted to talk about sideloading packages in S3.

Most of us have dev or test environments. Ours is in AWS alongside our production environment. With the release of Jamf Pro 10 this past week, it meant it was time to upgrade our dev instances to 10. Not only do we host the servers in AWS, but we also host the distribution point for our dev instance on S3. As you may be aware, each time you create a new cloud distribution point with Jamf Pro a new S3 bucket is spun up. That’s fine, but what if you have a bunch of test packages already in a dev S3 bucket that you want to use? That’s where I found myself this evening, and I figured out how to move those into the new S3 bucket that Jamf Pro created.

After spinning up your new Jamf Pro servers and creating your S3 bucket in Jamf Pro, head over to the AWS Console to manage your S3 buckets. Locate your original bucket and select all of the files in the bucket.

Next go under the More button and choose Copy.

Now go find the new bucket that was created by Jamf Pro and go under the More button in the bucket and choose Paste.

After getting the files over, we now have to tell Jamf Pro that the packages are there. Using the AWS CLI from your computer, grab a listing of your bucket:

aws s3 ls s3://<yourbucketaddress> >> ~/mypackages.txt
view raw AWS CLI LS hosted with ❤ by GitHub

Now that you’ve got a list of all packages in that S3 bucket, you’ll want to get rid of all of the extra data so that you only have the package filename. I used Excel to delete columns and combine columns if there were spaces (use the Open menu in Excel and choose “Delimited” and use Spaces as your delimiter). Once you have a clean list, save that out of Excel and then open it in your favorite text editor. TextMate is my go to for this. You’ll want to save the text out as a CSV with LF only.

We now have a clean list that we can send through a handy API script to stub out the packages in Jamf Pro. We will use the following script to import this data into the Jamf Pro server. Be sure to edit the JSS address in the script before running.

#!/bin/sh
# Name: addPackages.sh
# Date: 27 Mar 2018
# Author: Steve Wood (steve.wood@omnicomgroup.com)
# Purpose: used to stub out packages in the Jamf Pro database
# The CSV file needs to be saved as a UNIX file with LF, not CR
# Version: 1.1
# Update: 27 Mar 2018 - fixing an error in the counter
# A good portion of this script is re-purposed from the script posted in the following JAMF Nation article:
#
# https://jamfnation.jamfsoftware.com/discussion.html?id=13118#respond
#
args=("$@")
#jssAPIUsername="${args[0]}"
#jssAPIPassword="${args[1]}"
jssAPIUsername="apiuser"
jssAPIPassword="apipass"
jssAddress="https://yourserveraddress.com"
file="${args[0]}"
#Verify we can read the file
data=`cat $file`
if [[ "$data" == "" ]]; then
echo "Unable to read the file path specified"
echo "Ensure there are no spaces and that the path is correct"
exit 1
fi
totalqty=`awk -F, 'END {printf "%s\n", NR}' $file`
#Set a counter for the loop
counter="0"
duplicates=[]
echo $data
#Loop through the CSV and submit data to the API
while [ $counter -lt $totalqty ]
do
counter=$[$counter+1]
line=`echo "$data" | head -n $counter | tail -n 1`
package=`echo "$line" | awk -F , '{print $1}'`
apiData="<package><id>0</id><filename>${package}</filename><name>${package}</name></package>"
output=`curl -sS -k -i -u ${jssAPIUsername}:${jssAPIPassword} -X POST -H "Content-Type: text/xml" -d "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>$apiData" ${jssAddress}/JSSResource/packages`
echo $output
error=""
error=`echo $output | grep "Conflict"`
if [[ $error != "" ]]; then
duplicates+=($serialnumber)
fi
done
echo "The following packages could not be created:"
printf -- '%s\n' "${duplicates[@]}"
exit 0
view raw addPackages.sh hosted with ❤ by GitHub

Let’s open up terminal and run our script, feeding it our cleaned up text file.

/path/to/script/addPackages.sh <jssuser> <jsspass> /path/to/textfile.csv

That’s it. As that script runs it will add the packages to the Jamf Pro server so you don’t have to. This will help speed up the process of creating new dev instances each time.

In some of my next posts I’ll discuss how we are using the Server app on machines as distribution points, and utilizing simple scripts with LaunchDaemons to keep them in sync.

Categories: Casper, Jamf Pro, Tech

Collecting Data Using Plist Files

July 16, 2016 Leave a comment

At our recent Dallas area Casper User Group meeting, we got into a discussion around collecting data during a Casper recon. Specifically we were discussing the use of Extension Attributes to collect information about virtual machines.

Extension Attributes are a way to capture information from your systems. You can use scripts to pull information or drop downs or text boxes to store static information in the database. In the instance of collecting info about virtual machines, a script would be run on the systems during the recon to gather the information. Running a script on the system each time a recon happens can be processor heavy, depending on the data that is being gathered. For example, gathering home folder size by running “du” each time a recon happens can be taxing.

Rather than run the script each recon, you can use a policy to run the script once a week, once a month, or just one time, to gather the information you need and place it in a plist file somewhere. During the standard recon period, you can then use an Extension Attribute to read the information in that plist file. This is much less taxing on the systems than running the script during a recon.

Stash The Data

For our example, rather than run through grabbing info about virtual machines, let’s work on grabbing the home folder size for the logged on user. We will store the info in a plist file that we will stash in /Library/IT_Data.

First we need to find the logged in user name. There are plenty of ways to do this, but we’ll use the “Apple approved” method, using a Python one liner. Okay, it’s not really a one liner, it’s just built like one.

loggedInUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "\n");'`
view rawcontents.sh
view raw gistfile1.txt hosted with ❤ by GitHub

Now that we have our logged in user, we just need to find the user’s home folder and use du to grab the data. We’ll use dscl to grab the home folder location and then du to get the home folder size.

homeDir=`dscl . read /Users/$loggedInUser NFSHomeDirectory | awk '{ print $2 }'`
homeSize=`du -hs ${homeDir}`
view raw gistfile1.txt hosted with ❤ by GitHub

The next thing we need to do is to store this information into our plist file. Using the defaults command, we can write as much data as we want into the plist file, We can use different keys to store whatever data you want, and then recall it during recon by asking for those specific keys.

defaults write /Library/IT_Data/com.mycompany.homesize.plist HomeFolderSize -string "${homeSize}"
view raw gistfile1.txt hosted with ❤ by GitHub

Retrieve The Data

Now that we have the data stashed away, we just need to grab it during the recon process. To do this, we’ll use the defaults command again, to grab the data. We’ll use some variables for the folder path and the plist name, that way we can re-use this code fairly easily. We also want to make sure the file actually exists before trying to read data from it, hence the If statement.

if [[ -e ${dataFile} ]]; then
homeSize=`defaults read ${dataFile} HomeFolderSize`
fi
view raw gistfile1.txt hosted with ❤ by GitHub

Once we’ve read the data, all that’s left is to echo it out into the EA.

if [[ ${homeSize} ]]
echo "<result>${homeSize}</result>"
else
echo "<result>No Results Found</result>"
fi
view raw gistfile1.txt hosted with ❤ by GitHub

That’s All Folks

That’s pretty much all there is. Now that we know how save data to a plist and then read it back, this method could be used for any data we only need to gather once, or gather at infrequent times.

The full script to write the data and to then read the data are below.

#!/bin/bash
loggedInUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "\n");'`
homeDir=`dscl . read /Users/$loggedInUser NFSHomeDirectory | awk '{ print $2 }'`
homeSize=`du -hs ${homeDir}`
# check for our storage folder, and create if missing
if [[ ! -d "/Library/IT_Data" ]]; then
mkdir "/Library/IT_Data"
fi
defaults write /Library/IT_Data/com.mycompany.homesize.plist HomeFolderSize -string "${homeSize}"
#!/bin/bash
dataFolder="/Library/IT_Data"
dataFile="${dataFolder}/com.mycompany.homesize.plist"
if [[ -e ${dataFile} ]]; then
homeSize=`defaults read ${dataFile} HomeFolderSize`
fi
if [[ ${homeSize} ]]; then
echo "<result>${homeSize}</result>"
else
echo "<result>No Results Found</result>"
fi
Categories: Casper, Tech Tags: , , , ,

To Image or To First Boot

July 14, 2016 Leave a comment

When I first started out with the Casper Suite back in 2008, it was commonplace to create imaging configurations with their Casper Imaging tool. Drop an OS image into Casper, add in some applications in the order you want them installed, maybe a preference or two, and voila, you now have an imaging configuration for use in Casper Imaging. The next steps after that were to boot the machine you want imaged from an external source (NetBoot, USB drive, DVD, etc), run Casper Imaging, choose the configuration you want to run, and after some amount of time you’d have a machine ready to deploy.

Fast forward a few years, and more and more admins are no longer using an imaging methodology like this. Instead we’ve switched to leaving the factory operating system in place and simply adding the necessary applications and settings onto the systems. You can still utilize an imaging configuration deployed with Casper Imaging for this method, and that’s exactly how I first started with this method, but then I switched methods again, and started deploying apps and preferences with a post image, or First Boot, script.

This method, I felt, allowed me more opportunities to update the imaging process without having to touch the configuration. All I had to do was create a simple Bash script (or Python or whatever language you prefer) that would get called after Casper was done. After Casper had done it’s thing and restarted, my script would run and apply any config type items (set NTP server, time zone, etc) and then use the jamf binary to install software by calling policies.

Let’s take a look at the actual script and the LaunchDaemon I use to call it. The script in its entirety can be found on my GitHub repo here.

Script City

So the first bit of the script is just for setting up some variables and to setup logging. The script will output everything into this log file so that you can go back and troubleshoot later. If you are passing any sensitive data in the script, you may want to ship the log to a secure server and then delete it.

## Set global variables
LOGPATH='/path/to/your/logs'
JSSURL='https://<yourjssurl>&#39;
JSSCONTACTTIMEOUT=120 # amount of time to wait before timing out waiting for connection
LOGFILE=$LOGPATH/deployment-$(date +%Y%m%d-%H%M).logging #re-name to whatever name you want
VERSION=10.11.5
## Setup logging
if [[ ! -d $LOGPATH ]]; then
mkdir $LOGPATH
fi
set -xv; exec 1> $LOGFILE 2>&1
view raw gistfile1.txt hosted with ❤ by GitHub

After we’ve taken care of some of that housekeeping, we lock the screen so the end user or tech knows that we are working on the system. You can use the default lock icon that Apple uses, or you can upload your own icon and declare that in the swuIcon variable. This bit of code is not my own, but was borrowed from Mike Morales out of this JAMFNation post. Thanks Mike!

## Block the user from being able to see our trickery
## Define the name and path to the LaunchAgent plist
PLIST="/Library/LaunchAgents/com.LockLoginScreen.plist"
## set the icon
swuIcon="<set to your ICON for use on lock screen if you want>"
## Define the text for the xml plist file
LAgentCore="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
<dict>
<key>Label</key>
<string>com.LockLoginScreen</string>
<key>RunAtLoad</key>
<true/>
<key>LimitLoadToSessionType</key>
<string>LoginWindow</string>
<key>ProgramArguments</key>
<array>
<string>/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/MacOS/LockScreen</string>
<string>-session</string>
<string>256</string>
</array>
</dict>
</plist>"
## Create the LaunchAgent file
echo "Creating the LockLoginScreen LaunchAgent..."
echo "$LAgentCore" > "$PLIST"
## Set the owner, group and permissions on the LaunchAgent plist
echo "Setting proper ownership and permissions on the LaunchAgent..."
chown root:wheel "$PLIST"
chmod 644 "$PLIST"
## Use SIPS to copy and convert the SWU icon to use as the LockScreen icon
## First, back up the original Lock.jpg image
echo "Backing up Lock.jpg image..."
mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg \
/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak
## Now, copy and convert the SWU icns file into a new Lock.jpg file
## Note: We are converting it to a png to preserve transparency, but saving it with the .jpg extension so LockScreen.app will recognize it.
## Also resize the image to 400 x 400 pixels so its not so honkin' huge!
##echo "Creating SoftwareUpdate icon as png and converting to Lock.jpg..."
##sips -s format png "$swuIcon" --out /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg \
##--resampleWidth 400 --resampleHeight 400
cp $swuIcon /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg
## Now, kill/restart the loginwindow process to load the LaunchAgent
echo "Ready to lock screen. Restarting loginwindow process..."
kill -9 $(ps axc | awk '/loginwindow/{print $1}')
view raw gistfile1.txt hosted with ❤ by GitHub

Next we put a dummy receipt down in the JAMF Receipts folder (/Library/Application Support/JAMF/Receipts). This is so we can scope via Smart Groups to machines imaged on a certain day, if we need or want. The modelName variable uses system_profiler to grab the machine model.

TODAY=`date +"%Y-%m-%d"`
if [[ ! -d "/Library/Application Support/JAMF/Receipts" ]]; then
mkdir /Library/Application\ Support/JAMF/Receipts
fi
touch /Library/Application\ Support/JAMF/Receipts/$modelName_Imaged_$TODAY.pkg
view raw gistfile1.txt hosted with ❤ by GitHub

After this, we get into setting system preferences like time servers and such. Rather than post all of the code, I’m going to point out a few key blocks. Like this one for enabling Location Services:

### Enable Location Services to set time based on location
/bin/launchctl unload /System/Library/LaunchDaemons/com.apple.locationd.plist
uuid=`ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}'`
/usr/bin/defaults write /var/db/locationd/Library/Preferences/ByHost/com.apple.locationd.$uuid \
LocationServicesEnabled -int 1
/usr/sbin/chown -R _locationd:_locationd /var/db/locationd
/bin/launchctl load /System/Library/LaunchDaemons/com.apple.locationd.plist
# set time zone automatically using current location
/usr/bin/defaults write /Library/Preferences/com.apple.timezone.auto Active -bool true
####
view raw gistfile1.txt hosted with ❤ by GitHub

UPDATE: With Apple’s continuing security stance and the introduction of SIP, it is no longer possible to set the Location Services settings via script.

Or how about how to set the system preferences authorization to allow users access to the Network pref panel, etc:

##########################################
# /etc/authorization changes
##########################################
security authorizationdb write system.preferences allow
security authorizationdb write system.preferences.datetime allow
security authorizationdb write system.preferences.printing allow
security authorizationdb write system.preferences.energysaver allow
security authorizationdb write system.preferences.network allow
security authorizationdb write system.services.systemconfiguration.network allow
view raw gistfile1.txt hosted with ❤ by GitHub

And finally, the script checks for the location of the jamf binary file (to combat post-10.11 woes) and uses the binary to call policies. Thanks to Rich Trouton (derflounder.wordpress.com)for the code that checks for the binary location. Just copy and paste (changing policy ID and description) the policy install piece to add as many policies as you need.

# check for jamf binary
/bin/echo "Checking for JAMF binary"
/bin/date
if [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ ! -e "/usr/local/bin/jamf" ]]; then
jamf_binary="/usr/sbin/jamf"
elif [[ "$jamf_binary" == "" ]] && [[ ! -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then
jamf_binary="/usr/local/bin/jamf"
elif [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then
jamf_binary="/usr/local/bin/jamf"
fi
## JSS Policies for installing software
## Duplicate each block for the number of policies you need to run at post imaging time
/bin/echo "Installing <change to name of software>"
/bin/date
${jamf_binary} policy -id 1 # MAKE SURE TO SET TO THE CORRECT POLICY ID #
view raw gistfile1.txt hosted with ❤ by GitHub

After installing everything, run software update to install updates, remove the LaunchDaemon that controls the lock screen, and then restart the computer.

LaunchDaemon And Delivery

Now that we have our script built, we need to get it onto the system and have the system call the script. Let’s start with the LaunchDaemon. It’s a simple process to create a the file, just open up your favorite text editor (I like TextMate for writing Bash and Sublime Text for writing Python), drop in your XML and save out as a .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"&gt;
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.yourcompany.postimage</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/your/scripts/postimage_10.11.5.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
view raw gistfile1.txt hosted with ❤ by GitHub

With our script written and our LaunchDaemon ready, we just need to bundle it all up and get it into Casper. I use Packages for this part, but you can use your favorite packaging application.

I utilize a folder that I create inside of /private/var to hide all of my admin stuff, like scripts and binaries. So for this script, I would place it in this folder path I create, place the LaunchDaemon inside of /Library/LaunchDaemons, and then package it up.

Once packaged, drop your package into Casper Admin, set the priority to something low, like 5, and make sure to select “Install on boot drive after imaging”

With all of that work done, just add that to a configuration for Casper Imaging and image away. Casper Imaging will reboot the computer, at which time Casper’s first boot script will run and install any packages that were set to “Install on boot drive after imaging” and then restart the computer.

With our package installed during that process, on reboot of the computer our LaunchDaemon will take over and call our script. From there it’s just a matter of watching the paint dry until our computer is ready for us.

Final Thoughts

I’ve glossed over some things to try and shorten an overly long post. There are plenty of ways to image computers, and while this process works for me right now, it may not be your cup of tea. I’m currently looking at whether I should get rid of the system preference pieces and move that to Configuration Profiles, or if there’s some other trick to try. Either way, never stop tinkering with what you do, it’s what makes the job enjoyable and is the quickest way to learn something new.

If you’d like more information, or have a question, hit me up on the inter webs.

Categories: Uncategorized

Scripting Remote Desktop Bookmarks

February 29, 2016 Leave a comment

A few years ago I was searching for a way to easily create bookmarks in Microsoft Remote Desktop 8 on the Mac. Prior to version 8 you could drop an .RDP file on a machine and that was really all you needed to do to give your users the ability to connect to servers. Granted, you can still use this method, it’s just a bit sloppier, in my opinion.

So I went searching for a way to script the bookmarks, and that led me to my good friend Ben Toms’ (@macmuleblog) blog. I found his post, “HOW TO: CREATE A MICROSOFT REMOTE DESKTOP 8 CONNECTION” and started experimenting. After some trial and error, I discovered that using PlistBuddy to create the bookmarks just wasn’t being consistent. So I looked into using the defaults command instead. I finally was able to settle on the following script:

#!/bin/sh
# date: 18 Jun 2014
# Name: RDC-Connection.sh
# Author: Steve Wood (swood@integer.com)
# updated: 29 Feb 2016 - included line to add remote program to start on connection for @gmarnin
# grab the logged in user's name
loggedInUser=`/bin/ls -l /dev/console | /usr/bin/awk '{ print $3 }'`
# global
RDCPLIST=/Users/$loggedInUser/Library/Containers/com.microsoft.rdc.mac/Data/Library/Preferences/com.microsoft.rdc.mac.plist
myUUID=`uuidgen`
LOGPATH='/private/var/inte/logs'
# set variables
connectionName="NAME YOUR CONNECTION"
hostAddress="SERVERIPADDRESS"
# if you need to put an AD domain name, put it in the userName variable, otherwise leave blank
userName='DOMAINNAME\'
userName+=$loggedInUser
resolution="1280 1024"
colorDepth="32"
fullScreen="FALSE"
scaleWindow="FALSE"
useAllMonitors="TRUE"
set -xv; exec 1> $LOGPATH/rdcPlist.txt 2>&1
defaults write $RDCPLIST bookmarkorder.ids -array-add "'{$myUUID}'"
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.label -string "$connectionName"
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.hostname -string $hostAddress
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.username -string $userName
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.resolution -string "@Size($resolution)"
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.depth -integer $colorDepth
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.fullscreen -bool $fullScreen
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.scaling -bool $scaleWindow
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.useallmonitors -bool $useAllMonitors
#comment out the following if you do not need to execute a program on start of connection
# You can adjust the string to be any app that is installed.
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.remoteProgram -string "C:\\\\Program Files\\\\\\\\Windows NT\\\\Accessories\\\\wordpad.exe"
chown -R "$loggedInUser:staff" /Users/$loggedInUser/Library/Containers/com.microsoft.rdc.mac
view raw gistfile1.txt hosted with ❤ by GitHub

You can find that code in my GitHub repo here.

RDC URI Attribute Support

I had posted that script up on JAMF Nation back in June 2014 when someone had asked about deploying connections. Recently user @gmarnin posted to that thread asking if anyone knew how to add an alternate shell key to the script. After no response, he reached out to me on the Twitter (I’m @stevewood_tx in case you care). So, I dusted off my script, fired up my Mac VM, and started experimenting.

The RDC GUI does not allow for a place to add these URI Attributes. I read through that web page and Marnin forwarded me this one as well. Marnin explained that he was able to get it to work when he exported the bookmark as an .RDP file and then used a text editor to add the necessary “alternate shell:s:” information. Armed with this knowledge, I went to the VM and started testing.

First I created a bookmark in a fresh installation of RDC. I had no bookmarks at all. After creating a bookmark I jumped into Terminal and did a read of the plist file and came up with this:

YosemiteVM:Preferences integer$ defaults read /Users/integer/Library/Containers/com.microsoft.rdc.mac/Data/Library/Preferences/com.microsoft.rdc.mac.plist
{
QmoteUUIDKey = "ff870b10-7e8e-47c2-98bd-f14f3f0cd1b0";
"bld_number" = 26665;
"bookmarklist.expansionStates" = {
GENEREAL = 1;
};
"bookmarkorder.ids" = (
"{2a3925d6-659e-456e-ab03-86919b30b54b}"
);
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.fullscreenMode" = "@Variant(\177\017FullscreenMode\001)";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.hostname" = "termserv.company.com";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.label" = Test;
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.username" = "";
"connectWindow.geometry" = <01d9d0cb 00010000 000001b4 000000a0 000003b9 0000033f 000001b4 000000fc 000003b9 0000033f 00000000 0000>;
"connectWindow.windowState" = <000000ff 00000000 fd000000 00000002 06000002 44000000 04000000 04000000 08000000 08fc0000 00010000 00020000 00010000 000e0074 006f006f 006c0042 00610072 01000000 00ffffff ff000000 00000000 00>;
lastdevinfoupd = 1456781093;
lastdevresourceupd = 1456781153;
"preferences.ignoredhosts" = (
"10.93.209.210:3389"
);
"preferences.resolutions" = (
"@Size(640 480)",
"@Size(800 600)",
"@Size(1024 768)",
"@Size(1280 720)",
"@Size(1280 1024)",
"@Size(1600 900)",
"@Size(1920 1080)",
"@Size(1920 1200)"
);
"show_whats_new_dialog" = 0;
"stored_version_number" = "8.0.26665";
tlmtryOn = 1;
}
view raw gistfile1.txt hosted with ❤ by GitHub

Now that we had a baseline, I exported the bookmark to the desktop of the VM, edited it to add the “alternate shell” bits, and then re-imported it into RDC as a new bookmark. I then tested to make sure it would work as advertised. After some trial and error, I was able to get the exact syntax for the “alternate shell” entry to work. Now I just needed to see what changes were made in the plist file. A quick read showed me the following:

YosemiteVM:Preferences integer$ defaults read /Users/integer/Library/Containers/com.microsoft.rdc.mac/Data/Library/Preferences/com.microsoft.rdc.mac.plist
{
QmoteUUIDKey = "ff870b10-7e8e-47c2-98bd-f14f3f0cd1b0";
"bld_number" = 26665;
"bookmarklist.expansionStates" = {
GENEREAL = 1;
};
"bookmarkorder.ids" = (
"{2a3925d6-659e-456e-ab03-86919b30b54b}"
);
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.fullscreenMode" = "@Variant(\177\017FullscreenMode\001)";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.hostname" = "termserv.company.com";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.label" = Test;
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.username" = "";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.remoteProgram" = "C:\\\\Program Files\\\\\\\\Windows NT\\\\Accessories\\\\wordpad.exe";
"connectWindow.geometry" = <01d9d0cb 00010000 000001b4 000000a0 000003b9 0000033f 000001b4 000000fc 000003b9 0000033f 00000000 0000>;
"connectWindow.windowState" = <000000ff 00000000 fd000000 00000002 06000002 44000000 04000000 04000000 08000000 08fc0000 00010000 00020000 00010000 000e0074 006f006f 006c0042 00610072 01000000 00ffffff ff000000 00000000 00>;
lastdevinfoupd = 1456781093;
lastdevresourceupd = 1456781153;
"preferences.ignoredhosts" = (
"10.93.209.210:3389"
);
"preferences.resolutions" = (
"@Size(640 480)",
"@Size(800 600)",
"@Size(1024 768)",
"@Size(1280 720)",
"@Size(1280 1024)",
"@Size(1600 900)",
"@Size(1920 1080)",
"@Size(1920 1200)"
);
"show_whats_new_dialog" = 0;
"stored_version_number" = "8.0.26665";
tlmtryOn = 1;
}
view raw gistfile1.txt hosted with ❤ by GitHub

The key is the line that has “remoteProgram” as part of the entry. You have to get the full path on the Windows machine to the application you want to run on connection to the server. Once you know that path, you can adjust your bookmark script however you need.

The script I posted above, and is linked in my GitHub repo, contains the line to add that Remote Program (alternate shell). If you do not need it, just comment it out of the script.