Posted on 06-06-2019 07:41 PM
I know this feature request is out there about 100x and I have yet to see a script that does it. Most of these logs are available via the API. So I wrote a script that will let you input either a single serial number, or a CSV of serial numbers and then output a CSV with header for any of the following logs -
Screen Sharing Logs
Jamf Remote Logs
Computer Usage
Policy Logs
Completed MDM Commands
I only included those because they all leveraged the same API endpoint and it made scripting it easier. If you're handy with scripts, I'm sure you could build off this quite easliy.
I'm not a scripter - so I'm sure this isn't perfect. Use at your own risk, just posting it since it seems some people would benefit from it. Since it's 1 API call per device, it'll chew up some server resources, I wouldn't run it during peak hours if you have 10,000 devices.
I'm sure I'll miss linking this on a variety of feature requests, so feel free to link it on any that I missed.
#!/bin/bash
#
#
# Created by A.Hodgson
# Date: 2019/06/05
# Purpose: Output various computer logs that are only exportable through the API
#
#
######################################
##############################################################
#
# Log Export Options
#
##############################################################
#Screen Sharing
function screenSharing() # (serial)
{
#call the API
callAPI "$1"
#create a template file to parse the XML response
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_history/screen_sharing_logs/screen_sharing_log">
<xsl:text>$1</xsl:text>
<xsl:text>,</xsl:text>
<xsl:value-of select="date_time"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="status"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="details"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EOF
#parse the API response and output the desired values based on the template above
usageData=$( echo "$device_info" | xsltproc /tmp/stylesheet.xslt - )
#output results
outputData "$1" "$usageData" "$responseStatus"
}
#Jamf Remote
function jamfRemote() # (serial)
{
#call the API
callAPI "$1"
#create a template file to parse the XML response
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_history/casper_remote_logs/casper_remote_log">
<xsl:text>$1</xsl:text>
<xsl:text>,</xsl:text>
<xsl:value-of select="date_time"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="status"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EOF
#parse the API response and output the desired values based on the template above
usageData=$( echo "$device_info" | xsltproc /tmp/stylesheet.xslt - )
#output results
outputData "$1" "$usageData" "$responseStatus"
}
#Computer Usage
function computerUsage() # (serial)
{
#call the API
callAPI "$1"
#create a template file to parse the XML response
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_history/computer_usage_logs/usage_log">
<xsl:text>$1</xsl:text>
<xsl:text>,</xsl:text>
<xsl:value-of select="event"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="username"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="date_time"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EOF
#parse the API response and output the desired values based on the template above
usageData=$( echo "$device_info" | xsltproc /tmp/stylesheet.xslt - )
#output results
outputData "$1" "$usageData" "$responseStatus"
}
#Policy Logs
function policyLogs() # (serial)
{
#call the API
callAPI "$1"
#create a template file to parse the XML response
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_history/policy_logs/policy_log">
<xsl:text>$1</xsl:text>
<xsl:text>,</xsl:text>
<xsl:value-of select="policy_name"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="username"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="date_completed"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="status"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EOF
#parse the API response and output the desired values based on the template above
usageData=$( echo "$device_info" | xsltproc /tmp/stylesheet.xslt - )
#output results
outputData "$1" "$usageData" "$responseStatus"
}
#Completed MDM Commands
function mdmCommands() # (serial)
{
#call the API
callAPI "$1"
#create a template file to parse the XML response
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_history/commands/completed/command">
<xsl:text>$1</xsl:text>
<xsl:text>,</xsl:text>
<xsl:value-of select="name"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="completed"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EOF
#parse the API response and output the desired values based on the template above
usageData=$( echo "$device_info" | xsltproc /tmp/stylesheet.xslt - )
#output results
outputData "$1" "$usageData" "$responseStatus"
}
##############################################################
#
# Serial Option Functions
#
##############################################################
#CSV Option
function CSVreader() # (serialCSV)
{
#read serial CSV into array
IFS=$'
' read -d '' -r -a serialnumbers < $serial_CSV
length=${#serialnumbers[@]}
#process all serial numbers in array and call desired log option function
for ((i=0; i<$length;i++));
do
#trim the fat
serial=$(echo ${serialnumbers[i]} | sed 's/,//g' | sed 's/ //g'| tr -d '
')
#call appropriate function for log option
case $log_option in
1)
screenSharing "$serial"
;;
2)
jamfRemote "$serial"
;;
3)
computerUsage "$serial"
;;
4)
policyLogs "$serial"
;;
5)
mdmCommands "$serial"
;;
esac
done
}
#single Serial Option
function singleSerial() # (serial)
{
#call appropriate function for log option
case $log_option in
1)
screenSharing "$1"
;;
2)
jamfRemote "$1"
;;
3)
computerUsage "$1"
;;
4)
policyLogs "$1"
;;
5)
mdmCommands "$1"
;;
esac
}
##############################################################
#
# Processor Functions
#
##############################################################
function callAPI() # (serial)
{
#notify user in terminal of progress
echo "Processing serial number: $1"
#get device information from the API
device_info=$(curl --write-out "%{http_code}" -sku "Accept: text/xml" -u "$apiUser":"$apiPass" "$jssURL/JSSResource/computerhistory/serialnumber/$1")
responseStatus=${device_info: -3}
#trim response status off the information
device_info=$(sed 's/.{3}$//' <<< "$device_info")
}
function outputData() # (serial, API data, response status)
{
#write log entries to output file
if [ "$3" == "404" ]
then
echo "$1,Serial Number Not Found" >> "$file"
elif [ "$3" == "200" ]
then
if [ -z "$2" ]
then
echo "$1,No Data" >> "$file"
else
echo "$2" >> "$file"
fi
else
echo "Device not found in Jamf"
echo "Device not found in Jamf" >> "$file"
fi
}
##############################################################
#
# Main Function
#
##############################################################
#prompt user for variables
read -p "Enter your Jamf Pro URL (eg. https://example.jamfcloud.com or https://onprem.jamfserver.com:8443): " jssURL
read -p "Enter a username used for authentication to the API: " apiUser
#user password hidden from terminal
prompt="Please enter your API user password: "
while IFS= read -p "$prompt" -r -s -n 1 char
do
if [[ $char == $'' ]]; then
break
fi
if [[ $char == $'177' ]]; then
prompt=$' '
apiPass="${password%?}"
else
prompt='*'
apiPass+="$char"
fi
done
echo ""
#check the status of the API credentials before proceeding, exit script if fails
echo "Validating API credentials...."
apiCreds=$(curl --write-out %{http_code} --silent --output /dev/null -sku "Accept: text/xml" -u "$apiUser":"$apiPass" "$jssURL/JSSResource/activationcode")
if [ "$apiCreds" == "200" ]
then
echo "Validated, proceeding."
echo ""
else
echo "Credentials or URL not valid, please try again. Exiting script...."
exit 0
fi
#prompt user for log option and loop until we have a valid option
while true; do
echo "Log Report Options:"
echo "1 - Screen Sharing"
echo "2 - Jamf Remote"
echo "3 - Computer Usage"
echo "4 - Policy Logs"
echo "5 - Completed MDM Commands"
read -p "Please enter an option number: " log_option
case $log_option in
1)
break
;;
2)
break
;;
3)
break
;;
4)
break
;;
5)
break
;;
*)
echo "That is not a valid choice, try a number from the list."
echo ""
;;
esac
done
#file format and output
loggedInUser=$(/bin/ls -l /dev/console | /usr/bin/awk '{ print $3 }')
dt=$(date +"%a, %h %d, %Y %r")
#format based save file based on logging option
case $log_option in
1)
filename="Screen Sharing Logs - $dt.csv"
#set the file location
file="/Users/$loggedInUser/Downloads/$filename"
#write header values
echo "Serial Number,Date/Time,Status,Details" >> "$file"
;;
2)
filename="Jamf Remote Logs - $dt.csv"
#set the file location
file="/Users/$loggedInUser/Downloads/$filename"
#write header values
echo "Serial Number,Date/Time,Status" >> "$file"
;;
3)
filename="Computer Usage Logs - $dt.csv"
#set the file location
file="/Users/$loggedInUser/Downloads/$filename"
#write header values
echo "Serial Number,Event,Username,Date/Time" >> "$file"
;;
4)
filename="Computer Policy Logs - $dt.csv"
#set the file location
file="/Users/$loggedInUser/Downloads/$filename"
#write header values
echo "Serial Number,Policy Name,Username,Date Run,Status" >> "$file"
;;
5)
filename="Completed Computer MDM Commands - $dt.csv"
#set the file location
file="/Users/$loggedInUser/Downloads/$filename"
#write header values
echo "Serial Number,Command Name,Date/Time Completed" >> "$file"
;;
esac
echo ""
#prompt user for serial option and loop until we have a valid option
while true; do
echo "Serial Number Options:"
echo "1 - Single Serial Number"
echo "2 - CSV of Serial Numbers"
read -p "Please enter an option number: " serial_option
case $serial_option in
1)
break
;;
2)
break
;;
*)
echo "That is not a valid choice, try a number from the list."
echo ""
;;
esac
done
echo ""
#call necessary functions for a single serial number or process a CSV
case $serial_option in
1)
read -p "Please enter your serial number: " sn
singleSerial $sn
;;
2)
read -p "Drag and drop your CSV of Serial Numbers: " serial_CSV
CSVreader $serial_CSV
;;
esac
#output results file
echo ""
echo "Results stored to $file"
Posted on 04-28-2020 07:56 AM
Aaron,
I am new to API's but I am trying to learn them. How do you run this script? Do you put this as a script into the Jamf Web interface or use another program to make these calls to the API?
Posted on 06-09-2020 08:27 AM
@entrata One way to run this script is by saving it as an executable file (.sh) in TextEdit and then executing it in Terminal:
In the TextEdit menu click Format > Make Plain Text > Ok. Then click File > Save, and save it as a '.sh' file such as: logexporter.sh
Open Terminal and type in (without entering):
sudo sh
Then drag and drop the .sh file you just saved, from Finder into the Terminal window, and you should see the path added for you:
sudo sh /Users/username/Documents/Scripts/logexporter.sh
Hit Enter and the script should run and prompt you in Terminal
Posted on 06-18-2020 03:19 PM
@aaron.hodgson Thanks for sharing this script. It looks like almost exactly what we need.
I'm curious if you have discovered a way to extract the actions logged for a policy. We need to capture the detail of a specific policy running, not just the fact that it "Completed" or "Failed". I can see the actions in the GUI when I click on "Show", but I can't find a way using the API to return that additional information.
Is this possible at all?
Thanks!
-max
Posted on 12-11-2020 06:24 AM
Hi @aaron.hodgson,
Thanks for writing this script, really helpful.. do you know if it's possible to get it to show the results of a script that has been run?
For example, we have a script to check if a user is signed in to iCloud, it's great that I can see it has run, but it would be great if it could show the result as well.
Thanks!
Matt
Posted on 02-08-2021 11:41 AM
What permissions does the API user require? Also, this fails for me every time stating the account is not valid. I'm on Jamf Pro Cloud 10.26.
Posted on 04-19-2023 11:02 AM
Thank you to Google I found this script, it seems to hang when entering my API password, does anyone have a more recent script to try, Thanks !