Transfer Text In And Out Of The iOS Simulator Using xcrun

In a previous article, I covered creating, starting, and stopping iOS simulations. macOS comes with a handy tool to interact with the clipboard (aka pasteboard) on a Mac called pbcopy. You can redirect information from a file into your clipboard using the pbcopy command. Here, we’ll simply call pbcopy and then a file path

pbcopy ~/Desktop/transfer.txt

You can then redirect your text into simctl by doing a pbpaste into

xcrun simctl pbpaste booted

Once you’ve copied your data, clean up the transfer file:

rm ~/Desktop/transfer.txt

You can also pull text out. If you write data into the clipboard (e.g. during instrumentation) then you can extract it from that pasteboard using the simctl subcommand pbcopy as follows:

xcrun simctl pbcopy booted

The xcrun simctl subcommand also comes with a number of other pretty cool automations for programatic control, which I’ll try and cover later.

Managing The Xcode Simulator Programmatically

The iOS Simulator is a great way to test watchOS, tvOS, and iOS apps while you’re writing them. The easiest way to work with the simulator is through Xcode. But you can also use simctl for interacting with it, helpful in automating QA operations when possible. The simctl binary is located at /Applications/ and typically accessed as a verb from the /usr/bin/xcrun command. 

First let’s list all the simulators, done using the list command, called by simply running xcrun followed by simctl for the type of operation to be run and then the list command:

/usr/bin/xcrun simctl list

The output shows a lot of device types, runtimes, and devices, most of which should show as disconnected or shutdown:

== Device Types ==

