This is the follow up resources for our presentation "You Down With TCC" at the 2023 Penn State Mac Admins Conference. These files can be used during the presentation to follow along, or after the presentation to implement this workflow in your environment.
Workshop Files
In order to setup the workflow shown in this presentation, you will need the following files:
You will also need to download the DEPNotify package, which is located here: https://files.nomad.menu/DEPNotify.pkg
GitHub Link: https://github.com/Rocketman-Tech/App-Setup-Helper
What is TCC?
TCC was introduced with macOS Mojave and stands for Transparency, Consent, and Control. This allows the user to have control over what applications can access, including Location Services, Bluetooth, and Full Disk Access, to name a few. However, the three we are focusing on for this presentation is camera, microphone, and screen recording, since these three cannot be controlled via an MDM.
Issues with managing TCC
Camera, Microphone, and Screen Recording are protected by SIP
There is no way to programmatically enable this for users
You can, however, guide users through this process
Guiding the User
Script with loops detecting if user has completed the action
Videos guiding users through steps using DEPNotify
Launching the application and/or System Settings
Example of the Workflow
Under the Hood
There are two places where information is stored about the computer's TCC policies:
/Library/Application Support/com.apple.TCC/TCC.db
Screen Recording
~/Library/Application Support/com.apple.TCC/TCC.db
Camera
Microphone
The following commands explain how to read from the TCC database.
Show the TCC Database
sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select * from access"
Show just the services in the TCC Database
sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select distinct service from access"
Show just the applications that we have access information for
sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select distinct client from access"
Isolate the Client, Service, and Auth Value
sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select client, service, auth_value from access"
Just show the Client, Service and Auth Value for Screen Recording
service='screen'
sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select client, service, auth_value from access where service like '%${service}%'"
Just show the Client, Service and Auth Value for Screen Recording and Zoom
service='screen'
client='zoom'
sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select client, service, auth_value from access where service like '%${service}%' and client like '%${client}%'"
Lets see what the TCC Database in the User's Library looks like
sqlite3 -header "/Users/schasta/Library/Application Support/com.apple.TCC/TCC.db" "select * from access"
Lets look at the Zoom application specifically
service='microphone'
client='zoom'
sqlite3 -header "/Users/schasta/Library/Application Support/com.apple.TCC/TCC.db" "select client, service, auth_value from access where service like '%${service}%' and client like '%${client}%'"
Taking a look at our functions
We utilize functions pretty heavily in all of our scripts. Lets take a look at them one at a time to break down what's happening:
checkAccess function
The first variable defined is the application (EG 'zoom')
The second variable defined is the service (EG 'microphone')
Depending on the service, defines which TCC.db it's accessing
Reads the database using the first two variables defined
Returns the value "On" or "Off"
function checkAccess() {
application=$1
service=$2
## 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/${CONFIG[currentuser]}/Library/Application Support/com.apple.TCC/TCC.db"
;;
esac
query=""
## 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
}
countdown function
Creates a countdown timer so that when we run a while loop to wait for the user to do something it doesn't run indefinitely.
The time it takes to timeout is dictated by the timeout function defined by a parameter
function countdown() { #d0919#
## timeRemaining=$(countdown "${startTime}" "${CONFIG[timeout]}")
## Input
local startTime=$1 ## The UNIX time (in seconds) the clock started
## Ex. startTime=$(date +%s)
local timeout=$2 ## How many seconds are we waiting
## Output
local remaining=0 ## Number of seconds left in the countdown
remaining=$((${startTime}+${timeout}-$(date +%s)))
echo ${remaining}
}
waitForAccess function
The first variable defined is the Access Type, which is also used for the Service in the checkAccess function
Creates a while loop that utilizes the checkAccess function and the countdown function to check if the user has completed the action
function waitForAccess() {
## Input
local accessType=$1
## Output
local statusCode=1 ## Let's assume there was a problem
## Initial states
local timeRemaining=${CONFIG[timeout]}
local accessEnabled="Off"
## Start the clock
local startTime=$(date +%s)
while [[ ${accessEnabled} == "Off" && ${timeRemaining} -gt 0 ]]; do
sleep 1
accessEnabled=$(checkAccess "${CONFIG[appcode]}" "${accessType}")
timeRemaining=$(countdown ${startTime} ${CONFIG[timeout]})
done
## Did we get here because it turned on or timeout
if [[ ${accessEnabled} == "On" ]]; then
statusCode=0
else
dumpLog
cleanUp "ERROR: Timeout while enabling ${accessType}"
fi
return ${statusCode}
}
Basic way this is run
With these functions in place, you can begin to see how this is run using a small amount of code:
## Camera
DEPNotify "Status: Enable Camera"
DEPNotify "Command: MainText: Please enable the Camera for Zoom to continue."
waitForAccess "camera"
## Microphone
DEPNotify "Command: MainText: Please enable the Microphone for Zoom to continue."
DEPNotify "Status: Enable Microphone"
waitForAccess "microphone"
## Screen Sharing
DEPNotify "Command: MainText: Please enable Screen Sharing for Zoom to continue."
DEPNotify "Status: Enable Screen Sharing"
waitForAccess "screen"
GitHub
We'd love to hear your feedback and help improve this workflow! We will be updating this workflow over the next couple of months to support Swift Dialogue and allow for additional configuration through attributes. We'd also like to create more videos for more applications to give admins as many tools as possible. Join our GitHub repo and help add to this workflow! https://github.com/Rocketman-Tech/App-Setup-Helper
Do you have any updates on this workflow using swiftDialog?
What would need to be changed to make the zsh script work with Addigy? I noticed that it currently has jamf requirements, which makes sense. But our org is on Addigy...