An Octory Pro tour
What you will learn in this tutorial
Prerequisites
To not get lost in this tutorial, you should know how to start the Octory application on your device. You can refer the tutorial Getting started with Octory to learn that.
First part: Onboard
- How to conditionally insert and remove interface components.
- How to conditionally execute a bash command with root rights as well as a Jamf policy.
- How to conditionally display a monitor
- How to inform the user when the onboarding process is over
Second part: Watch over
- Move Octory to the menu bar
- Display relevant information to the user from the menu bar.
- Execute a support command
This tutorial requires a valid Octory PRO license inside the /Library/Application Support/Octory folder. You can find more information to buy PRO licenses on the website. Also, you can request a free trial license at this email address.
- Materials are provided like images and sounds, in the Resources folder on the configurations repository. If you can use this folder from wherever you want, it will be simpler to follow this tutorial if you copy the folder at the following path: /Library/Application Support/Octory/Resources.
- Do not forget to check /tmp/username-octory.log or /tmp/username-ceremony.log if you face an issue.
- This tutorial has two Octory configuration files you can find in the configurations repository: Octory_first_part_start.plist is the one to use to start the first part of this tutorial, and Octory_first_part_finish.plist is the one you should have when first part is finished. The same goes for the second part files.
Insert and remove components
Start by copying the file Octory_first_part_start.plist in the folder /Library/Application Support/Octory. You might want to rename it to remove the “start” part. I will rename it to Octory_pro.plist. In this tutorial, we will work mainly inside the folder /Library/Application Support/Octory, assuming the Octory.app is in there. But you are free to launch Octory from anywhere you want, as long as the file Octory_pro.plist is at /Library/Application Support/Octory.
Assuming that you are in the folder Octory (cd "/Library/Application Support/Octory"
), start by launching Octory by specifying the configuration we just copied:
open Octory.app --args -c Octory_pro.plist
You should see the following window with the (current logged in user name) appearing on your screen.

ScreenIndex
. Rather than dive into the configuration file right now, you should try to set the key value with:PlistBuddy
/usr/libexec/PlistBuddy -c "Set :Window:ScreenIndex 1" Octory_pro.plist
Scout:
scout set "Window.ScreenIndex"=1 -m Octory_pro.plist
.
To know which screen corresponds to which index, you can use the brightness tool and identify your screens by their resolution.
brew install brightness
brightness -lv
Add a variable to play with
We will start by adding a ListInput
component so that we can play with its variable. Please open the configuration file (Octory_pro.plist in this tutorial), and expand the Slides array.

Here is the Slides array as Plist.
<array>
<dict>
<key>Containers</key>
<array>
<dict>
<key>Components</key>
<array>
<dict>
<key>Type</key>
<string>Spacer</string>
</dict>
<dict>
<key>Text</key>
<string>Welcome to ${CompanyName} ${USER_FULL_NAME}!</string>
<key>TextFontConfiguration</key>
<string>Title</string>
<key>Type</key>
<string>Text</string>
</dict>
<dict>
<key>Type</key>
<string>Spacer</string>
</dict>
</array>
</dict>
</array>
</dict>
</array>
Insert the following ListInput
component as a new component in the components array:
<dict>
<key>Type</key>
<string>Input</string>
<key>InputType</key>
<string>List</string>
<key>Variable</key>
<string>FoodLike</string>
<key>Label</key>
<string>What food do you like?</string>
<key>PercentWidth</key>
<real>0.2</real>
<key>Margins</key>
<dict>
<key>Top</key>
<integer>30</integer>
</dict>
<key>Items</key>
<array>
<string>Sushis</string>
<string>Pasta</string>
<string>Tartiflette</string>
</array>
</dict>
The components array should now look like this:

Notes
- We are using the variable
FoodLike
to retrieve the user’s choice. Thus, it can have several values: “Sushis”, “Pasta” and “Tartiflette”. - The
PercentWidth
key is used to to specify how large theListInput
component should be in its container. More info here. - The tartiflette is a famous French meal with lot of cheese and potatoes.
Reload the app interface with ⌘R. The ListInput
component should now be present, and you can select a value inside it.

