diff --git a/support/Jamf-Log-Grabber/Jamf Log Grabber b/support/Jamf-Log-Grabber/Jamf Log Grabber new file mode 100644 index 0000000..0770621 --- /dev/null +++ b/support/Jamf-Log-Grabber/Jamf Log Grabber @@ -0,0 +1,1032 @@ +#!/bin/bash + +#Jamf Log Grabber is designed to collect any logs associated with Jamf managed devices. + +#Custom arrays are now set for each individual type of log. It is recommended to include all as there are minor dependencies for some arrays. +#This new workflow allows for you to add arrays for additional in house apps like SUPER, DEPNOTIFY, Crowdstrike, or any other commonly used MacOS applications. + +#################################################################################################### +#This script is not intended for using to attach logs to a device record in Jamf Pro. Do not utilize such a workflow as it can lead to severe server performance issues +#################################################################################################### + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the JAMF Software, LLC nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY JAMF SOFTWARE, LLC "AS IS" AND ANY +# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL JAMF SOFTWARE, LLC BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#################################################################################################### + +#DATE FOR LOG FOLDER ZIP CREATION +current_date=$(date +"%Y-%m-%d") +#HARD CODED VARIABLES, DO NOT CHANGE +loggedInUser=$( echo "show State:/Users/ConsoleUser" | /usr/sbin/scutil | /usr/bin/awk '/Name :/ && ! /loginwindow/ { print $3 }' ) +#### Error check to make sure environment variables are correctly set as multiple recent reports in early 2024 had this broken +echo "HOME is $HOME" +if [[ $HOME == "" ]]; then + HOME="/Users/$loggedInUser" +fi +echo $HOME +#### End of HOME check +log_folder=$HOME/Desktop/"$loggedInUser"_"$current_date"_logs +results=$log_folder/Results.html +JSS=$log_folder/Client_Logs +security=$log_folder/Jamf_Security +connect=$log_folder/Connect +managed_preferences=$log_folder/Managed_Preferences +recon=$log_folder/Recon +self_service=$log_folder/Self_Service +Device_Compliance=$log_folder/Device_Compliance +JRA=$log_folder/JRA +App_Installers=$log_folder/App_Installers +jamfLog=$JSS/jamf.log + +reconleftovers=$(ls /Library/Application\ Support/JAMF/tmp/ 2> /dev/null) +runProtectDiagnostics="${10}" +#for testing +#runProtectDiagnostics="true" +protectDiagnostics=$(ls "$HOME/Desktop/" | grep "JamfProtectDiagnostics") + + + +#DATE AND TIME FOR RESULTS.TXT INFORMATION +#currenttime=$(date +"%D %T") +currenttime() { + date +"%D %T" +} +currenttime1=$(echo "$(currenttime)" | awk '{print $2}') + +#################################################################################################### +#You can add custom app log grabbing using the following rubric, just continue numbering the appnames or renaming them to fit your needs +#You can pass jamf script variables as part of a policy to get your additional apps + +#CustomApp1Name=$4 +CustomApp1Folder=$log_folder/$CustomApp1Name +#CustomApp1LogSource="$5" +#Now go down to CustomApp1Array and put in the files you want to grab +#CustomApp2Name="$6" +CustomApp2Folder=$log_folder/$CustomApp2Name +#CustomApp2LogSource="$7" +#Now go down to CustomApp2Array and put in the files you want to grab +#CustomApp3Name="$8" +CustomApp3Folder=$log_folder/$CustomApp3Name +#CustomApp3LogSource="$9" +#Now go down to CustomApp2Array and put in the files you want to grab +#################################################################################################### + +#Build a results file in HTML +buildHTMLResults() { + printf ' +
+ + + + + +%s
%s
' "red" "Install Logs not found" >> $results
+ fi
+ #CHECK FOR JAMF SYSTEM LOGS
+ if [ -e /var/log/system.log ]; then cp "/var/log/system.log" $JSS
+ else
+ printf '%s
' "red" "System Logs not found" >> $results
+ fi
+ #CHECK FOR JAMF SYSTEM LOGS
+ if [ -e /Library/Logs/MCXTools.log ]; then cp "/Library/Logs/MCXTools.log" $JSS
+ else
+ printf '%s
' "red" "System Logs not found" >> $results
+ fi
+ #FIND AND COPY JAMF SOFTWARE PLIST, THEN COPY AND CONVERT TO A READABLE FORMAT
+ #COPY DEBUG LOG
+ if [ -e /Library/Preferences/com.jamfsoftware.jamf.plist ]; then cp "/Library/Preferences/com.jamfsoftware.jamf.plist" "$JSS/com.jamfsoftware.jamf.plist" | plutil -convert xml1 "$JSS/com.jamfsoftware.jamf.plist"
+ else
+ printf '%s
' "red" "Jamf Software plist not found" >> $results
+ fi
+ mkdir -p $log_folder/Self_Service
+ #Checks what versions of self service are installed
+ if [ -e /Applications/Self\ Service.app ]; then
+ og="true"
+ else
+ og="false"
+ fi
+ if [[ -e /Applications/Self\ Service+.app ]]; then
+ ssPlus="true"
+ else
+ ssPlus="false"
+ fi
+ #fun little logic to update selfServiceStatus variable to pull logs according to reporting
+ if [ $og == "true" ] && [ $ssPlus == "true" ]; then
+ selfServiceStatus="ogPlus"
+ elif [ $og == "true" ] && [ $ssPlus == "false" ]; then
+ selfServiceStatus="ogSS"
+ elif [ $og == "false" ] && [ $ssPlus == "true" ]; then
+ selfServiceStatus="ssPlus"
+ else
+ selfServiceStatus="notInstalled"
+ fi
+ #everything put together to pull SS, SS+, or both apps logs
+ case $selfServiceStatus in
+ ogPlus)
+ printf '%s
' "white" "Self Service and Self Service+ are installed on this machine" >> $results
+ cp -r "$HOME/Library/Logs/JAMF/" $self_service
+ log show --style compact --predicate 'subsystem == "com.jamf.selfserviceplus"' --debug --info > $self_service/SelfServicePlus.log
+ ;;
+ ssplus)
+ printf '%s
' "white" "Self Service+ is installed on this machine" >> $results
+ log show --style compact --predicate 'subsystem == "com.jamf.selfserviceplus"' --debug --info > $self_service/SelfServicePlus.log
+ ;;
+ ogSS)
+ printf '%s
' "white" "Self Service is installed on this machine" >> $results
+ cp -r "$HOME/Library/Logs/JAMF/" $self_service
+ ;;
+ *)
+ printf '%s
' "white" "Self Service and Self Service+ are not installed on this machine" >> $results
+ esac
+ #Checks current MacOS Version against GDMF feed and flags if not a current release
+ currentMacOS=$(sw_vers --buildVersion)
+ checkIfSupportedOS=$(curl -s https://gdmf.apple.com/v2/pmv | grep -c $currentMacOS)
+ if [[ $checkIfSupportedOS == 1 ]]; then
+ printf '%s
' "white" "MacOS build $currentMacOS installed" >> $results
+ else
+ printf '%s
' "red" "MacOS build $currentMacOS installed. Unable to locate in GDMF feed." >> $results
+ fi
+ #Check if account is a Mobile Account and report if so
+ NETACCLIST=$(dscl . list /Users OriginalNodeName | awk '{print $1}' 2>/dev/null)
+ if [ "$NETACCLIST" == "" ]; then
+ printf '%s
' "white" "No mobile accounts on device." >> $results
+ else
+ printf '%s
' "red" "The following are mobile accounts:" >> $results
+ for account in $NETACCLIST; do
+ printf ' '$account'
' >> $results
+ done
+ fi
+ #############################################
+ # Software Update Stuff #
+ #############################################
+ #Show Secure Token enabled users
+ checkForSecureTokenUsers=$(fdesetup list | sed -E 's/,[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}//g')
+ if [[ $checkForSecureTokenUsers == "" ]]; then
+ printf '%s
' "red" "No Secure Token Users Found" >> $results
+ else
+ printf '%s
' "white" "Secure Token Users are: $checkForSecureTokenUsers" >> $results
+ fi
+ #Get info.plist that would be relayed to server for comparison
+ mkdir -p $JSS/SoftwareUpdates
+ if [[ -e /System/Library/PrivateFrameworks/RemoteManagement.framework/XPCServices/SoftwareUpdateSubscriber.xpc/Contents/Info.plist ]]; then
+ cp /System/Library/PrivateFrameworks/RemoteManagement.framework/XPCServices/SoftwareUpdateSubscriber.xpc/Contents/Info.plist $JSS/SoftwareUpdates/ClientInfo.plist
+ else
+ printf '%s
' "red" "Unable to find SoftwareUpdate info.plist" >> $results
+ fi
+ if [[ -e /private/var/db/softwareupdate/SoftwareUpdateDDMStatePersistence.plist ]]; then
+ cp /private/var/db/softwareupdate/SoftwareUpdateDDMStatePersistence.plist $JSS/SoftwareUpdates/DDM.plist
+ else
+ printf '%s
' "red" "Unable to find Software Update DDM plist" >> $results
+ fi
+ #############################################
+ # DDM Info #
+ #############################################
+ #Copy the current declaration info.plists for reference
+ DDMInfoPlists=$(ls /System/Library/PrivateFrameworks/RemoteManagement.framework/XPCServices/ | grep -Ewv 'SoftwareUpdateSubscriber.xpc')
+ mkdir -p $JSS/DDM
+ for file in $DDMInfoPlists; do
+ if [[ -e /System/Library/PrivateFrameworks/RemoteManagement.framework/XPCServices/$file/Contents/info.plist ]]; then
+ cp /System/Library/PrivateFrameworks/RemoteManagement.framework/XPCServices/$file/Contents/info.plist $JSS/DDM/"$file"_info.plist
+ fi
+ done
+ #Parse through all agents and deamons for any running keyword "jamf" and are not a part of standard Jamf applications. If none are found, they are still printed
+ AgentsAndDaemons=$(grep -r "jamf" /Users/$loggedInUser/Library/LaunchAgents/ /Library/LaunchAgents/ /Library/LaunchDaemons/ /System/Library/LaunchAgents/ /System/Library/LaunchDaemons/)
+ printf '%s
' "white" "A search for custom Agents and Daemons containing 'jamf' keywords has been ran and a copy of the results can be found in the Client Logs folder." >> $results
+ echo -e "$AgentsAndDaemons" > $JSS/AgentsAndDaemons.txt
+ #read blocked applications in jamf
+ sudo cat /Library/Application\ Support/JAMF/.jmf_settings.json > $JSS/restricted_software.json
+ #show installed profiles and output to xml. Use this to compare profile settings against actual settings in Managed Preferences Folder
+ sudo profiles show -output $JSS/profiles.xml stdout-xml
+ /usr/libexec/mdmclient AvailableOSUpdates > $JSS/SoftwareUpdates/AvailableOSUpdates.txt
+ /usr/libexec/mdmclient QueryDeviceInformation > $JSS/QueryDeviceInformation.txt
+ /usr/libexec/mdmclient QueryInstalledApps > $JSS/QueryDeviceApplications.txt
+ /usr/libexec/mdmclient DumpManagementStatus > $JSS/DumpManagementStatus.txt
+ launchctl dumpstate > $JSS/launchctl_dumpstate.txt
+ systemextensionsctl list > $JSS/system_extensions.txt
+ kextstat > $JSS/kextstat.txt
+ cp /Library/Receipts/InstallHistory.plist $JSS
+ if [ -e /Library/Logs/DiagnosticReports/ ]; then mkdir -p $JSS/DiagnosticReports && cp -r /Library/Logs/DiagnosticReports/ "$JSS/DiagnosticReports"
+ #SLEEP TO ALLOW COPY TO FINISH PROCESSING ALL FILES
+ sleep 5
+ else
+ printf '%s
' "red" "No crash reports found." >> $results
+ fi
+}
+
+
+####################################################################################################
+#Array for Jamf Connect Logs
+Connect() {
+ printf '%s
' "white" "Checking for Jamf Connect" >> $results
+ if [ -e /Library/Managed\ Preferences/com.jamf.connect.plist ]; then
+ printf '%s
' "white" "Jamf Connect installed, collecting Jamf Connect logs..." >>$results
+ connectInstalled="True"
+ mkdir -p $log_folder/Connect
+ #OUTPUT ALL HISTORICAL JAMF CONNECT LOGS, THIS WILL ALWAYS GENERATE A LOG FILE EVEN IF CONNECT IS NOT INSTALLED
+ log show --style compact --predicate 'subsystem == "com.jamf.connect"' --debug > $connect/JamfConnect.log
+ #OUTPUT ALL HISTORICAL JAMF CONNECT LOGIN LOGS
+ log show --style compact --predicate 'subsystem == "com.jamf.connect.login"' --debug > $connect/jamfconnect.login.log
+ kerblist=$("klist" 2>/dev/null)
+ if [[ "$kerblist" == "" ]];then
+ printf '%s
' "white" "-No Kerberos Ticket for Current Logged in User $loggedInUser" > $connect/klist_manuallyCollected.txt; else
+ echo $kerblist > $connect/klist_manuallyCollected.txt;
+ fi
+ #CHECK FOR JAMF CONNECT LOGIN LOGS AND PLIST, THEN COPY AND CONVERT TO A READABLE FORMAT
+ if [ -e /tmp/jamf_login.log ]; then cp "/tmp/jamf_login.log" $connect/jamf_login_tmp.log
+ else
+ printf '%s
' "orange" "-Jamf Login /tmp file not found%s
' "red" "-Jamf Connect Login plist not found" >> $results
+ fi
+
+ #CHECK FOR JAMF CONNECT LICENSE, THEN COPY AND CONVERT TO A READABLE FORMAT
+ printf '%s
' "red" "-A Jamf Connect State list was not found because no user is logged into Menu Bar" >> $results;
+ else cp $HOME/Library/Preferences/com.jamf.connect.state.plist "$connect/com.jamf.connect.state.plist" | plutil -convert xml1 $connect/com.jamf.connect.state.plist
+ fi
+
+ #CHECK FOR JAMF CONNECT MENU BAR PLIST, THEN COPY AND CONVERT TO A READABLE FORMAT
+ if [ -e /Library/Managed\ Preferences/com.jamf.connect.plist ]; then cp "/Library/Managed Preferences/com.jamf.connect.plist" "$connect/com.jamf.connect_managed.plist" | plutil -convert xml1 "$connect/com.jamf.connect_managed.plist" | log show --style compact --predicate 'subsystem == "com.jamf.connect"' --debug > "$connect/com.jamf.connect.log"
+ else
+ printf '%s
' "red" "Jamf Connect plist not found" >> $results
+ fi
+
+ #LIST AUTHCHANGER SETTIGNS
+ if [ -e /usr/local/bin/authchanger ]; then
+ /usr/local/bin/authchanger -print > "$connect/authchanger_manuallyCollected.txt"
+ :
+ else
+ printf '%s
' "white" "-No Authchanger settings found" >> $results
+ fi
+ else
+ printf '%s
' "white" "-No Jamf Connect Installed, doing nothing" >> $results
+ connectInstalled="False"
+ fi
+ #CHECK THE JAMF CONNECT LICENSE FILE AND LOOK FOR OTHER PROFILES THAT MAY HAVE A JAMF CONNECT LICENSE
+ connectLicenseInstalled=$(defaults read $managed_preferences/com.jamf.connect.plist LicenseFile)
+ connectLoginLicenseInstalled=$(defaults read $managed_preferences/com.jamf.connect.login.plist LicenseFile)
+ profilesWithConnectLicense=$(grep -A 1 "LicenseFile" $JSS/profiles.xml | awk -F'%s
' "red" "No License found for com.jamf.connect" >> $results
+ else
+ for i in $profilesWithConnectLicense; do
+ if [[ "$i" == $connectLicenseInstalled ]]; then
+ printf '%s
' "lime" "--Matching Profile found for installed Connect License (com.jamf.connect)." >> $results
+ if [[ $connectDateCompare -ge $connectLicenseExpiration ]]; then
+ printf '%s
' "red" "--Currently installed Jamf Connect License is expired." >> $results
+ else
+ printf '%s
' "lime" "--Currently installed Jamf Connect License is valid." >> $results
+ fi
+ else
+ printf '%s
' "red" "--Mismatch between installed Connect license found in com.jamf.connect.login plist and an installed profile. Search the profiles.xml file for this license string to see which one profile is attempting to install this license string:%s
' "white" "$i " >> $results
+ fi
+ done
+ fi
+ }
+
+ connectLoginLicenseArray() {
+ if [[ "$connectLoginLicenseInstalled" == "" ]]; then
+ printf '%s
' "red" "No License found for com.jamf.connect.login" >> $results
+ else
+ for i in $profilesWithConnectLicense; do
+ if [[ "$i" == $connectLoginLicenseInstalled ]]; then
+ printf '%s
' "lime" "--Matching Profile found for installed Connect License (com.jamf.connect.login)." >> $results
+ if [ $connectDateCompare -ge $connectLicenseExpiration ]; then
+ printf '%s
' "red" "--Currently installed Jamf Connect License is expired." >> $results
+ else
+ printf '%s
' "lime" "--Currently installed Jamf Connect License is valid." >> $results
+ fi
+ else
+ printf '%s
' "red" "--Mismatch between installed Connect license found in com.jamf.connect.login plist and an installed profile. Search the profiles.xml file for this license string to see which one profile is attempting to install this license string:%s
' "white" "$i" >> $results
+ fi
+ done
+ fi
+ }
+
+ connectLicenseFormatCheck() {
+ PI110629Check=$(awk '/string>PD94/ {count++} END {print count}' "$JSS/profiles.xml")
+ if [[ $PI110629Check -ge "1" ]]; then
+ printf '%s
' "red" "Key value assigned to LicenseFile uses string tag and appears to be affected by PI110629" >> $results
+ elif [[ $PI110629Check -le "0" ]]; then
+ printf '%s
' "lime" "Key value assigned to LicenseFile uses data tag and does not appear to be affected by PI110629" >> $results
+ fi
+ }
+ if [[ $connectInstalled = "True" ]]; then
+ connectLicenseArray
+ connectLoginLicenseArray
+ connectLicenseFormatCheck
+ fi
+}
+
+
+####################################################################################################
+#Array for Jamf Protect Logs
+Protect() {
+ #MAKE DIRECTORY FOR ALL JAMF SECURITY RELATED FILES
+ mkdir -p $log_folder/Jamf_Security
+ printf '%s
' "red" "Jamf Protect diagnostic files created. Please check Jamf_Security Folder for files." >> $results
+ else
+ echo "Protect Diagnostics found, copying to Jamf Log Grabber"
+ cp "$HOME/Desktop/$protectDiagnostics" "$security"
+ printf '%s
' "red" "Jamf Protect diagnostic files found. A 'protectctl diagnostics' command has been previously ran and the diagnostics folder was found on the desktop of this device." >> $results
+ fi
+ ;;
+ *)
+ if [[ $protectDiagnostics != "" ]]; then
+ echo "Protect Diagnostics found, copying to Jamf Log Grabber"
+ cp "$HOME/Desktop/$protectDiagnostics" "$security"
+ printf '%s
' "red" "Jamf Protect diagnostic files found. A 'protectctl diagnostics' command has been previously ran and the diagnostics folder was found on the desktop of this device." >> $results
+ else
+ echo "Protect Diagnostics disabled and no copy found on desktop"
+ fi
+ ;;
+ esac
+ #CHECK FOR JAMF PROTECT PLIST, THEN COPY AND CONVERT TO READABLE FORMAT
+ if [ -e /Library/Managed\ Preferences/com.jamf.protect.plist ]; then cp "/Library/Managed Preferences/com.jamf.protect.plist" "$security/com.jamf.protect.plist"
+ printf '%s
' "white" "Jamf Protect plist found" >> $results
+ plutil -convert xml1 "$security/com.jamf.protect.plist"
+ protectctl info --verbose > $security/jamfprotectinfo.log
+
+ else
+ printf '%s
' "orange" "Jamf Protect plist not found" >> $results
+ fi
+ #CHECK FOR JAMF TRUST PLIST, THEN COPY AND CONVERT TO READABLE FORMAT
+ if [ -e /Library/Managed\ Preferences/com.jamf.trust.plist ]; then cp "/Library/Managed Preferences/com.jamf.trust.plist" "$security/com.jamf.trust.plist"
+ plutil -convert xml1 "$security/com.jamf.trust.plist"
+ else
+ printf '%s
' "orange" "Jamf Trust plist not found" >> $results
+ fi
+}
+
+####################################################################################################
+#Array for Recon Troubleshoot
+Recon_Troubleshoot() {
+ mkdir -p $log_folder/Recon
+ #check for Jamf Recon leftovers
+ if [[ $reconleftovers == "" ]]; then
+ :
+ else
+ printf '%s
' "white" "Recon Troubleshoot found files in the /tmp directory that should not be there. A report of these files as well as next actions can be found in the Leftovers.txt file in the Recon Directory." >> $results
+ #copy all files in tmp folder to recon results folder
+ cp -r /Library/Application\ Support/Jamf/tmp/ $recon/
+ timefound=`grep -E -i '[0-9]+:[0-9]+' ${jamfLog} | awk '{print $4}' | tail -1`
+ echo $timefound > /dev/null
+ timeFoundNoSeconds=$(echo "${timefound:0:5}${timefound:8:3}")
+ currentTimeNoSeconds=$(echo "${currenttime1:0:5}${currenttime1:8:3}")
+ echo $timeFoundNoSeconds > /dev/null
+ echo $currentTimeNoSeconds > /dev/null
+ if [[ "$timeFoundNoSeconds" == "$currentTimeNoSeconds" ]]; then
+ printf '%s
' "Orange" "JLG appears to be running via policy, results in Recon directory may be inaccurate as files are stored there while policies are running." >> $results
+ else
+ printf '%s
' "Orange" "JLG appears to have been manually run. Results in Recon directory should be examined closely." >> $results
+ fi
+ fi
+}
+####################################################################################################
+#Array for MDM Communication Check
+#IF A DEVICE IS NOT COMMUNICATING WITH MDM, THIS WILL GIVE ITEMS TO LOOK INTO
+MDMCommunicationCheck() {
+ touch $log_folder/MDMCheck.txt
+ #WRITE TO LOGS WHAT WE ARE DOING NEXT
+ echo -e "Checking $loggedInUser's computer for MDM communication issues:" >> $log_folder/MDMCheck.txt
+ #CHECK MDM STATUS AND ADVISE IF IT IS COMMUNICATING
+ result=$(log show --style compact --predicate '(process CONTAINS "mdmclient")' --last 1d | grep "Unable to create MDM identity")
+ if [[ $result == '' ]]; then
+ echo -e "-MDM is communicating" >> $log_folder/MDMCheck.txt
+ else
+ echo -e "-MDM is broken" >> $log_folder/MDMCheck.txt
+ fi
+ #CHECK FOR THE MDM PROFILE TO BE INSTALLED
+ mdmProfile=$(/usr/libexec/mdmclient QueryInstalledProfiles | grep "00000000-0000-0000-A000-4A414D460003")
+ if [[ $mdmProfile == "" ]]; then
+ echo -e "-MDM Profile Not Installed" >> $log_folder/MDMCheck.txt
+ else
+ echo -e "-MDM Profile Installed" >> $log_folder/MDMCheck.txt
+ fi
+ #TELL THE STATUS OF THE MDM DAEMON
+ mdmDaemonStatus=$(/System/Library/PrivateFrameworks/ApplePushService.framework/apsctl status | grep -A 18 com.apple.aps.mdmclient.daemon.push.production | awk -F':' '/persistent connection status/ {print $NF}' | sed 's/^ *//g')
+ echo -e "-The MDM Daemon Status is:$mdmDaemonStatus" >> $log_folder/MDMCheck.txt
+ #WRITE THE APNS TOPIC TO THE RESULTS FILE IF IT EXISTS
+ profileTopic=$(system_profiler SPConfigurationProfileDataType | grep "Topic" | awk -F '"' '{ print $2 }');
+ if [ "$profileTopic" != "" ]; then
+ echo -e "-APNS Topic is: $profileTopic\n" >> $log_folder/MDMCheck.txt
+ else
+ echo -e "-No APNS Topic Found\n" >> $log_folder/MDMCheck.txt
+ fi
+}
+####################################################################################################
+#Array for Managed Preferences Collection
+Managed_Preferences_Array() {
+ #mkdir -p $log_folder/Managed\ Preferences
+ #CHECK FOR MANAGED PREFERENCE PLISTS, THEN COPY AND CONVERT THEM TO A READABLE FORMAT
+ if [ -e /Library/Managed\ Preferences/ ]; then cp -r /Library/Managed\ Preferences $managed_preferences
+ #SLEEP TO ALLOW COPY TO FINISH PROCESSING ALL FILES
+ sleep 5
+ #UNABLE TO CHECK FOLDER FOR WILDCARD PLIST LIKE *.PLIST
+ plutil -convert xml1 $managed_preferences/*.plist
+ plutil -convert xml1 $managed_preferences/$loggedInUser/*.plist
+ printf '%s
' "white" "Managed notifications found for the following applications:" >> $results
+ for app in $checkManagedNotifications; do
+ printf '\t%s\n
' "white" "- $app" >> $results
+ done
+ else
+ printf '%s
' "red" "No Managed Preferences plist files found" >> $results
+ fi
+}
+
+####################################################################################################
+#Array for Device Compliance
+DeviceCompliance() {
+ mkdir -p $log_folder/Device_Compliance
+ log show --debug --info --predicate 'subsystem CONTAINS "jamfAAD" OR subsystem BEGINSWITH "com.apple.AppSSO" OR subsystem BEGINSWITH "com.jamf.backgroundworkflows"' > $Device_Compliance/JamfConditionalAccess.log
+ if [ -e /Library/Logs/Microsoft/Intune/ ]; then cp /Library/Logs/Microsoft/Intune/*.log $Device_Compliance
+ else
+ printf '%s
' "orange" "Device Compliance system logs not found" >> $results
+ fi
+ if [ -e /$loggedInUser/Logs/Microsoft/Intune/ ]; then cp /Library/Logs/Microsoft/Intune/*.log $Device_Compliance
+ else
+ printf '%s
' "orange" "Device Compliance user logs not found" >> $results
+ fi
+
+}
+
+####################################################################################################
+#Array for Device Compliance
+Remote_Assist() {
+ JRA3Check=$(defaults read /Library/Application\ Support/JAMF/Remote\ Assist/jamfRemoteAssistConnectorUI.app/Contents/Info.plist CFBundleVersion 2>/dev/null)
+ printf '%s
' "white" "Jamf Remote Assist not installed, skipping version and network check." >> $results
+ else
+ #ADD JAMF Remote Assist Log Folder
+ printf '%s
' "white" "Jamf Remote Assist Version: $JRA3Check" >> $results
+ function createNetworkCheckTableJRA () {
+
+ /bin/cat << EOF >> "$results"
+
+ Network access to the following hostnames are required for using Jamf Remote Assist. These hostnames are a small sample as the Relay hostnames can increment up to 100 currently.
+${HOST_TEST_TABLES} +EOF + } + + + function CalculateHostInfoTablesJRA () { + echo "[step] Checking URLS" + lastCategory="zzzNone" # Some fake category so we recognize that the first host is the start of a new category + firstServer="yes" # Flag for the first host so we don't try to close the preceding table -- there won't be one. + HOST_TEST_TABLES='' # This is the var we will insert into the HTML + for SERVER in "${JRA_URL_ARRAY[@]}"; do + #split the record info fields + HOSTNAME=$(echo ${SERVER} | cut -d ',' -f1) + PORT=$(echo ${SERVER} | cut -d ',' -f2) + PROTOCOL=$(echo ${SERVER} | cut -d ',' -f3) + CATEGORY=$(echo ${SERVER} | cut -d ',' -f4) + # We have categories of hosts... enrollment, software update, etc. We'll put them in separate tables + # If the category for this host is different than the last one and is not blank... + if [[ "${lastCategory}" != "${CATEGORY}" ]] && [[ ! -z "${CATEGORY}" ]]; then + # If this is not the first server, close up the table from the previous category before moving on to the next. + echo "Starting Category : ${CATEGORY}" + if [[ "${firstServer}" != "yes" ]]; then + #We've already started the table html so no need to do it again. + HOST_TEST_TABLES+=" ${NL}" + fi + firstServer="no" + lastCategory="${CATEGORY}" + HOST_TEST_TABLES+="| HOSTNAME | Reverse DNS | IP Address | Port | Protocol | Accessible | SSL Error | Available | ' + #Test for SSL Inspection + if [[ ${PORT} == "80" ]]; then + #http traffic no ssl inspection issues + SSL_STATUS='N/A | ' + else + if [[ ${PROTOCOL} == "TCP" ]]; then + if [[ ${PROXY_HOST} == "" ]] && [[ ${PROXY_PORT} == "" ]];then + CERT_STATUS=$(echo | /usr/bin/openssl s_client -showcerts -connect "${HOSTNAME}:${PORT}" -servername "${HOSTNAME}" 2>/dev/null | /usr/bin/openssl x509 -noout -issuer ) + + else + CERT_STATUS=$(echo | /usr/bin/openssl s_client -showcerts -proxy "${PROXY_HOST}:${PROXY_PORT}" -connect "${HOSTNAME}:${PORT}" -servername "${HOSTNAME}" 2>/dev/null | /usr/bin/openssl x509 -noout -issuer) + fi + + if [[ ${CERT_STATUS} != *"Apple Inc"* ]] && [[ "${CERT_STATUS}" != *"Akamai Technologies"* ]] && [[ "${CERT_STATUS}" != *"Amazon"* ]] && [[ "${CERT_STATUS}" != *"DigiCert"* ]] && [[ "${CERT_STATUS}" != *"Microsoft"* ]] && [[ "${CERT_STATUS}" != *"COMODO"* ]] && [[ "${CERT_STATUS}" != *"QuoVadis"* ]]; then + + SSL_ISSUER=$(echo ${CERT_STATUS} | awk -F'O=|/OU' '{print $2}') + + if [[ ${HOSTNAME} == *"jcdsdownloads.services.jamfcloud.com" ]];then + SSL_STATUS='N/A | ' + else + SSL_STATUS="Unexpected Certificate: ${SSL_ISSUER} | " + fi + + else + + SSL_STATUS='Successful | ' + fi + else + SSL_STATUS='N/A | ' + fi + fi + else + # nc did not connect. There is no point in trying the SSL cert subject test. + AVAILBILITY_STATUS='Unavailable | ' + SSL_STATUS='Not checked | ' + fi + + # Done. Stick the row of info into the HTML var... + HOST_TEST_TABLES+="
|---|---|---|---|---|---|---|
| ${HOSTNAME} | ${REVERSE_DNS} | ${IP_ADDRESS} | ${PORT} | ${PROTOCOL} | ${AVAILBILITY_STATUS}${SSL_STATUS}
The following files were found in the app installer logs and show failed installations. If you are troubleshooting failed App Installers, please examine the following files.
+EOF + printf '%s
' "red" "Here's a list of failed app installer logs" >> $results
+ touch $App_Installers/commandUUID.txt
+ for file in $App_Installers/_Completed/*; do
+ failedInstall=$(defaults read "$file" InstallFailed 2> /dev/null)
+ commandUUID=$(defaults read "$file" InstallUUID 2> /dev/null)
+ if [[ $failedInstall == 1 ]]; then
+ printf '%s
' "yellow" "-$file" >> $results
+ echo "$commandUUID," >> $App_Installers/commandUUID.txt
+ fi
+ done
+ cat $App_Installers/commandUUID.txt | tr '\n' ' ' > $App_Installers/commandUUID.txt
+ else
+ /bin/cat << EOF >> "$results"
+ No failed App Installer files found.
+EOF + fi + + /bin/cat << EOF >> "$results" + +Network access to the following hostname is required for using Jamf's App Installers.
+${HOST_TEST_TABLES} +EOF + } + function CalculateHostInfoTablesAppInstallers () { + echo "[step] Checking URLS" + lastCategory="zzzNone" # Some fake category so we recognize that the first host is the start of a new category + firstServer="yes" # Flag for the first host so we don't try to close the preceding table -- there won't be one. + HOST_TEST_TABLES='' # This is the var we will insert into the HTML + for SERVER in "${APP_Installer_URL_ARRAY[@]}"; do + #split the record info fields + HOSTNAME=$(echo ${SERVER} | cut -d ',' -f1) + PORT=$(echo ${SERVER} | cut -d ',' -f2) + PROTOCOL=$(echo ${SERVER} | cut -d ',' -f3) + CATEGORY=$(echo ${SERVER} | cut -d ',' -f4) + # We have categories of hosts... enrollment, software update, etc. We'll put them in separate tables + # If the category for this host is different than the last one and is not blank... + if [[ "${lastCategory}" != "${CATEGORY}" ]] && [[ ! -z "${CATEGORY}" ]]; then + # If this is not the first server, close up the table from the previous category before moving on to the next. + echo "Starting Category : ${CATEGORY}" + if [[ "${firstServer}" != "yes" ]]; then + #We've already started the table html so no need to do it again. + HOST_TEST_TABLES+=" ${NL}" + fi + firstServer="no" + lastCategory="${CATEGORY}" + HOST_TEST_TABLES+="| HOSTNAME | Reverse DNS | IP Address | Port | Protocol | Accessible | SSL Error | Available | ' + #Test for SSL Inspection + if [[ ${PORT} == "80" ]]; then + #http traffic no ssl inspection issues + SSL_STATUS='N/A | ' + else + if [[ ${PROTOCOL} == "TCP" ]]; then + if [[ ${PROXY_HOST} == "" ]] && [[ ${PROXY_PORT} == "" ]];then + CERT_STATUS=$(echo | /usr/bin/openssl s_client -showcerts -connect "${HOSTNAME}:${PORT}" -servername "${HOSTNAME}" 2>/dev/null | /usr/bin/openssl x509 -noout -issuer ) + + else + CERT_STATUS=$(echo | /usr/bin/openssl s_client -showcerts -proxy "${PROXY_HOST}:${PROXY_PORT}" -connect "${HOSTNAME}:${PORT}" -servername "${HOSTNAME}" 2>/dev/null | /usr/bin/openssl x509 -noout -issuer) + fi + + if [[ ${CERT_STATUS} != *"Apple Inc"* ]] && [[ "${CERT_STATUS}" != *"Akamai Technologies"* ]] && [[ "${CERT_STATUS}" != *"Amazon"* ]] && [[ "${CERT_STATUS}" != *"DigiCert"* ]] && [[ "${CERT_STATUS}" != *"Microsoft"* ]] && [[ "${CERT_STATUS}" != *"COMODO"* ]] && [[ "${CERT_STATUS}" != *"QuoVadis"* ]]; then + + SSL_ISSUER=$(echo ${CERT_STATUS} | awk -F'O=|/OU' '{print $2}') + + if [[ ${HOSTNAME} == *"jcdsdownloads.services.jamfcloud.com" ]];then + SSL_STATUS='N/A | ' + else + SSL_STATUS="Unexpected Certificate: ${SSL_ISSUER} | " + fi + + else + + SSL_STATUS='Successful | ' + fi + else + SSL_STATUS='N/A | ' + fi + fi + else + # nc did not connect. There is no point in trying the SSL cert subject test. + AVAILBILITY_STATUS='Unavailable | ' + SSL_STATUS='Not checked | ' + fi + + # Done. Stick the row of info into the HTML var... + HOST_TEST_TABLES+="
|---|---|---|---|---|---|---|
| ${HOSTNAME} | ${REVERSE_DNS} | ${IP_ADDRESS} | ${PORT} | ${PROTOCOL} | ${AVAILBILITY_STATUS}${SSL_STATUS}
%s
' "orange" "App Installer Directory not found, device is not in scope for any App Installers or is not receiving the App Installer command from Jamf." >> $results
+ fi
+}
+
+####################################################################################################
+#Array for App Named in Dynamic Variables
+#When done, remove the associated array comment/# inside the Case command inside the logGrabberMasterArray
+CustomApp1Array() {
+ mkdir -p $log_folder/$CustomApp1Name
+ if [ -e $CustomApp1LogSource ] && [ $CustomApp1LogSource != "/" ]; then cp -r $CustomApp1LogSource $CustomApp1Folder
+ else
+ printf '%s
' "orange" "$CustomApp1Name does not have a log file available to grab or was set to an invalid path." >> $results
+ fi
+}
+
+####################################################################################################
+#Array for App Named in Dynamic Variables
+#When done, remove the associated array comment/# inside the Case command inside the logGrabberMasterArray
+CustomApp2Array() {
+ mkdir -p $log_folder/$CustomApp2Name
+ if [ -e $CustomApp2LogSource ] && [ $CustomApp2LogSource != "/" ]; then cp -r $CustomApp2LogSource $CustomApp2Folder
+ else
+ printf '%s
' "orange" "$CustomApp2Name does not have a log file available to grab or was set to an invalid path." >> $results
+ fi
+}
+
+####################################################################################################
+#Array for App Named in Dynamic Variables
+#When done, remove the associated array comment/# inside the Case command inside the logGrabberMasterArray
+CustomApp3Array() {
+ mkdir -p $log_folder/$CustomApp3Name
+ if [ -e $CustomApp3LogSource ] && [ $CustomApp3LogSource != "/" ]; then cp -r $CustomApp3LogSource $CustomApp3Folder
+ else
+ printf '%s
' "orange" "$CustomApp3Name does not have a log file available to grab or was set to an invalid path." >> $results
+ fi
+}
+
+####################################################################################################
+
+#Array for folder cleanup
+Cleanup() {
+ #IF AN ARRAY IS NOT SET TO RUN, REMOVE THE FOLDER NAME FOR IT BELOW TO AVOID ERRORS WITH THE CLEANUP FUNCTION AT THE END OF THE SCRIPT
+ cleanup=("Client_Logs Recon Self_Service Connect Jamf_Security Managed_Preferences Device_Compliance JRA App_Installers $CustomApp1Name $CustomApp2Name $CustomApp3Name")
+ #CLEANS OUT EMPTY FOLDERS TO AVOID CONFUSION
+ printf '%s
' "white" "The following folders contained no files and were removed:" >> $results
+ for emptyfolder in $cleanup
+ do
+ if [ -z "$(ls -A /$log_folder/$emptyfolder)" ]; then
+ printf '%s
' "yellow" "-$emptyfolder" >> $results
+ rm -r $log_folder/$emptyfolder
+ else
+ :
+ fi
+ done
+ printf '%s
' "white" "Completed Log Grabber on '$(currenttime)'" >> $results
+}
+
+####################################################################################################
+Zip_Folder() {
+ cd $HOME/Desktop
+ #NAME ZIPPED FOLDER WITH LOGGED IN USER
+ zip "$loggedInUser"_"$current_date"_logs.zip -r "$loggedInUser"_"$current_date"_logs
+ rm -r $log_folder
+}
+####################################################################################################
+# Set the Arrays you want to grab.
+# Default Array is logsToGrab=("Jamf" "Managed_Preferences" "Protect" "Connect" "Recon_Troubleshoot" "MDM_Communication_Check" "Device_Compliance" "App_Installers" "Remote_Assist" "$CustomApp1Name" "$CustomApp2Name" "$CustomApp3Name")
+
+declare -a logsToGrab=("Jamf" "Managed_Preferences" "Protect" "Connect" "Recon_Troubleshoot" "MDM_Communication_Check" "Device_Compliance" "App_Installers" "Remote_Assist" "$CustomApp1Name" "$CustomApp2Name" "$CustomApp3Name")
+
+####################################################################################################
+# Put it all together in the Master Array
+
+logGrabberMasterArray() {
+ #CLEAR OUT PREVIOUS RESULTS
+ if [ -e $log_folder ] ;then rm -r $log_folder
+ fi
+ #CREATE A FOLDER TO SAVE ALL LOGS
+ mkdir -p $log_folder
+ #CREATE A LOG FILE FOR SCRIPT AND SAVE TO LOGS DIRECTORY SO ADMINS CAN SEE WHAT LOGS WERE NOT GATHERED
+ touch $results
+ buildHTMLResults
+ #SET A TIME AND DATE STAMP FOR WHEN THE LOG GRABBER WAS RAN
+ printf '%s
' "white" "Log Grabber was started at '$(currenttime)'
+
+-Under Computers> Policies, create a new policy that contains the Jamf Log Grabber. If you want to get additional app logs, set the name and file path you want as seen below in the "Parameter Values" section
+
+
+-When the script is ran, there will be a folder with your app name and the logs inside as seen with DEPNotify in this example.
+
+
+
+-If you do not see your app folder, it is because the file was not found and the cleanup array removed the empty folder. You can confirm checking the cleanup section of results.html