Transparency, Consent, and Control (TCC) is the macOS system for providing applications access to system services. For obvious security reasons Apple does not allow admins (or attackers) to automatically provide applications to access user files, external drives, or hardware such as the camera and microphone.
Yet with zero touch provisioning our goal is to provide our people with the ability to do their jobs right out of the box. So how do we guide people through the process of enabling these services for applications like Zoom or Teams that they need in a remote world? The easy answer is to have documentation in some other channel: a setup PDF in Sharepoint, IT wiki, welcome email, etc. But all of those come after the computer is setup. Why not guide them through setting up Teams or Zoom for the first time and enabling their camera, microphone, and screen sharing as part of your Zero Touch workflow.
Diving Deeper
When we had a client that wanted to have Teams ready to go for video conferencing right away, we were easily able to make a video guiding them through the process. But how do we know if they did it yet? We needed a way to check before we moved on to the next step.
We just needed to figure out what plist file held this information. TO THE INTERNET! After a couple quick searches I was able to find that it wasn’t a plist file at all. It was a sqlite3 database. Or rather, two.
/Library/Application Support/com.apple.TCC/TCC.db
/Users/${USERNAME}/Library/Application Support/com.apple.TCC/TCC.db
After a few minutes poking around with sqlite3 I was able to find the table I needed (access) and determine which fields corresponded to the application (client), the section in Privacy settings (service), and if it was enabled (auth_value) or not. Applications (client) were in the standard domain format of: com.microsoft.teams
If the setting was enabled or not (auth_value) was simply a zero (0) or one (1) Determining which setting was which (service) took a second longer. Every single one of them began the same: kTCCService. And the list was different between the two files. But with a little bit of sorting, removing duplicate values, and stripping out the redundant text, it became a bit clearer:
% sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "select service from access" | sort | uniq | sed -e 's/kTCCService//'
Accessibility
DeveloperTool
ListenEvent
PostEvent
ScreenCapture
SystemPolicyAllFiles
% sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db "select service from access" | sort | uniq | sed -e 's/kTCCService//'
AddressBook
AppleEvents
BluetoothAlways
Calendar
Camera
FileProviderDomain
FocusStatus
Liverpool
Microphone
Photos
Reminders
SystemPolicyDesktopFolder
SystemPolicyDocumentsFolder
SystemPolicyDownloadsFolder
SystemPolicyNetworkVolumes
SystemPolicyRemovableVolumes
So of the three things I’m concerned about for video conferencing (Microphone, Camera, and ScreenCapture) like Zoom and Teams; screen sharing is tracked at the computer level and camera and microphone at the user level.
Another Tool in the Toolbox
Later that day I had created the following function to help with these workflows:
function checkAccess() {
## Sample usage
## micCheck=$(checkAccess 'microphone' 'teams')
## Input
local service=$1 ## microphone, camera, or screen
local app=$2 ## Substring or complete app domain
## Output
local result=''
## Determine which database we need
case ${service} in
## Screen recording is handled at the /Library layer 'screen')
db="/Library/Application Support/com.apple.TCC/TCC.db"
;;
## Microphone and camera are handled at the /User/Library layer
'microphone' | 'camera')
db="/Users/${USERNAME}/Library/Application Support/com.apple.TCC/TCC.db"
;;
esac
## Read the sqlite database
access=$(sqlite3 \
-separator ',' \
"${db}" \
"select client,service,auth_value from access where service like '%${service}%' and client like '%${APP}%'"; \
)
## Send back "On" or "Off"
result=$([[ ${access: -1} -gt 0 ]] && echo "On" || echo "Off")
echo "${result}"
}
In Series with DEPNotify
And from there I was able to create two workflows: One that guided the user through turning each on, one at a time using DEPNotify to display videos (not shown) for instruction.
#!/bin/zsh
###
### Functions
###
function checkAccess() {
## Input
local application=$1 ## Part of the domain path of the app (e.g. 'teams')
local service=$2 ## Which setting to check: 'microphone', 'camera', or 'screen'
## Output: returns "On" or "Off"
## Use local variable for path to sqlite database
local db
## Determine which database we need
case ${service} in
## Screen recording is handled at the /Library layer
screen*)
service='screen'
db="/Library/Application Support/com.apple.TCC/TCC.db"
;;
## Microphone and camera are handled at the /User/Library layer
'microphone' | 'camera')
db="/Users/${USERNAME}/Library/Application Support/com.apple.TCC/TCC.db"
;;
esac
## Read the sqlite database
access=$(sqlite3 \
-separator ',' \
"${db}" \
"select client,service,auth_value from access where service like '%${service}%' and client like '%${application}%'"; \
)
lookupSuccess=$?
if [[ ${lookupSuccess} ]]; then
## Send back "On" or "Off"
[[ ${access: -1} -gt 0 ]] && echo "On" || echo "Off"
fi
}
function DEPNotify {
local NotifyCommand=$1
echo "$NotifyCommand" >> /var/tmp/depnotify.log
}
###
### Main
###
APPCODE="Teams"
## Check if DEPNotify is already running and launch if not
echo "" > /var/tmp/depnotify.log
if [[ ! $(pgrep DEPNotify) ]]; then
dnLoc=$( find /Applications -maxdepth 2 -type d -iname "*DEP*.app" )
"${dnLoc}/Contents/MacOS/DEPNotify" 2>/dev/null &
fi
## Capture DEPNotify PID
echo $(pgrep DEPNotify) > /tmp/depnotify.pid
## Initial Setup
DEPNotify "Command: MainTitle: Configure Microsoft Teams"
DEPNotify "Command: MainText: Please provide access to the following items for Microsoft Teams."
## Camera
DEPNotify "Status: Enable Camera"
while [[ $(checkAccess "${APPCODE}" 'camera') == "Off" ]]; do
sleep 3
done
## Microphone
DEPNotify "Status: Enable Microphone"
while [[ $(checkAccess "${APPCODE}" 'microphone') == "Off" ]]; do
sleep 3
done
## Screen Sharing
DEPNotify "Status: Enable Screen Sharing"
while [[ $(checkAccess "${APPCODE}" 'screen') == "Off" ]]; do
sleep 3
done
DEPNotify "Command: Quit"
In Parallel with swiftDialog
But since DEPNotify hasn’t been updated in two years, we are starting to move towards swiftDialog. While swiftDialog doesn't (yet) have the ability to play videos in a running window, it does have the ability to display a list of items and update their status as you go.
So we also created a version that can handle the permissions being turned on in any order and exit once all three have been enabled.
#!/bin/zsh
###
### Functions
###
function checkAccess() {
## Input
local application=$1 ## Part of the domain path of the app (e.g. 'teams')
local service=$2 ## Which setting to check: 'microphone', 'camera', or 'screen'
## Output: returns "On" or "Off"
## Use local variable for path to sqlite database
local db
## Determine which database we need
case ${service} in
## Screen recording is handled at the /Library layer
screen*)
service='screen'
db="/Library/Application Support/com.apple.TCC/TCC.db"
;;
## Microphone and camera are handled at the /User/Library layer
'microphone' | 'camera')
db="/Users/${USERNAME}/Library/Application Support/com.apple.TCC/TCC.db"
;;
esac
## Read the sqlite database
access=$(sqlite3 \
-separator ',' \
"${db}" \
"select client,service,auth_value from access where service like '%${service}%' and client like '%${application}%'"; \
)
lookupSuccess=$?
if [[ ${lookupSuccess} ]]; then
## Send back "On" or "Off"
[[ ${access: -1} -gt 0 ]] && echo "On" || echo "Off"
fi
}
function dialog() {
local theCommand=$1
echo "${theCommand}" >> /var/tmp/dialog.log
}
###
### Main
###
APPTAG="Teams"
## Conditions to check before completion
declare -A MONITORS
MONITORS=(
['camera']='Yes' # Camera must be enbled
['microphone']='Yes' # Microphone must be enabled
['screen-sharing']='Yes' # Screen sharing must be enabled
)
## Launch swiftDialog
dialogCommand=" \
--title 'Configure Microsoft Teams' \
--message 'In System Preferences, please allow the following items for Microsoft Teams.' \
--button1text "Waiting..." \
--button1disabled \
--listitem 'Enable Camera' \
--listitem 'Enable Microphone' \
--listitem 'Enable Screen Sharing' \
"
eval "/usr/local/bin/dialog ${dialogCommand[*]}" &
pid=$!
echo ${pid} > /tmp/monitor.pid
done='No'
while [[ ${done} == "No" ]]; do
done="Yes"
for monitor in ${(k)MONITORS}; do
state="pending"
MONITORS[${monitor}]=$(checkAccess "${APPTAG}" "$monitor")
[[ ${MONITORS[${monitor}]} == "On" ]] && state="success" || done='No'
monitor="Enable ${(C)monitor//-/ }"
dialog "listitem: title: ${(C)monitor//-/ }, status: ${state}"
done
## If swiftDialog has stopped, we stop too
[[ $(pgrep -F /tmp/monitor.pid) ]] || done="Yes"
sleep 1
done
Comments