Now it’s time to add conditions!
Add conditions
We will add three Text
components: one for each possible meal. Each Text
component will appear when the user selects a meal.
In Octory, a condition is a String key which is most often named Condition. The value of this key is very similar to what you might use in Bash or another programming language. For example, to state that a component should only be visible when the variable FoodLike
has the value “Sushis”, we would write
FoodLike == "Sushis"
That’s it! So to insert a Text
component which only appears when the user select the “Sushi” value, we would insert the following below the ListInput
component we added previously:
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>So, do you like sushis?</string>
<key>Condition</key>
<string>FoodLike == "Sushis"</string>
<key>TextFontConfiguration</key>
<string>Body</string>
</dict>

The key TextFontConfiguration
uses a FontConfiguration defined in the FontStyles section.
Now, reload the app interface with ⌘R and… wait a minute! The text is visible although we just added a condition to it! Well, it’s logical actually.
The value “Sushis” is selected by default in the ListInput
component. So the variable FoodLike
has the “Sushis” value.
We could add validation to it to prevent this behaviour
but it is not the purpose of this tutorial.
If you select another value than the “Sushis” one, you should see the text disappear, and reappear when you select again “Sushis”.
It was easy, wasn’t it? And now a little exercise! You did not think I would do all the work myself, did you? 😉 Let’s add the two other Text
components. You can set their Text
keys to whatever you like. You should add them below the Text
component we just added.
Small tip: you should copy the Text
component we just added and paste it twice.
When you have finished, here is the solution:
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>So, do you like sushis?</string>
<key>Condition</key>
<string>FoodLike == "Sushis"</string>
<key>TextFontConfiguration</key>
<string>Body</string>
</dict>
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>We all love pasta</string>
<key>Condition</key>
<string>FoodLike == "Pasta"</string>
<key>TextFontConfiguration</key>
<string>Body</string>
</dict>
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>You are keen of cheese too, he?</string>
<key>Condition</key>
<string>FoodLike =="Tartiflette"</string>
<key>TextFontConfiguration</key>
<string>Body</string>
</dict>
Now reload the application interface with … ⌘R (you got it!). You should see the several Text
components
appear and disappear depending on the value you choose.
The condition key is available in all components. Even a Spacer
component can be inserted conditionally!
Several components can also have the same condition for example, and thus appear at the same time. If you want to learn more about conditions,
you can refer to the documentation. Take the time to try them, as they are really useful in many scenarios.
We can imagine a dynamic form whose content will appear under specific conditions. But also a way to spare the space and to display an information to the user only when it is relevant, and so on…
Conditionally execute a bash command or a Jamf policy
Playing with a file
Displaying information depending on the user inputs is great. But now it’s action time! We will execute a bash command depending on the user choice. Then we will see how to do the same to execute Jamf policy.
There are several actions you can execute in Octory. Some let you interact with the user like the DisplayAlert
action, while some let you send instruction to the Mac, like the RestartComputer
action. You can find the full actions list in the documentation.
We will start to play with the ExecuteCommand
action. It is certainly the most useful action as you can use it to execute bash/zsh commands with root privileges. Let’s make a file to play with. Run the following command in terminal:
echo "Octory Pro actions rock!" >> "/Library/Application Support/Octory/file"
sudo chown root:wheel "/Library/Application Support/Octory/file"
sudo chmod 600 "/Library/Application Support/Octory/file"
Now if you try to run cat "/Library/Application Support/Octory/file"
you should get a Permission denied error. Run sudo !!
to make sure you can output the file with root rights.
Installing Octory Helper
As Octory has the rights of the user launching it, it cannot execute actions which require root privileges. A solution would be to run Octory as the root user. But really: this is a bad solution. An application should never be ran as root, especially when it is customisable like Octory. It is a fault which can be easily exploited. As an application can all the same need to execute actions with root rights, Apple provides a way to it safely (or at least with more security than running the app as root). The developer (your humble servant) has to develop a Helper. It’s a program which will be ran as root by a LaunchDaemon. Only the application signed with the same certificate as the one used to sign the Helper can use it. The LaunchDaemon will act as a listener. When the application requests the right to use the Helper, it will forward the request to the Helper. The latter then ensure the application is authorised, and execute the request.
You can find the Helper in the downloads section of Octory website:

