Skip to main content

I found this script very useful and extracting the list of application usage in the Smart Group. 

 

#!/bin/bash

# This script uses the api to gather all serial numbers of a computer group and then gathers application usage data for that mac.
# each entry contains User data so, multiple lines will exist for each serial. one for each application.
# exported report is stored in a coma sperated spreadsheet.
# Please see end of script for terms
###
# Environment Specific Variables can be left blank if passing parameters from jamf
###
# hardcode here for testing

api_user=""
api_pass=""
jamf_url=""
# A Jamf Pro computer group (static or smart) that contains the client for which you want a report
group_name=''
# Number of days in report (using today as the end date...)
days=

# Check for passed parameters from jamf
if [ "$4" != "" ]; then
api_user=$4
fi

if [ "$5" != "" ]; then
api_pass=$5
fi

if [ "$6" != "" ]; then
jamf_url=$6
fi

if [ "$7" != "" ]; then
group_name=$7
fi

if [ "$8" != "" ]; then
days=$8
fi

# Location of jamfHelper
jamfHelper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"

# For testing only if you have a test JSS with a self signed SSL cert...
# Set security='' for normal use. Set security='--insecure' for testing
security=''
#security='--insecure'

# URL encode function
urlencode() {
old_lc_collate=$LC_COLLATE
LC_COLLATE=C
local length="${#1}"
for (( i = 0; i < length; i++ )); do
local c="${1:i:1}"
case $c in
[a-zA-Z0-9.~_-]) printf "$c" ;;
😉 printf '%%%02X' "'$c" ;;
esac
done
LC_COLLATE=$old_lc_collate
}

# URL encode the group name
group_name=$( urlencode "$group_name" )

# Pretty file name
filename="Application Usage Report - $days day "

# Today's date minus the days
start_date=$(date -j -v-${days}d +"%Y"-"%m"-"%d")

# Today's date
end_date=$(date +"%Y-%m-%d")

# Timestamp for filename
timestamp=$(date +"%Y-%m-%d-%H%M%S")

# Getting the Bearer token
jamfTokenCurl=$(curl -s -u "$api_user:$api_pass" "$jamf_url/api/v1/auth/token" -X POST)
jamfBearerToken=$(echo "$jamfTokenCurl" | jq -r .token)




# If you want, this dialog box will let the user know what's about to happen
buttonClicked=$("$jamfHelper" \\
-windowType utility \\
-title "Application Usage Report" \\
-heading "Please click OK to build the report." \\
-description "This process may take a few minutes depending on how many computers are in the report. We'll let you know when we're finished." \\
-button1 "Okay" \\
-defaultButton 1 \\
-icon "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertNoteIcon.icns")

# Build the API path to the smartgroup
api_path="JSSResource/computergroups/name/${group_name}"

# Get the currently logged in user for the output file path
loggedInUser=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name 😕 { print $3 }')
loggedInUserHome=$(dscl . read /Users/$loggedInUser NFSHomeDirectory | cut -d' ' -f2-)

# Getting Serial Numbers
echo "Getting a list of devices in Jamf Pro group. "
URL="${jamf_url}/${api_path}"
responseXML=$(curl ${security} --silent --show-error --write-out "\\n%{http_code}" \\
--header "Authorization: Bearer ${jamfBearerToken}" \\
--header "Accept: text/xml" "${URL}")
HTTP_Status=$(echo "$responseXML" | tail -1)
responseXML=$(echo "$responseXML" | sed \\$d)

echo "HTTP_Status : $HTTP_Status"
/bin/echo -n "HTTP Status Code: $HTTP_Status : "
if [[ $HTTP_Status = "200" ]]; then
echo '[ok]'
elif [[ $HTTP_Status = "400" ]]; then
echo "[error] Invalid API request"
exit 1
else
echo "[error] API could not return the group information. "
echo "$responseXML"
exit 1
fi

serialnumbers=($(echo "$responseXML" | xsltproc /tmp/stylesheet.xslt -))

