top of page
Search

You Down with TCC? -or- Providing Zero Touch Guided Setup for Camera, Microphone, and Screen Sharing

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





204 views0 comments

Commenti


bottom of page