It contains three files. The Helper, the LaunchDaemon and a script installHelper.sh to install them. The script will install the Helper in the required folder: /Library/PrivilegedHelperTools. Then it will copy the LaunchDaemon in /Library/LaunchDaemons and load it. The script requires root rights to run as it installs files in system folders. It will output the success of the operation. You can then check that everything is setup with
sudo launchctl list | grep com.amaris.octory.helper
If everything has been installed correctly, the command should output something like
- 0 com.amaris.octory.helper
If you face an issue in the process, please let me know on Slack.
Build the action
Now that everything is setup, let’s build our action. Action
s are specified within an ActionSet
. You can specify an ActionSet
in the ActionSets section array, or as the OnClick
key of a button. In this tutorial, we will start by defining one action set in the ActionSets section array and add one action into it.
Add a new array key at the top of the configuration file (just below the root element if you are using PLIST Editor) and name it ActionSets (only one “s” to “Set”):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ActionSets</key>
<array/>
<key>Admin</key>
<dict>
<key>IsAdminModeEnabled</key>
<true/>
</dict>
...
An ActionSet
is a dictionary with several children keys, whose two are required: the String Type
key and the Array Actions
key. The Type
key can take two values: “Parallel” and “Chained”. Those let you specify how you want the actions to be executed: one after the previous has finished with “Chained”, or concurrently with “Parallel”. Last but not least, you should specify an Array key of Trigger
s to let Octory know when the actions in the set should be executed. You can specify several triggers. Among them is “Launch” to execute the action set when the application launches, or “Update(variable)” to execute the set when a variable is updated. You can find the list here.
For now, we will set the Type
to “Chained”, add a “Launch” trigger and add the required Actions
key:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>Type</key>
<string>Chained</string>
<array>
<string>Launch</string>
</array>
<key>Actions</key>
<array/>
</dict>
</array>
<key>Admin</key>
...
Make sure the application can retrieve the configuration by reloading the interface and checking the log.
Let’s add our action. An Action
is a dictionary key with one required child key: Type
. Depending on the provided value for the Type
key, other keys can be required. The action we want to add has the type “ExecuteCommand” and thus requires one key to be specified: the Command
key. This key has a String value which is the bash/zsh command to execute. It supports pipes |
to let you manipulate the command output. This output can be stored as a variable in Octory with the Variable
key. Let’s read the content of the file we just created and store that into a variable named message. Here how it looks like:

<array>
<dict>
<key>Type</key>
<string>Chained</string>
<key>Triggers</key>
<array>
<string>Launch</string>
</array>
<key>Actions</key>
<array>
<dict>
<key>Type</key>
<string>ExecuteCommand</string>
<key>Command</key>
<string>cat "/Library/Application Support/Octory/file"</string>
<key>Variable</key>
<string>message</string>
</dict>
</array>
</dict>
</array>
Now restart the app. Ostensibly nothing happened. We don’t use the variable in a Text
component. To see it, open your Octory log and search for the line “Successfully updated a variable [ message: ‘Octory Pro actions rocks!’]”. Under the hood, a huge work has been done (trust me I know a few things about it 😉): convert the string to a bash command, send it to the Helper to execute it with root privileges, wait for the result and write it into the variable message.
To see the variable in the interface, you can add a Text
component below the last Text
component with a condition, and use the message variable inside it (I added a top margin with 15 points to ventilate the layout):
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>You are keen of cheese too, he?</string>
<key>Condition</key>
<string>FoodLike == "Tartiflette"</string>
<key>TextFontConfiguration</key>
<string>Body</string>
</dict>
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>Here is the message: ${message}</string>
<key>Margins</key>
<dict>
<key>Top</key>
<integer>15</integer>
</dict>
<key>TextFontConfiguration</key>
<string>Body</string>
</dict>
You can now restart the application to see the variable in the added Text
component (reloading the interface would have no effect, as the action set is executed at the application launch):

