Tiny Deathstars of Foulness

In this article, we’ll cover how to use Zapier to connect data from your Jamf Account to a Google sheet. Once you build a WebHooks receiver in Zapier, you don’t have to use Google as the third party service that your WebHook triggers. You could use any other service that Zapier integrates as well, including Mailchimp, WordPress, Shopify, Todoist, ZenDesk, SurveyMonkey, Freshdesk, Quickbooks, Basecamp, and about 1,200 other solutions. In other words, you can link a WebHook from Jamf Pro into pretty much any automated service that you can think of!

So what’s a WebHook? A WebHook is an HTTP callback, or an HTTP POST that is fired when an event happens. The goal of WebHooks is that they are simple event notifications, typically sending a small amount of json to a destination web server that’s then ; a simple event-notification via HTTP POST. A web application listening for that event will then receive the WebHooks and perform a task in the background. Zapier is a great little tool that connects web apps. For modern software apps, most of this is done via acting as a WebHooks receiver and sending an API call to another tool. 

To follow along with this article, you will need a Zapier account, Jamf account and a Google account. Since we’ll be using Zapier to connect Jamf Pro and Google. 

Google Sheet Setup

  1. Signup/Login to your google account at
  2. Create new spreadsheet.
  3. Add any column names of fields you will want to store on your google sheet about your device—i.e. Date, Group, Name, Mac Address, IP Address, Make, Model, etc. We will use these columns in the Zap to map our data to the correct column.

Zapier Setup

  1. Signup/Login to your Zapier account at
  2. Click “Make a Zap!” button in top right-hand corner.
  3. You will start my setting up the webhook for catching the smart group change.

Catching Smart Group Membership Change Zap

  1. Name your Zap
  2. Add Note (optional) – Optionally it might be good to add some details about what you zap does. You can do this by clicking the link “Add Note” under the name.
  3. Example Note:Catches POST from Smart Group Change Zap and gets the Id of the computer added and/or removed from the group, to get all the computer details and save to Google Sheet.
  4. Setup your trigger step. Choose Webhooks under Built-In Apps.
  5. Select Catch Hook.
  6. Continue pass the “Pick Off a Child Key” as there is nothing specific we need to select off the hook.
  7. Zapier will provide an URL to send requests to. This URL will need to be copied and pasted in your webhook settings in Jamf.
  8. Open up a new window to begin setup of Jamf webhook, leave this zap open as you will return to it.

Jamf Setup of Webhook

  1. After copying the URL from the zap. Open up a new window and login to your Jamf account.
  2. Click the cog in the top right corner and go to your settings.
  3. Under Global Management click on the Webhooks icon.
  4. Click “New” button.
  5. Add a Display Name.
  6. Check “Enabled”.
  7. Paste the URL from the previous zap to the Webhook URL input.
  8. Set Authentication Type to “None”
  9. Fill in your preferred connection and read timeout
  10. Set Content Type to “JSON”
  11. Select the Webhook event from the dropdown – Set to “SmartGroupComputerMembershipChange”
  12. Set up the target smart computer group. You can select a group or apply to all groups.
  13. Save and return to your Zapier window with your previous zap.

Catching Smart Group Membership Change Zap (continued)

  1. After completing setup of Jamf webhook. Click “Ok, I did this” to begin a test and pull in a test sample. Then return to your Jamf window and trigger the webhook. The webhook can be triggered in two ways.
    1. Trigger the webhook by going to Computers – > Search Inventory -> (Select a computer) -> Edit -> Update site to a new site that is in a different computer group and Save
    2. Trigger the webhook by going to Computers -> Smart Computer Groups – > (Select a Group) -> Update site to a new site that is in a different computer group and Save.
  2. If the event is triggered correctly you will see a test result/message like below:

  1. Next setup an action step. Select Code under Built-In Apps.
  2. Select “Run Javascript”
  3. Use the following Input Data Parameters
    1. eventName : Select “Event Name” from Field Options
    2. addedComputers: Select “Event Group Added Device Ids” from Field Options
    3. removedComputers: Select “Event Group Removed Device Ids” from Field Options
  4. In the code section. Copy and paste the following code:

** The highlighted url will be replaced by another url in a later zap.