echo "Serial number list : ${serialnumbers[@]}"
device_count=${#serialnumbers[@]}
touch /tmp/export.csv

# Write the header values of each column
echo "Full Name,Username,Position,Department,Building,Room,Email Address,Model,Serial Number,App Name,Version Number,Minutes in Forground,Minutes Open" > /tmp/export.csv

for serial in "${serialnumbers[@]}"; do
echo "Requesting details for device serial number : ${serial}"
api_device_path="JSSResource/computers/serialnumber/$serial"
location_data=$(curl ${security} --silent --show-error \\
--header "Authorization: Bearer ${jamfBearerToken}" \\
--header "Accept: text/xml" "$jamf_url/$api_device_path")
username=$(echo $location_data | /usr/bin/awk -F'<username>|</username>' '{print $2}')
realname=$(echo $location_data | /usr/bin/awk -F'<realname>|</realname>' '{print $2}')
email_address=$(echo $location_data | /usr/bin/awk -F'<email_address>|</email_address>' '{print $2}')
position=$(echo $location_data | /usr/bin/awk -F'<position>|</position>' '{print $2}')
department=$(echo $location_data | /usr/bin/awk -F'<department>|</department>' '{print $2}')
room=$(echo $location_data | /usr/bin/awk -F'<room>|</room>' '{print $2}')
building=$(echo $location_data | /usr/bin/awk -F'<building>|</building>' '{print $2}')
model=$(echo $location_data | /usr/bin/awk -F'<model>|</model>' '{print $2}')

echo "\\$username : \\"${username}\\""
echo "\\$realname : \\"${realname}\\""
echo "\\$email_address : \\"${email_address}\\""
echo "\\$position : \\"${position}\\""
echo "\\$department : \\"${department}\\""
echo "\\$room : \\"${room}\\""
echo "\\$building : \\"${building}\\""
echo "\\$model : \\"${model}\\""

api_path="JSSResource/computerapplicationusage/serialnumber/$serial/${start_date}_${end_date}"
xmlResponse=$(curl ${security} --silent --show-error \\
--header "Authorization: Bearer ${jamfBearerToken}" \\
--header "Accept: text/xml" "$jamf_url/$api_path")

cat << EOF > /tmp/stylesheet.xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="computer_application_usage/usage/apps/app">
<xsl:text>${realname//,}</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>${username//,}</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>${position//,}</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>${department//,}</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>${room//,}</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>${building//,}</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>${email_address//,}</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>${model//,}</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>$serial</xsl:text>
<xsl:text>,</xsl:text>
<xsl:value-of select="name"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="version"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="foreground"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="open"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EOF

usageData=$(echo "$xmlResponse" | xsltproc /tmp/stylesheet.xslt -)
if [[ -z "$usageData" ]]; then
echo "No application usage data found for serial number ${serial}."
else
echo "$usageData" >> /tmp/export.csv
fi
done

# Output file path
outputFilePath="${loggedInUserHome}/Desktop/${filename} - ${timestamp}.csv"

# Move the file to the user's desktop
mv /tmp/export.csv "$outputFilePath"

# Notify the user that the report is complete
"$jamfHelper" \\
-windowType utility \\
-title "Application Usage Report" \\
-heading "Report Complete" \\
-description "The application usage report has been saved to your Desktop." \\
-button1 "OK" \\
-defaultButton 1 \\
-icon "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertNoteIcon.icns"

# Invalidate the Bearer token
curl -s -H "Authorization: Bearer ${jamfBearerToken}" -X POST "${jamf_url}/api/v1/auth/invalidate-token"

echo "Done! Check ${outputFilePath} for the report."

Thanks for putting thr work in here. 

I'm attempting to use this and I'm getting a "Getting a list of devices in Jamf Pro group.
HTTP_Status : 401
HTTP Status Code: 401 : [error] API could not return the group information." error

Any suggestions? 


Thanks for putting thr work in here. 

I'm attempting to use this and I'm getting a "Getting a list of devices in Jamf Pro group.
HTTP_Status : 401
HTTP Status Code: 401 : [error] API could not return the group information." error

Any suggestions? 


it looks like it is using the classic API, so be sure to use the correct credentials when authenticating for the calls...


Reply