About the Helper
You can find a log which shows the commands the Helper executed as well as the outputs at /tmp/octoryHelper.log. It can be useful for debugging. But once you are sure everything is working correctly you might want to avoid to output the commands on your end-users machines. To prevent that, run the following (you can also edit the LaunchDaemon manually):
PlistBuddy
sudo /usr/libexec/PlistBuddy -c "Delete StandardErrorPath" /Library/LaunchDaemons/com.amaris.octory.helper.plist
sudo scout delete StandardErrorPath -m /Library/LaunchDaemons/com.amaris.octory.helper.plist
To finish this chapter, let’s add a command to read the message variable. The key to add will be very similar to the one we just used: an ExecuteCommand
action with the following command: “say ${message}”. Once again, you can try to find the solution yourself.
…
…
And here is mine (you are fast!).
<dict>
<key>Type</key>
<string>ExecuteCommand</string>
<key>Command</key>
<string>say ${message}</string>
</dict>

Make sure your speakers are on and restart the app. You should hear your Mac saying “Octory Pro actions rock!”… I’m not the one saying that, it’s your computer! More seriously, note that as we setup the Type
key of the ActionSet
to “Chained”, Octory waits for the first action to finish before executing the second one. And this is great as the second action needs to access the message variable which is written by the first action!
Jamf policy
We will not explain how to setup a Jamf policy in this tutorial, but I will tell you how to use it in Octory. It is another Action
with the Type
key set to “ExecuteJamfPolicy” and one required child key: Name
to let you specify policy custom trigger. For example:
<dict>
<key>Type<key>
<string>ExecuteJamfPolicy</string>
<key>Name</key>
<string>StartEnrolling</string>
</dict>
Pretty simple, right? Note that Octory uses the Helper to execute the policy as it requires root privileges.
How to conditionally display a monitor
If it is great to display components depending on conditions, it would be also interesting to conditionally display an application monitor. For example, it might be useful to execute a Jamf policy to install a package depending on the user choice, and then to display the corresponding monitor. Naturally, if I’m talking about such a feature, it is because it is possible to do so!
Setup
We will play with three monitors to insert and remove them, while another one will stay in place. As monitoring with Octory is not the purpose of this tutorial, I will ask you to make several copy and paste actions. It is not the most interesting part of the tutorial but I promise you that it will over quickly. You just need attention and we will go through this together, ready? Go!
Monitors
First: the monitors. the monitors: please copy the following. If you are not using PLIST Editor, copy only between the plist keys as indicated by the comments. Otherwise, you can copy the overall text.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<!-- Copy from here for a standard text editor -->
<dict>
<key>Monitors</key>
<array>
<dict>
<key>Type</key>
<string>Application</string>
<key>Installer</key>
<string>System</string>
<key>Name</key>
<string>Drink</string>
<key>IconURL</key>
<string>${Monitors}/drink.png</string>
</dict>
<dict>
<key>Type</key>
<string>Application</string>
<key>Installer</key>
<string>System</string>
<key>Name</key>
<string>Sushis</string>
<key>IconURL</key>
<string>${Monitors}/sushis.png</string>
</dict>
<dict>
<key>Type</key>
<string>Application</string>
<key>Installer</key>
<string>System</string>
<key>Name</key>
<string>Pasta</string>
<key>IconURL</key>
<string>${Monitors}/pasta.png</string>
</dict>
<dict>
<key>Type</key>
<string>Application</string>
<key>Installer</key>
<string>System</string>
<key>Name</key>
<string>Tartiflette</string>
<key>IconURL</key>
<string>${Monitors}/tartiflette.png</string>
</dict>
</array>
</dict>
<!-- Copy up to here for a standard text editor -->
</plist>
PLIST Editor
Paste the copied text at the end of the plist, below the closing tag </dict>
of the Window section:

Then change the “New item –2” key name to “Monitoring”:

Standard text editor
Paste the copied text between the closing </dict>
tag of the Window section and the global closing </dict>
tag:

Add the key name below the closing closing </dict>
tag of the Window section : <key>Monitoring</key>
:

Variables
To enjoy the (beautiful) icons for the monitors we will add the variables to the resources folder. So please copy the following in General→Variables
, below the “CompanyName” key:


PLIST Editor
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<string>/Library/Application Support/Octory/Resources</string>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<string>${Resources}/Monitors</string>
</plist>
Standard text editor
<key>Resources</key>
<string>/Library/Application Support/Octory/Resources</string>
<key>Monitors</key>
<string>${Resources}/Monitors</string>
Appearance
Finally, let’s just take the left container take2/3 of the width, by adding the Boolean key IsLarge
to it, with the value true
:
And we are ready to go! Run the app, it should look like this now:

Conditionally display a monitor
We will display the “Sushis” monitor only when the user selects the “Sushis” option. For the “Pasta” and “Tartiflette” monitors, we will display them together with a less specific condition.
Let’s add the “Sushis” condition. It is exactly the same as for the sushis Text
component. So please add a Condition
key to the “Sushis” monitor with the value FoodLike == "Sushis"
.

<dict>
<key>Type</key>
<string>Application</string>
<key>Installer</key>
<string>System</string>
<key>Name</key>
<string>Sushis</string>
<key>IconURL</key>
<string>${Monitors}/sushis.png</string>
<key>Condition</key>
<string>FoodLike == "Sushis"</string>
</dict>
If you reload the interface now, and change the option to something else than “Sushis”, the monitor should disappear.
Let’s now add the condition to display both “Pasta” and “Tartiflette” monitors together. We will add a condition “IsFoodEuropean” which will be true when the FoodLike
variable equals “Pasta” or “Tartiflette”. To avoid to repeat ourselves, let’s store this condition and use it in both monitors. To do so, please add a Conditions section in the file, and add a key named IsFoodEuropean
with the value "Pasta, Tartiflette" <: FoodLike
into it:

<plist>
<dict>
<key>Conditions</key>
<dict>
<key>IsFoodEuropean</key>
<string>"Pasta, Tartiflette" <: FoodLike</string>
</dict>
<key>ActionsSets</key>
<dict>
...
Here is how it works:
- We specify an array of possibilities between double quotes, separating items with a coma: "Pasta, Tartiflette"
. Note that we could also define a variable to contains those items.
- The operator <:
translates a “contains” condition. The condition is true
is the left operand "Pasta, Tartiflette"
contains the right operand; the FoodLike
variable.
Thus, the condition is true
only when the FoodLike
variable is equal to “Pasta” or “Tartiflette”.
Finally, let’s add our new condition to the remaining monitors “Pasta” and “Tartiflette”. Here is the “Pasta” monitor. Note that as the variable “IsFoodEuropean” has a true
or false
value, we can specify its true
condition by simply writing it. I’ll let you add the condition to the “Tartiflette” one.

<dict>
<key>Type</key>
<string>Application</string>
<key>Installer</key>
<string>System</string>
<key>Name</key>
<string>Pasta</string>
<key>IconURL</key>
<string>${Monitors}/pasta.png</string>
<key>Condition</key>
<string>IsFoodEuropean</string>
</dict>
Now reload the interface. The monitors “Pasta” and “Tartiflette” should have disappear. If you choose “Pasta” or “Tartiflette” as an option, they should appear while the “Sushis” monitor should disappear. Also, note that the Text
components still appear and disappear depending on your choice. This is starting to be really dynamic!
Inform the user when the onboarding process is over
Display an alert
Now, to end the first part of this tutorial, let’s use a nice feature to finish the onboarding process. We will display an alert and play a custom sound to the end-user when all monitors are installed.
To do so, we will add another ActionSet
to our ActionSets section. Remember it ? We used that to execute a bash command to output a root owned file content and make the Mac speak out loud. So, please add a new item to the ActionSets array, setting its Type
key to “Parallel”, add a Trigger
array and an Actions
array into it.
Small tip: you should copy and paste the first existing item and paste it below. Then modify the Type
key and remove the items in the arrays

<key>ActionSets</key>
<array>
<dict>
...
</dict>
<dict>
<key>Type</key>
<string>Parallel</string>
<key>Triggers</key>
<array/>
<key>Actions</key>
<array/>
</dict>
</array>
The trigger we are going to use let us execute the ActionSet
when a variable is updated. The variable we are going to observe is a one generated by Octory: INSTALLATION_COMPLETE
. It is set to true
when all monitors (applications and mandatory files) are installed/present. By using the trigger “Update(INSTALLATION_COMPLETE)”, we are telling Octory to execute the set whenever this variable changes.
Hm, this is not exactly what we want. We want to execute the action to display an alert when the INSTALLATION_COMPLETE
variable is true
only. Not when it is set to false
. So… let’s add a condition! An ActionSet
can have a Condition
key to specify to Octory whether the set should be executed when it is triggered. The condition here is simply “INSTALLATION_COMPLETE”. Here is how our set looks like when adding those keys:

<key>ActionSets</key>
<array>
<dict>
...
</dict>
<dict>
<key>Type</key>
<string>Parallel</string>
<key>Condition</key>
<string>INSTALLATION_COMPLETE</string>
<key>Triggers</key>
<array>
<string>Update(INSTALLATION_COMPLETE)</string>
</array>
<key>Actions</key>
<array/>
</dict>
</array>
Our set is ready to execute the DisplayAlert
action, so let’s configure it. A DisplayAlert
action has three required keys: the string Title
key is the alert title, the string Message
key is the message displayed below the title, and the string Style
key lets us choose the alert style: “Informational”, “Warning”, “Critical”. We will use the “Informational” value, but you are free to test the two others. Here is the reference in the documentation.

<dict>
<key>Type</key>
<string>Parallel</string>
<key>Condition</key>
<string>INSTALLATION_COMPLETE</string>
<key>Triggers</key>
<array>
<string>Update(INSTALLATION_COMPLETE)</string>
</array>
<key>Actions</key>
<array>
<dict>
<key>Type</key>
<string>DisplayAlert</string>
<key>Title</key>
<string>Finished!</string>
<key>Message</key>
<string>Everything is installed and your ${DEVICE_MODEL_NAME} is ready to use.</string>
<key>Style</key>
<string>Informational</string>
</dict>
</array>
</dict>
Now relaunch the application (not reload its interface). Whatever the current displayed monitors, if you navigate to the Admin menu bar and clicks on “Reach end of simulation” (⌥⌘M), all the monitors should be set as “Installed” and the alert should appear in the top of Octory window:

You can customise an alert with many options like adding buttons and associate variables to them. In this tutorial, we will only add a custom icon to use. Let’s first add an “Images” variable, and as you are there, add a “Sounds” which will be useful soon.

<key>General</key>
<dict>
<key>Variables</key>
<dict>
<key>CompanyName</key>
<string>Acme, Inc.</string>
<key>Resources</key>
<string>/Library/Application Support/Octory/Resources</string>
<key>Images</key>
<string>${Resources}/Images</string>
<key>Sounds</key>
<string>${Resources}/Sounds</string>
<key>Monitors</key>
<string>${Resources}/Monitors</string>
</dict>
</dict>
If you navigate to the Images folder in the provided materials, you will see an image named “acme_logo.png”. It is the image we are going to use. Here is how:

<dict>
<key>Type</key>
<string>Parallel</string>
<key>Condition</key>
<string>INSTALLATION_COMPLETE</string>
<key>Triggers</key>
<array>
<string>Update(INSTALLATION_COMPLETE)</string>
</array>
<key>Actions</key>
<array>
<dict>
<key>Type</key>
<string>DisplayAlert</string>
<key>Title</key>
<string>Finished!</string>
<key>Message</key>
<string>Everything is installed and your ${DEVICE_MODEL_NAME} is ready to use.</string>
<key>Style</key>
<string>Informational</string>
<key>IconPath</key>
<string>${Images}/acme_logo.png</string>
</dict>
</array>
</dict>
Now restart the application and use the same option “Reach end of simulation” (⌥⌘M):

Play a sound
You can ask Octory to play a sound with the PlaySound
action. It can play a sound from the system, like “Hero”, or play a sound in a file. We will be using the second option. As we want the sound to be played when the installation is finished, we will just add the PlaySound
action in the set which already contains the DisplayAlert
action. As the Type
key of the set has a “Parallel” value, the DisplayAlert
and PlaySound
actions will be executed concurrently. Let’s do this!
Note: Sorry for the sound which will be played. I could not resist.