var added = [];
var removed = [];
if(inputData.addedComputers) {
added = inputData.addedComputers.split(',');
if(inputData.removedComputers) {
removed = inputData.removedComputers.split(',');
var otherZapURL = "";
for(var i = 0; i< added.length; i++) {
var addedBody = JSON.stringify({
computer: added[i],
status: "added",
await fetch(otherZapURL, {method:'POST', body:addedBody});
for(var i = 0; i< removed.length; i++) {
var removedBody = JSON.stringify({
computer: removed[i],
status: "removed",
await fetch(otherZapURL, {method:'POST', body:removedBody});
return [{status: 'ok'}];

  1. This zap is done.

Saving Computer Details to Google Sheet

  1. Start a new zap.
  2. Add Note (optional) – Optionally it might be good to add some details about what you zap does. You can do this by clicking the link “Add Note” under the name.
  3. Example Note:Catches POST from Smart Group Change Zap and gets the Id of the computer added and/or removed from the group, to get all the computer details and save to Google Sheet.
  4. Setup your trigger step. Choose Webhooks under Built-In Apps.
  5. Select Catch Hook.
  6. Continue pass the “Pick Off a Child Key” as there is nothing specific we need to select off the hook.
  7. Zapier will provide an URL to send requests to. This URL will need to be copied and pasted in your previous zap in the code section where the highlighted url was listed.
  8. Setup an action step. Select Webhooks under “Built-In Apps”
  9. Select “GET” action type.
  10. Fill in the following input:
    1. URL : Copy and Paste your with your role)
      1. Select “Computer” from field options
      2. See below as sample:

  1. Query String Params – leave blank
  2. Send As JSON – Select Yes
  3. JSON key – enter “json”
  4. Unflatten – Select Yes
  5. Basic Auth –separate your username and password with a pipe character
  6. Headers – leave blank
  1. Continue to through to Test Step and make sure data was received about the computer.
  2. If successful, continue to add a third action step by clicking the plus icon under you previous step and selection Action.
  3. Search for Google sheets in Search for Apps.
  4. Select Google Sheets and select the action type “Create Spreadsheet Row”.
  5. Connect your Google Account you used to create your Google sheet in the previous steps. See Google Sheets Setup section earlier in this document.
  6. Select Spreadsheet you set up under Spreadsheet input.
  7. Select the worksheet that has your columns names.
  8. After the worksheet is selected, each column will appear as an input option to map fields to. You will need to map each column to the appropriate field by selecting the field from the field options menu to the right of the input.
  9. After you’ve finished the mapping, select Continue to Test the step. If the test was successful, you should see your new record on your google sheet.
  10. Turn both of your zaps on and you are ready to automatically record all your smart group changes!

This was written for a specific use case, but because it’s useful for a number of apps and many of those can be hooked to other apps providing a nearly infinite number of use cases to link Jamf Pro Smart Group changes (one of the most valuable things that Jamf does) to other solutions!


August 17th, 2018

Posted In: JAMF, Mac OS X, Mac OS X Server

Tags: , , , , , ,

Leave a Comment

I’ve seen a few issues now where ApplePay and Health stopped working properly on a Mac and iOS device and when you fixed one, it seemed to wreck the connection with the other. Turns out that the information on a local system is managed with the new(ish) ckksctl command. Using ckksctl is pretty straight forward. First, let’s look at what’s on the Mac, using the ckksctl command with the status verb:

/usr/sbin/ckksctl status

There will be a section for ApplePay and another for Health. Here, if the services are configured, you should see the following in that section:

CloudKit account: logged in

Now, let’s force a pull of what’s in iCloud using the fetch verb:

/usr/sbin/ckksctl fetch

A successful sync will simply exit. However, that doesn’t mean that the keys are actually working. So if the issues persist, what we’re going to do is reset what’s in the local system and then pull the information from CloudKit again and show the status:

/usr/sbin/ckksctl reset; /usr/sbin/ckksctl status

Additionally, if you feel the local system is correct and the CloudKit data is incorrect then you could do the opposite and push a fresh config from the client to CloudKit:

/usr/sbin/ckksctl reset-cloudkit; /usr/sbin/ckksctl status

This has resolved issues I’ve seen. The status is also useful to track what a client has been configured to access. Please feel free to comment if you’ve had other experiences as I’ve found practically no information on this command.

August 10th, 2018

Posted In: cloud, Mac OS X, Mac Security

Tags: , ,

Leave a Comment

July 27th, 2018

Posted In: MacAdmins Podcast

Tags: , , ,

July 9th, 2018

Posted In: Uncategorized

June 29th, 2018

Posted In: MacAdmins Podcast

Tags: , , ,

Bryson mentioned Docstrings in the latest episode of the MacAdmins Podcast. But how do you use them? Documentation strings (or docstrings for short) are an easy way to document Python objects (classes, functions, methods, and modules in-line with your code. Docstrings are created using three double-quotes in the first statement of the definition and are meant to describe in a human readable way what the object does.

Let’s look at an example for hello_krypted:

def hello_krypted():
"""This simply echos Hello Krypted.

But there's so much potential to do more!

Docstrings can then be accessed using the __doc__ attribute on objects (e.g. via print):

>>> print hello_krypted.__doc__
This simply echos Hello Krypted.

But you can say hello in person!

For more on docstrings, check out the official docs at

June 25th, 2018

Posted In: Programming, Python

Tags: ,

May 9th, 2018

Posted In: Uncategorized

Migrating file services from a macOS Server to a macOS Client can be a bit traumatic at first. Mostly because the thought itself can be a bit daunting. But once you get started, it’s pretty simple. Mostly because there’s less to do. And that can be a challenge. While there are ways to hack together solutions for network homes and other more advanced features, if you’re doing that, then you’re missing a key point here. 

Let’s start by documenting our existing share points. We’ll do this with the serveradmin command and using the settings verb for the sharing service as follows:

sudo serveradmin settings sharing

Each share is an item in the sharePointList array, with the following:

sharing:sharePointList:_array_id:/Users/charles.edge/Public:nfsExportRecord = _empty_array sharing:sharePointList:_array_id:/Users/charles.edge/Public:smbName = “Charles Edge’s Public Folder” sharing:sharePointList:_array_id:/Users/charles.edge/Public:name = “Charles Edge’s Public Folder” sharing:sharePointList:_array_id:/Users/charles.edge/Public:afpIsGuestAccessEnabled = yes sharing:sharePointList:_array_id:/Users/charles.edge/Public:isIndexingEnabled = no sharing:sharePointList:_array_id:/Users/charles.edge/Public:dsAttrTypeNative\:sharepoint_group_id = “6C37A421-C506-4523-8769-1AF6EA245B68” sharing:sharePointList:_array_id:/Users/charles.edge/Public:mountedOnPath = “/” sharing:sharePointList:_array_id:/Users/charles.edge/Public:dsAttrTypeNative\:sharepoint_account_uuid = “C0405AE4-6CBE-40C7-9584-174687C80C07” sharing:sharePointList:_array_id:/Users/charles.edge/Public:path = “/Users/charles.edge/Public” sharing:sharePointList:_array_id:/Users/charles.edge/Public:smbIsShared = yes sharing:sharePointList:_array_id:/Users/charles.edge/Public:smbIsGuestAccessEnabled = yes sharing:sharePointList:_array_id:/Users/charles.edge/Public:afpName = “Charles Edge’s Public Folder” sharing:sharePointList:_array_id:/Users/charles.edge/Public:dsAttrTypeStandard\:GeneratedUID = “5C13E2AA-A86D-45D0-80B4-00CA86DE2253” sharing:sharePointList:_array_id:/Users/charles.edge/Public:smbDirectoryMask = “755” sharing:sharePointList:_array_id:/Users/charles.edge/Public:afpIsShared = yes sharing:sharePointList:_array_id:/Users/charles.edge/Public:smbCreateMask = “644” sharing:sharePointList:_array_id:/Users/charles.edge/Public:ftpName = “Charles Edge’s Public Folder”

Once you’ve removed the Server app, you’ll be left with using the sharing command. Using that command, you can list shares using the -l option:

sharing -l

That same share then appears as follows:

List of Share Points
name: Charles Edge’s Public Folder
path: /Users/charles.edge/Public
afp: {
name: Charles Edge’s Public Folder
shared: 1
guest access: 1
inherit perms: 0
smb: {
name: Charles Edge’s Public Folder
shared: 1
guest access: 1
read-only: 0
sealed: 0

Or from the Sharing System Preference Pane.

Now you just have to loop through and create each share (although they should co-exist between tools). To create a share, click on the plus sign under Shared Folders.

You can then browse to the folder you’d like to share. Next, we’ll give access to the directory. Use the plus sign on the right side of the screen and then select the user or group you’d like to add to the list that has access to the directory (while the directory is highlighted in the list on the left).

Once the user is in the list, use the permissions on the right side of the user list to select what level each user or group gets.

You have additional controls for file and folder security that can be set at either the directory that is shared or those below it hierarchically. To do so, highlight the directory and use the Get Info option under the File menu in the Finder.

Note: You can also check the Shared Folder box on these folders to share them, meaning you have one less step once you get used to the workflow!

April 25th, 2018

Posted In: Mac OS X

Tags: , , , , , ,

Awhile back, I wrote a tool to rewrap ipa files that I called ipasign: But I wanted to do something similar for the Mac, and specifically have it run in Linux. So looking at what you’d need to be able to do, let’s start with viewing the contents of a flattened Apple package. This command will show you the files installed as a part of the Node JS package. Why did I choose that package? It was sitting on my desktop…

pkgutil --files org.nodejs.node.pkg

Now, this logic is available because you’re running pkgutil on a Mac. But that can’t run in Linux. So what would you do if you wanted to complete that same operation? If the package hasn’t been flattened then you can simply traverse the files in the package. If it has been flattened (and it must be in order to properly be signed) then that can’t work. So to see the files installed from a Linux system will require a tad bit more work. First, we’ll create a directly to extract our package into:

mkdir node-v8.11.1.pkg

Then cd into that directory and use xar to extract the package:

xar -xf /Users/charles.edge/Downloads/node-v8.11.1.pkg

In there, you’ll see three files: Bom, PackageInfo, and Payload. The contents, which mimic the –files option to some extent are found by first changing the name of payload to Payload.gz:

mv ./node-v8.11.1.pkg/Payload ./node-v8.11.1.pkg/Payload.gz

Then unzipping it:

gunzip Payload

And viewing the contents:

cpio -iv < Payload

Or throw all that into a one-liner:

cpio -o | gzip -c > Payload

You can also use bomutils to traverse and make BOMs:

You can also see some metadata about how the package will lay down by catting the distribution file:

<?xml version=”1.0″ encoding=”utf-8″ standalone=”yes”?>
<installer-gui-script minSpecVersion=”1″>
<welcome file=”welcome.html”/>
<conclusion file=”conclusion.html”/>
<background alignment=”topleft” file=”osx_installer_logo.png”/>
<pkg-ref id=”org.nodejs.node.pkg” auth=”root”>
<pkg-ref id=”org.nodejs.npm.pkg” auth=”root”>
<options customize=”allow” require-scripts=”false”/>
<license file=”license.rtf”/>
<line choice=”org.nodejs.node.pkg”/>
<line choice=”org.nodejs.npm.pkg”/>
<choice id=”org.nodejs.node.pkg” visible=”true” title=”Node.js v8.11.1″>
<pkg-ref id=”org.nodejs.node.pkg”/>
<pkg-ref id=”org.nodejs.node.pkg” version=”v8.11.1″ onConclusion=”none” installKBytes=”37377″>#node-v8.11.1.pkg</pkg-ref>
<choice id=”org.nodejs.npm.pkg” visible=”true” title=”npm v5.6.0″>
<pkg-ref id=”org.nodejs.npm.pkg”/>
<pkg-ref id=”org.nodejs.npm.pkg” version=”v5.6.0″ onConclusion=”none” installKBytes=”20113″>#npm-v5.6.0.pkg</pkg-ref>

If you want to make a package, check out this gist:

Next up, you frequently want to check the signature of a package. So to see the signature, I can simply use: pkgutil if on a Mac:

pkgutil --check-signature org.nodejs.node.pkg

Or I can use codesign:

codesign -v node-v8.11.1.pkg

The beauty of codesign is that it’s been open sourced by Apple. The bummer about codesign is that it uses multiple CoreFoundation frameworks:

otool -L /usr/bin/codesign


/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1452.23.0)

/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 58286.51.6)

/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.0)

/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)

April 24th, 2018

Posted In: Mac OS X, Mac Security, Mass Deployment

Tags: , , , , , ,

Next Page »