== Device Types ==
iPhone 4s (
iPhone 5 (
iPhone 5s (
iPhone 6 (
iPhone 6 Plus (
iPhone 6s (
iPhone 6s Plus (
iPhone 7 (
iPhone 7 Plus (
iPhone 8 (
iPhone 8 Plus (
iPhone SE (
iPhone X (
iPhone Xs (
iPhone Xs Max (
iPhone Xʀ (
iPad mini (5th generation) (–5th-generation-)
iPad Air (3rd generation) (–3rd-generation-)
iPad 2 (
iPad Retina (
iPad Air (
iPad mini 2 (
iPad mini 3 (
iPad mini 4 (
iPad Air 2 (
iPad (5th generation) (–5th-generation-)
iPad Pro (9.7-inch) (–9-7-inch-)
iPad Pro (12.9-inch) (
iPad Pro (12.9-inch) (2nd generation) (–12-9-inch—2nd-generation-)
iPad Pro (10.5-inch) (–10-5-inch-)
iPad (6th generation) (–6th-generation-)
iPad Pro (11-inch) (–11-inch-)
iPad Pro (12.9-inch) (3rd generation) (–12-9-inch—3rd-generation-)
Apple TV (
Apple TV 4K (
Apple TV 4K (at 1080p) (
Apple Watch – 38mm (
Apple Watch – 42mm (
Apple Watch Series 2 – 38mm (
Apple Watch Series 2 – 42mm (
Apple Watch Series 3 – 38mm (
Apple Watch Series 3 – 42mm (
Apple Watch Series 4 – 40mm (
Apple Watch Series 4 – 44mm (
== Runtimes ==
iOS 12.2 (12.2 – 16E226) – 
tvOS 12.2 (12.2 – 16L225) – 
watchOS 5.2 (5.2 – 16T224) – 
== Devices ==
— iOS 12.2 —
    iPhone 5s (4A2CC670-55EF-4B36-896F-EC2009EDEC07) (Shutdown) 
    iPhone 6 (937105ED-AEF1-4487-B406-1795D9AD9243) (Shutdown) 
    iPhone 6 Plus (E4031984-D79E-4888-A104-0C6DA73EB67F) (Shutdown) 
    iPhone 6s (EA859DD2-D934-4CB1-B2D5-055F1DF93FB9) (Shutdown) 
    iPhone 6s Plus (CFFD27D8-4FEE-4632-B3BB-8133DBF74925) (Shutdown) 
    iPhone 7 (66BE2C4D-CB66-4618-A95D-BF239C569965) (Shutdown) 
    iPhone 7 Plus (3C6B97F3-AF19-49A9-B400-09A5CBCCB3D6) (Shutdown) 
    iPhone 8 (2D18B685-A642-44E5-BF8E-6D09AB478B0F) (Shutdown) 
    iPhone 8 Plus (BF652604-8F18-457C-B111-E951E8B55947) (Shutdown) 
    iPhone SE (384E1A51-7354-4FAC-A1A8-CB2B9CC5D1CF) (Shutdown) 
    iPhone X (98DD2AEC-4566-4896-83E5-E0B8603FFE9E) (Shutdown) 
    iPhone Xs (02A90E87-B2C4-44FE-9477-DD9455D7CB0C) (Shutdown) 
    iPhone Xs Max (79F42764-5020-41EA-87C3-55C541A790F5) (Shutdown) 
    iPhone Xʀ (AD6FD253-20A7-432C-8C22-5070489E787E) (Shutdown) 
    iPad Air (3rd generation) (DAB405D3-C1FD-47ED-B4FE-F0E1857A2862) (Shutdown) 
    iPad Air (3216E338-D522-43C7-9484-D7AEA081E926) (Shutdown) 
    iPad Air 2 (6B0BCC75-0ED6-4FC9-94C5-48A3466B79AD) (Shutdown) 
    iPad (5th generation) (24313239-A853-46EB-B5A3-0DD8A86080F2) (Shutdown) 
    iPad Pro (9.7-inch) (8E2BEE13-D726-4CF5-8388-0913B87495F1) (Shutdown) 
    iPad Pro (12.9-inch) (9493C54B-D61E-4D42-8952-A89C2CCF2FDD) (Shutdown) 
    iPad Pro (12.9-inch) (2nd generation) (5D978189-856D-4598-8C09-876BD832C71F) (Shutdown) 
    iPad Pro (10.5-inch) (2BF88E18-8C89-4515-A486-DC72C7A8629E) (Shutdown) 
    iPad (6th generation) (016DAA95-D917-4CD9-9A54-700D01BAF1AE) (Shutdown) 
    iPad Pro (11-inch) (307BEEDD-8652-4438-944B-CB423A26CC9E) (Shutdown) 
    iPad Pro (12.9-inch) (3rd generation) (D893291C-F214-4B43-A54F-3AB95A5442ED) (Shutdown) 
— tvOS 12.2 —
    Apple TV (1562B2B3-F403-4C99-8026-20AA51C8716B) (Shutdown) 
    Apple TV 4K (6C5CC68C-4C01-4097-91B8-A503B465F961) (Shutdown) 
    Apple TV 4K (at 1080p) (B8DCEFC3-F5BC-455D-B8B7-BAE7A48BE032) (Shutdown) 
— watchOS 5.2 —
    Apple Watch Series 2 – 38mm (06938C62-9757-4F5E-913F-F9639D8AFF9C) (Shutdown) 
    Apple Watch Series 2 – 42mm (BBA6DFC3-BC5A-494F-AFF4-E2B1331AFC69) (Shutdown) 
    Apple Watch Series 3 – 38mm (2D70BA7B-66BF-43E1-9F80-20EEFF588BB5) (Shutdown) 
    Apple Watch Series 3 – 42mm (F0E4E11E-639C-4F45-974A-2AAC684F1411) (Shutdown) 
    Apple Watch Series 4 – 40mm (B0CC419F-220F-46E1-B827-B512B08552B6) (Shutdown) 
    Apple Watch Series 4 – 44mm (7DAE1296-C9FF-4759-A066-1BA42A259853) (Shutdown) 
== Device Pairs ==
620743A8-FD31-408C-AEBF-8D0F0849BD9D (active, disconnected)
    Watch: Apple Watch Series 4 – 40mm (B0CC419F-220F-46E1-B827-B512B08552B6) (Shutdown)
    Phone: iPhone Xs (02A90E87-B2C4-44FE-9477-DD9455D7CB0C) (Shutdown)
20E13C7F-3788-4DA5-962A-FF1389CDDA12 (active, disconnected)
    Watch: Apple Watch Series 4 – 44mm (7DAE1296-C9FF-4759-A066-1BA42A259853) (Shutdown)
    Phone: iPhone Xs Max (79F42764-5020-41EA-87C3-55C541A790F5) (Shutdown)

Next, let’s create a fresh new spiffy simulator called testing_iPhone7. To do so we’ll 

/usr/bin/xcrun simctl create testing_iPhone7

The output includes a UUID such as the following. That can then be used to track further interactions with the simulation:


The most common tasks would be booting, shutting down, erasing, and opening simulations. First let’s boot it up:

/usr/bin/xcrun simctl boot E6D4C2B8-0601-4557-99DA-B6B8251D534D

To shut that same simulator down use the shutdown verb:

/usr/bin/xcrun simctl shutdown E6D4C2B8-0601-4557-99DA-B6B8251D534D

Neither of these commands provide any output on success, but do error on failure. Once you’ve run tests, I like to erase my simulator and start fresh. To do so, simply use the erase command:

/usr/bin/xcrun simctl erase E6D4C2B8-0601-4557-99DA-B6B8251D534D

To open the simulator you loaded, you can use the open :

open /Applications/

Simulator App

For more information on using simctl subcommands, use the help subcommand:

/usr/bin/xcrun simctl help

Notice there are a lot of verbs, which I’ll cover further in later articles:

create              Create a new device.

clone               Clone an existing device.
upgrade             Upgrade a device to a newer runtime.
delete              Delete a device or all unavailable devices.
pair                Create a new watch and phone pair.
unpair              Unpair a watch and phone pair.
pair_activate       Set a given pair as active.
erase               Erase a device’s contents and settings.
boot                Boot a device.
shutdown            Shutdown a device.
rename              Rename a device.
getenv              Print an environment variable from a running device.
openurl             Open a URL in a device.
addmedia            Add photos, live photos, videos, or contacts to the library of a device.
install             Install an app on a device.
uninstall           Uninstall an app from a device.
get_app_container   Print the path of the installed app’s container
launch              Launch an application by identifier on a device.
terminate           Terminate an application by identifier on a device.
spawn               Spawn a process by executing a given executable on a device.
list                List available devices, device types, runtimes, or device pairs.
icloud_sync         Trigger iCloud sync on a device.
pbsync              Sync the pasteboard content from one pasteboard to another.
pbcopy              Copy standard input onto the device pasteboard.
pbpaste             Print the contents of the device’s pasteboard to standard output.
help                Prints the usage for a given subcommand.
io                  Set up a device IO operation.
diagnose            Collect diagnostic information and logs.
logverbose          enable or disable verbose logging for a device

Next up, we’ll look at performing tasks in the Simulator in the next article on regression testing with the Simulator app.

Approve Or Deny GSuite Access For Devices

The Google Directory integration with GSuite allows you to manage which devices have access to GSuite. This allows you to control access based on a variety of factors.

Below, you’ll find a Google Cloud Function that is meant to respond to a webhook. This function takes an action to set a device into ‘approve’ or ‘deny’ as a state within Google Directory. Before using the function you’ll want to set CustomerID, ResourceID, and EMAIL_ACCOUNT for your GSuite account before using.

Once you have all that, you can upload in your Google Cloud Console.

# Google Cloud Function meant to respond to a webhook
# Takes an action to set a device into approve or deny state
# Set CustomerID, ResourceID, and EMAIL_ACCOUNT for your GSuite account before using

from google.oauth2 import service_account
import googleapiclient.discovery

SCOPES = ['']

def get_credential():
credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject(EMAIL_ACCOUNT)
# admin ='admin', 'directory_v1', credentials=credentials)
admin ='admin', 'directory_v1', credentials=delegated_credentials)
return admin

def get_mobiledevice_list(admin, customerId):
results = admin.mobiledevices().list(customerId=customerId).execute()
mobiledevices = results.get('mobiledevices', [])
print('mobile devices name and resourceId')
for mobiledevice in mobiledevices:
print(u'{0} ({1})'.format(mobiledevice['name'], mobiledevice['resourceId']))
return results

def action_mobiledevice(admin, customerId, resourceId, actionName): # actionName: "approve", "block",etc body = dict(action=actionName)
results = admin.mobiledevices().action(customerId=customerId, resourceId=resourceId, body=body).execute()
return results

def main():
admin = get_credential()
customerId = ''
resourceId = ''
action = "approve"
#action = "block"

mobiledevice_list = get_mobiledevice_list(admin, customerId)

action_mobiledevice(admin, customerId, resourceId, action)
print ("Approved successfully")

if __name__ == '__main__':

This is likely to evolve, given that you’ll likely want to migrate your settings into a database as part of your build process, but the general logic is here for now. Happy Googleatinging!

Obtain A List Of Devices or Apps In ZuluDesk Using Bash

The curl command can be used to authenticate to an API using a variety of authentication types such as Bearer, OAuth, Token, and of course Basic. To authenticate to the ZuluDesk API, first create an API token. This is done by logging into ZuluDesk, clicking Organization, then Settings, then API, an then clicking on the Add API Key button.

Once you have your API key, your header will look as follows:

GET /users HTTP/1.1 User-Agent: curl/7.24.0 X-Server-Protocol-Version: 2 Authorization: Basic YOURTOKENHERExxx000111222== Content-Length: 0

The curl command can do this would be as follows, simply converting these into separate values in the -H or header. The URL provided will do a GET against devices, displaying a list of devices in json:

curl -S -i -k -H "Content-Length: 0" "User-Agent: curl/7.24.0" X-Server-Protocol-Version: 2" "Authorization: Basic YOURAPITOKENxx000111222=="

Once you have the “serialNumber” you can programmatically perform a number of other tasks using a POST. Another example would be obtaining a list of apps, done using the /apps/ endpoint.

curl -S -i -k -H "Content-Length: 0" "User-Agent: curl/7.24.0" X-Server-Protocol-Version: 2" "Authorization: Basic YOURAPITOKENxx000111222"

You can also run a POST in the same fashion. In the following we’ll do that, sending a simple delete command to the group 505

curl -X DELETE -S -i -k -H "Content-Length: 0" "User-Agent: curl/7.24.0" X-Server-Protocol-Version: 2" "Authorization: Basic YOURAPITOKENxx000111222"

Overall, the ZuluDesk API is pretty easy to use and follow with just some basic curl commands.

Register A Webhook In Jamf Pro

A webhook is a small web trigger that when fired can easily send amount of small json to a web listener. Most modern software solutions support webhooks. They provide an easy way to trigger events from a piece of software to happen in another piece of software.

An example of this is when a smart group change happens in Jamf Pro, do something elsewhere. To start, you register a webhook in Jamf Pro by opening an instance of Jamf Pro, clicking on Settings, clicking on Global Management, and then clicking on Webhooks.

Registering Webhooks

From the Webhooks screen, click New.

New Webhook Screen

At the New Webhook screen, you will see a number of fields. Here,

  • Display Name: The name used to identify the webhook in Jamf Pro.
  • Enabled: Check to enable the webhook, uncheck the box to disable the webhook.
  • Webhook URL: The URL that the json or xml will be sent to (note that you’ll need something at this URL to accept your webhook).
  • Authentication Type: None is used for an anonymous webhook and basic can be used to send a username and password to the webhook listener.
  • Connection Timeout: How long the webhook will attempt to open a connection before sending data.
  • Read Timeout: How long the webhook will attempt to send data for before it turns off.
  • Content Type: Choose to send information via xml or json.
  • Webhook Event: The type of event that Jamf Pro can send a hook based on.

The options for webhook events include:

  • ComputerAdded
  • ComputerCheckin
  • ComputerInventoryCompleted
  • ComputerPatchPolicyCompleted
  • ComputerPolicyFinished
  • CoputerPushCapabilityChanged
  • DeviceRateLimited
  • JSSShutdown
  • JSSStartup
  • MobileDeviceCheckin
  • MobileDeviceCommandCompleted
  • MobileDeviceEnrolled
  • PatchSoftwareTitleUpdated
  • PushSent
  • RestAPIOperation
  • SCEPChallenge
  • SmartGroupComputerMembershipChange
  • SmartGroupMobileDeviceMembershipChange

An example of a full workflow would be what we did to trigger a Zapier action, documented at Here, we look at sending smart group membership changes to a google sheet so we can analyze it with other tools, a pretty standard use case.


Screen Time And Setting Limits For Ourselves And Our Families

Do you know how much time you spend in various apps and on your device? Do you want to gently be reminded of how much time you’re staring at screens and maybe even be limited in how much you can be lost on the screen? 

First, let’s Let’s do this limiting the time you can be on the device in the first place, using a feature of Screen Time called Downtime:

  • Open Settings
  • Tap on Screen Time
  • Tap Downtime

Tap on and then set the start of Downtime and the stop of Downtime. Tap back on Screen Time in the upper left hand corner of the screen. Now, let’s setup an app limit for social apps (because really, most of us are on those wayyyy too much:

  • Open Settings
  • Tap on Screen Time
  • Tap App Limits
  • Tap an app category (e.g. Social Networking)
  • Set the number of hours you can use that type of app (note, if you set 23 hours and 59 minutes you are totally cheating)
  • Tap Add

Should you want to remove those limits you created, just tap Delete Limit. Or better, just configure apps that are allowed to bypass the limits you’ve made by tapping Always Allowed and adding apps that are always allowed to work. This allows you to limit all your apps except, as an example, Maps and Camera. 

Another option in Screen Time is Content and Privacy Restrictions. To configure these:

  • Open Settings
  • Tap on Screen Time
  • Tap on Content & Privacy Restrictions
  • Turn Content & Privacy Restrictions on by tapping the slider
  • Tap on iTunes & App Store Purchases

Here, you can limit installing apps, deleting apps, or making in-app purchases on the device. You can also just force a password in order to make any purchase from iTunes, Book Store purchases, or App Store purchases

  • Tap the back button
  • Tap Allowed Apps
  • Use the indicator light to disable any app you don’t want to be able to access on this profile
  • Once all apps are configured, tap the back button
  • Tap Content Restrictions

There are a lot of restrictions available. Most are mirrored with a profile and so can be controlled by an MDM as well:

  • Country: Start with the country your ratings are set for. 
  • Music, Podcasts, & News: Then, choose what whether or not explicit content is allowed (and by content we really mean music, podcasts, & news). 
  • Music Profiles & Posts: Then choose whether the device is allowed to publish music options and posts about music. 
  • Movies: Then set a maximum AFTRA rating (e.g. PG-13 or R) for content.
  • TV Shows: Select the TV ratings allowed (e.g. TV-G or TV-MA for mature audiences)
  • Books: Luckily, Tipper Gore never got her way so there’s no true rating systems for books. Just select Clean or Explicit.
  • Apps: Choose an age that ratings for apps are most appropriate
  • Web Content: Limit access only to specific websites, limit access to adult websites, or provide unrestricted access to web content
  • Web Search Content: Allow Siri to access the web to search
  • Explicit Language: Allow or restrict Siri from using dirty words
  • Multiplayer Games: Allow or deny access to multiplayer games
  • Adding Friends: Allow or deny access to add friends within the Game Center app
  • Screen Recording: Allow or deny access to screen recordings

Next, go back and in the privacy section, configure what apps are able to access Location Services, Contacts, Calendars, Reminders, Photos, Share My Location, Bluetooth Sharing, Microphone, Speech Recognition, Advertising, Media And Apple Music. 

Finally, under allow changes, configure whether you’ll be able to make changes to Passcode Changes, Account Changes, Cellular Data Changes, Volume Limits, Do Not Disturb While Driving, TV Providers, and Background App Activities. 

Episode 107 of the MacAdmins Podcast: Sweet Rootkits, a Year in Review

It’s been a great year for the MacAdmins Podcast. And a special thank you to Tom, Marcus, Emily, James, and the former co-host Pepijn for continuing to allow me to be a part of something special. The last episode of the year is available at, using the below embedded link, or wherever you get your podcasts!

New Page Explaining Apple MDM Even Further

Apple has published a new page that goes through all of the settings and commands available via MDM and explains many in much more detail. This is available at The new guide is a great addition to the work @Mosen has done at in terms of explaining what each setting, command, and payload do. And let’s not forget the definitive MDM protocol reference guide, available at

Overall, I’m excited to see so much information now available about MDM, including how to develop an MDM properly, what each setting does, and now what you should expect out of an MDM!

Apple Business Manager Documentation Publicly Available

Hey, who knew that the developer documentation for Apple Business Manager would be made publicly available? It’s at Or if it gets taken down, at Apple Business Manager Documentation.

Note: I saw this pop up in like 4 different places. If anyone knows who I can attribute for realizing it was publicly available, please let me know so I can!

Using Managed App Config with Jamf Pro

Hey look, there’s a new category on the Jamf Marketplace, available at,selecting the AppConfig category. The new AppConfig category gives administrators of any MDM that supports AppConfig access to a set of apps that support AppConfig. If you have an app that isn’t listed here, feel free to let me know. 

What does this mean? Well, AppConfig is a way of sending data into an app. App config allows a customer to deploy settings into applications on iOS devices in much the same way that settings can be sent into a Mac app via the defaults command. This means an end user could get an app installed on their device from the iOS App Store, a custom app, or a B2B app and that app would have any settings the user might need to connect to servers or configure the experience.

So what is Managed App Config? At it’s most basic, you identify a label and a value in XML and send it to an iOS device that’s running iOS 7 or later (e.g. via Jamf 9 and up). The vendor who makes the app has to basically define what those settings are. Which brings up an interesting problem never fully addressed with defaults domains: standardization and ease-of-use (although MCX was close).  is a consortium of MDM vendors and software vendors that maintain the emerging AppConfig standards around Managed App Config (within the confines of what Apple gives vendors) and then makes a feed of settings for apps that conform to those standards. Jamf is a founding member of, along with MobileIron and AirWatch. Examples of what you could put into the feed include 
  • Enabling certain features of apps
  • Server URLs
  • Logos (if they’re pulled dynamically)
  • Text labels
  • Language packs

To see a list of apps that are available, check out 

Managed App Config options are set by vendors at compile time within the code and then the XML sent with the app is parsed by the app at installation time. If you’re a software vendor who wants to get started with AppConfig, check out the Spec Creator from Jamf Research or get in touch with the developer relations team from any MDM vendor.

If you’re a customer of an app and would like to leverage Managed App Config and your vendor isn’t listed on the site, get in touch with them, as this is the future of app management and chances are that you won’t be the only organization looking to unlock this type of feature. 

Let’s look at how this actually works. The Managed App Config options per supported app are available on a feed. The feed is available at Here, as follows, you’ll see a list of all of the apps supported.

You can then copy the path for an app, such as com.adobe.Adobe-Reaser/1/appconfig.xml and append it to the end of the URL to get the feed for that specific app. You can test this using to see output as follows.

Here, note that most of these fields are key value pairs defined by Adobe (in this example at least). You can enable or disable features of Adobe Reader using these keys. The same is true with a tool like Box that might want a more granular collection of settings than a feature like Managed Open In. 

Once you have the XML, you can then copy it to the clipboard and paste it into the App Configuration tab of an app, as follows. 

Finally, Apple has sample code available at