<dict>
<key>Type</key>
<string>Parallel</string>
<key>Condition</key>
<string>INSTALLATION_COMPLETE</string>
<key>Triggers</key>
<array>
<string>Update(INSTALLATION_COMPLETE)</string>
</array>
<key>Actions</key>
<array>
<dict>
<key>Type</key>
<string>DisplayAlert</string>
<key>Title</key>
<string>Finished!</string>
<key>Message</key>
<string>Everything is installed and your MacBook Pro is ready to use.</string>
<key>Style</key>
<string>Informational</string>
<key>IconPath</key>
<string>${Images}/acme_logo.png</string>
</dict>
<dict>
<key>Type</key>
<string>PlaySound</string>
<key>SoundNameOrFilePath</key>
<string>${Sounds}/roadrunner.wav</string>
</dict>
</array>
</dict>
Finally, if you restart the application and reach the end of the simulation one again, you should here the custom sound (mip mip!).
And that’s all for the first par of this tutorial! Pfeew! So far, you learned:
- how to conditionally insert and remove interface components
- how to conditionally execute a bash command with root rights as well as a Jamf policy
- how to conditionally display a monitor
- how to inform the user when the onboarding process is over
I hope this gave you many ideas to onboard your users with a nice experience, as well as facilitate your workflow. Take a break to think about it. We will soon dive into Octory features as a day-to-day software to watch over your users. Exciting!
Move Octory to the menu bar
Moving Octory to the menu bar offers new possibilities to use the software on a day to day basis. We will see first that it can be useful to display relevant information to the user using placeholders, then how to use it to propose custom actions to the user. Executing them with root privileges if necessary.
You will find a configuration file named Octory-second-part-start.plist. Please use this file now for the second part by moving it to the /Library/Applications Support/Octory/ or /Library/Managed Preferences/, and launch Octory in the command-line with the configuration
option:
open Octory.app --args -c Octory-second-part-start.plist
To move Octory to the menu bar, we have to change the key Window→OnScreen
to “MenuBar”, and to add a new section in the configuration file, named MenuBarSlide. This MenuBarSlide is a sole slide which will be displayed when clicking on the Octory menu bar icon. Let’s make those changes.
OnScreen
key

<dict>
<key>MaximumSize</key>
<dict>
<key>Height</key>
<integer>960</integer>
<key>Width</key>
<integer>1280</integer>
</dict>
<key>MinimumSize</key>
<dict>
<key>Height</key>
<integer>768</integer>
<key>Width</key>
<integer>1024</integer>
</dict>
<key>OnScreen</key>
<string>MenuBar</string>
<key>Position</key>
<dict>
<key>Horizontal</key>
<string>Center</string>
<key>Vertical</key>
<string>Center</string>
</dict>
<key>ScreenIndex</key>
<integer>2</integer>
</dict>
MenuBarSlide section
We will add a simple slide to start: one Text
component with two Spacer
s to center it.

<key>Window</key>
<dict>
...
</dict>
<key>MenuBarSlide</key>
<dict>
<key>Containers</key>
<array>
<dict>
<key>Components</key>
<array>
<dict>
<key>Type</key>
<string>Spacer</string>
</dict>
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>Help</string>
<key>TextFontConfiguration</key>
<string>Title</string>
</dict>
<dict>
<key>Type</key>
<string>Spacer</string>
</dict>
</array>
</dict>
</array>
</dict>
Launch the app now, using open Octory.app --args -c Octory-second-part-start.plist
if you did not already run this command once. You should be able to see the Octory icon in your menu bar, and to click on it.

Display relevant information to the user
When an end-user contacts the support to find some help, it might be useful for them to find all relevant information in one place. For example, it is possible to display the Mac serial number and UUID, but also more specific values like the Mac asset-tag if one is set. If we can use Octory placeholders to display the Mac serial number and UUID, retrieving the asset tag would be done by executing a command.
Let’s add those placeholders.
Note
To edit Octory when it is on the Menu bar, it might be painful to have to click on the menu bar icon to reload the interface or see the changes. To make your workflow easier, you might want to add the key Admin→IsMenuBarSemiTransient
and set its value to true
. Thus, the window will not disappear when you click outside of its bounds, which is easier if you have to move the to configuration file often to edit it. When you want to hide the window on the menu bar, right click on the menu bar icon.

Device serial number
Add a new Text
component to the Components
array in the MenuBarSlide. This component will display the device serial number using a placeholder.

<!-- First text component -->
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>Help</string>
<key>TextFontConfiguration</key>
<string>Title</string>
</dict>
<!-- Label text component: Mac serial number -->
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>Mac serial number</string>
<key>TextFontConfiguration</key>
<dict>
<key>Style</key>
<string>Body</string>
<key>SystemFontWeight</key>
<string>Semibold</string>
</dict>
<key>Margins</key>
<dict>
<key>Top</key>
<integer>10</integer>
</dict>
</dict>
<!-- Placeholder text component: Mac serial number -->
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>${DEVICE_SERIAL_NUMBER}</string>
<key>TextFontConfiguration</key>
<string>Body</string>
<key>Margins</key>
<dict>
<key>Top</key>
<integer>5</integer>
</dict>
</dict>
Now once you click on the menu bar icon and the window appeared, reload the application interface with ⌘R.

Device UUID and user name
And now, it’s your turn! Insert two new components to display the placeholders DEVICE_UUID
and USER_NAME
. You can simply copy and paste the Text
component we just added. Once your done, here is how the application should look like.

And here is my solution.

<!-- Label text component: Mac UUID -->
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>Mac UUID</string>
<key>TextFontConfiguration</key>
<dict>
<key>Style</key>
<string>Body</string>
<key>SystemFontWeight</key>
<string>Semibold</string>
</dict>
<key>Margins</key>
<dict>
<key>Top</key>
<integer>10</integer>
</dict>
</dict>
<!-- Placeholder text component: Mac UUID -->
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>${DEVICE_UUID}</string>
<key>TextFontConfiguration</key>
<string>Body</string>
<key>Margins</key>
<dict>
<key>Top</key>
<integer>5</integer>
</dict>
</dict>
<!-- Label text component: User name -->
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>User name</string>
<key>TextFontConfiguration</key>
<dict>
<key>Style</key>
<string>Body</string>
<key>SystemFontWeight</key>
<string>Semibold</string>
</dict>
<key>Margins</key>
<dict>
<key>Top</key>
<integer>10</integer>
</dict>
</dict>
<!-- Placeholder text component: User name -->
<dict>
<key>Type</key>
<string>Text</string>
<key>Text</key>
<string>${USER_NAME}</string>
<key>TextFontConfiguration</key>
<string>Body</string>
<key>Margins</key>
<dict>
<key>Top</key>
<integer>5</integer>
</dict>
</dict>
ExecuteCommand
but using the Helper seems to prevent to correctly use pbcopy
. Thus, a CopyClipboard
action will be added in a future release to make this possible.Execute a support command
As we are talking about support, let’s now add a small useful feature to let your users contact the support by email. This will be done with an ExecuteCommand
command action which opens a link: mailto:support@acme.com
To execute this command, let’s add a Button
component with an ActionSet
. This is done by specifying the OnClick
key, which is a single ActionSet
. The button should be added at the bottom. We will also add a spacer with a Height
to 25 to ventilate the layout a bit. Here is what it looks like:

<dict>
<key>Type</key>
<string>Spacer</string>
</dict>
<dict>
<key>Type</key>
<string>Button</string>
<key>Text</key>
<string>Contact Support</string>
<key>Style</key>
<string>Simple</string>
<key>Alignment</key>
<string>Center</string>
<key>OnClick</key>
<dict>
<key>Type</key>
<string>Parallel</string>
<key>Actions</key>
<array>
<dict>
<key>Type</key>
<string>ExecuteCommand</string>
<key>Command</key>
<string>open mailto:support@acme.com</string>
</dict>
</array>
</dict>
</dict>
<dict>
<key>Type</key>
<string>Spacer</string>
<key>Height</key>
<integer>25</integer>
</dict>
If you reload the interface, you should see the button. And if you click on it, your default mail client should open with the given destination address.


This could be convenient for your users when they do not remember the correct support email address!
Summary
This was a huge tutorial! I hope you found it useful. If you have any questions, do not hesitate to reach me on the admin slack channel, or ask them in the Octory channel.
See you soon on your Octory journey!
Author
Alexis Bridoux
Octory Lead developer & Product owner
Email
Github