Code42 CrashPlan Extension Attributes v8.2 + 2020

Rhio
New Contributor III

Working on revamping all the EAs for Code42 CrashPlan to be compatible in 2020 version 8.2+

@jamf is there a way we can get the CrashPlan (now Code42) Templates all updated with these revised EAs?

I will be updating this post in the next 24-48 hours as I get them worked through the rest of the way. If anyone has ideas on how to simplify the code down short, I'm all ears :) I prefer to do Python looking at the JSON returned values to get the correct Key-value vs. grep/awk/sed (bs) as it makes the code more easily read and it ensures that even if the location of the keypair gets moved, we're still okay.

Remember to keep your Code42 account limited to the lowest Roles possible to do its required job, which in this case is Cross Org Security Viewer:
c5796c29f6e544b9b66e01b76f032252

.
.
.
.

To download the EAs: Github - Code42 EAs

.
.
.
LAST CONNECTED

#!/bin/sh
####################################################
### INFO ###
#Revamped by: Rhio
#Revamped on: August, 4th, 2020
#Display Name: Code42 - Last Connected
#Description: This attribute displays the last time Code42 established a connection with the server. 
#Data Type: Date
#Input Type: Script
####################################################




####################################################
### VARIABLES ###
#use the server address utilized to log into a fresh client installed manual deployment
CP_ServerAddress="central.crashplan.com"

#This port should work for everyone, it may be different if you have an on-prem install and have altered your ports from the default ports
CP_ServerPort="4285"


#Configure an API account with the minimum roles possible, for cloud customers, this would be Cross Org Security Viewer and must be a LOCAL account within the console, this will allow READ access ONLY. If you are a cloud customer, this is also required to be a full email address.
CP_AdminUsername="yourAPIacct@yourorg.com"

#Configure to your API account PW. If desired, you could encrypt this with base 64 or a keypair stored in a separate file or config profile elsewhere on the machine to avoid having it in plaintext. Since you should have limited the rights of this account, the vulnerability will be reduced.
CP_AdminPassword=""

####################################################




####################################################
### LOGIC ###

if [ "$CP_ServerAddress" == "" ] || [ "$CP_ServerPort" == "" ] || [ "$CP_AdminUsername" == "" ] || [ "$CP_AdminPassword" == "" ];then

    echo "Please ensure all variables are set in the extension attribute script."

else

    if [ -f /Library/Application Support/CrashPlan/.identity ];then

        #Checks the Unique Code42 Identifier to the currently running backup; the directory may need to be updated if they ever change the name of their cached settings directory
        GUID=`/bin/cat /Library/Application Support/CrashPlan/.identity | grep guid | sed s/guid=//g`

        #Queries API of your Code42 instance then uses Python to parse the JSON that's returned for the nested key pair (under "data") of lastConnected
        value=`/usr/bin/curl -u "$CP_AdminUsername":"$CP_AdminPassword" -k "https://"$CP_ServerAddress":"$CP_ServerPort"/api/Computer/"$GUID"?idType=guid" | python -c 'import json,sys;print json.load(sys.stdin)["data"]["lastConnected"]'`

        #Converts the time format from the resulting JSON to the Jamf Smart Group compatible format 
        result=`/bin/date -j -f "%Y-%m-%dT%H:%M:%S" "$value" "+%Y-%m-%d %H:%M:%S"`

        echo "<result>$result</result>"

    else

        echo "<result>Not installed</result>"

    fi
fi
####################################################

.
.
.
ALERT STATUS
.

#!/bin/sh
####################################################
### INFO ####Revamped by: Rhio
#Revamped on: August, 8th, 2020
#Display Name: Code42 - Alert Status
#Description: This attribute displays the Alert Status of Code42. 
#Data Type: String
#Input Type: Script
####################################################




####################################################
### VARIABLES ###

#use the server address utilized to log into a fresh client installed manual deployment
CP_ServerAddress="central.crashplan.com"

#This port should work for everyone, it may be different if you have an on-prem install and have altered your ports from the default ports
CP_ServerPort="4285"


#Configure an API account with the minimum roles possible, for cloud customers, this would be an Org Admin or All Org Admin, and must be a LOCAL account within the console. If you are a cloud customer, this is also required to be a full email address.
CP_AdminUsername="yourAPIacct@yourorg.com"

#If desired you could encrypt this with base 64 or a keypair stored in a separate file or config profile elsewhere on the machine to avoid having it in plaintext. Since you should have limited the rights of this account, the vulnerability should be reduced.
CP_AdminPassword=""

####################################################




####################################################
### LOGIC ###

if [ "$CP_ServerAddress" == "" ] || [ "$CP_ServerPort" == "" ] || [ "$CP_AdminUsername" == "" ] || [ "$CP_AdminPassword" == "" ];then

    echo "Please ensure all variables are set in the extension attribute script."

else

    if [ -f /Library/Application Support/CrashPlan/.identity ];then

        #Checks the Unique Code42 Identifier to the currently running backup; the directory may need to be updated if they ever change the name of their cached settings directory
        GUID=`/bin/cat /Library/Application Support/CrashPlan/.identity | grep guid | sed s/guid=//g`

        #Queries API of your Code42 instance then uses Python to parse the JSON that's returned for the nested key pair that's in an array (under "data") of alertStates
        value=`/usr/bin/curl -u "$CP_AdminUsername":"$CP_AdminPassword" -k "https://"$CP_ServerAddress":"$CP_ServerPort"/api/Computer/"$GUID"?idType=guid" | python -c 'import json,sys;print json.load(sys.stdin)["data"]["alertStates"]'`

        #Filters off unwanted parts of the value due to the results being in an array
        result=`echo "$value" | sed 's/...(.*)/1/' | sed 's/(.*)../1/'`

        echo "<result>$result</result>"

    else

        echo "<result>Not installed</result>"

    fi
fi
####################################################

.
.
.
BACKUP COMPLETE PERCENTAGE
.

#!/bin/sh

####################################################
### INFO ###
#Found at: https://github.com/RHI0/Code42-EAs
#Revamped by: Rhio
#Revamped on: August, 4th, 2020
#Display Name: Code42 - Percent Complete
#Description: This attribute displays the last percentage completion of the first archive of the GUID. 
#Data Type: Integer
#Input Type: Script
####################################################




####################################################
### VARIABLES ###

#use the server address utilized to log into a fresh client installed manual deployment
CP_ServerAddress="central.crashplan.com"

#This port should work for everyone, it may be different if you have an on-prem install and have altered your ports from the default ports
CP_ServerPort="4285"

#Configure an API account with the minimum roles possible, for cloud customers, this would be an Org Admin or All Org Admin, and must be a LOCAL account within the console. If you are a cloud customer, this is also required to be a full email address.
CP_AdminUsername="yourAPIacct@yourorg.com"

#If desired you could encrypt this with base 64 or a keypair stored in a separate file or config profile elsewhere on the machine to avoid having it in plaintext. Since you should have limited the rights of this account, the vulnerability should be reduced.
CP_AdminPassword=""

####################################################




####################################################
### LOGIC ###

if [ "$CP_ServerAddress" == "" ] || [ "$CP_ServerPort" == "" ] || [ "$CP_AdminUsername" == "" ] || [ "$CP_AdminPassword" == "" ];then

    echo "Please ensure all variables are set in the extension attribute script."

else

    if [ -f /Library/Application Support/CrashPlan/.identity ];then

        #Checks the Unique Code42 Identifier to the currently running backup; the directory may need to be updated if they ever change the name of their cached settings directory
        GUID=`/bin/cat /Library/Application Support/CrashPlan/.identity | grep guid | sed s/guid=//g`

        #Queries API of your Code42 instance then uses Python to parse the JSON that's returned for the nested arrayed key pair (under "data" > "backupUsage") of percentComplete for the first archive. If a client is backing up to more than one physical location you will need to add a loop to iterate over the array
        value=`/usr/bin/curl -u "$CP_AdminUsername":"$CP_AdminPassword" -k "https://"$CP_ServerAddress":"$CP_ServerPort"/api/Computer/"$GUID"?idType=guid&incBackupUsage=true" | python -c 'import json,sys;print json.load(sys.stdin)["data"]["backupUsage"][0]["percentComplete"]'`

        echo "<result>$value</result>"

    else

        echo "<result>Not installed</result>"

    fi
fi
####################################################
10 REPLIES 10

bpavlov
Honored Contributor

My coworker built these out and it's what we use. No API access required. Though some of the comments and variables reference CrashPlan instead of Code42, they functionality of the scripts are meant to work with Code42 8.2+

Code42 Backup Status:

#!/bin/zsh

# This is an extension attribute script to report CrashPlan status
# There are 4 possible values with definitions below:
# On, Logged In: ${CrashPlanUser}:
#   Application is running and user is logged in
# On, Not Logged In:
#   Application is running but user is not logged in
# Off:
#   Application is not running
# Not Installed:
#   CrashPlan Is Not Installed

# CrashPlan Application Path
CrashPlanPath="/Applications/Code42.app"

# Check if CrashPlan is installed before anything else
if [[ ! -d "$CrashPlanPath" ]]; then
    echo "<result>Not Installed</result>"
    exit 0
fi

# Sets value of CrashPlan Application Log
CrashPlanAppLog="/Library/Logs/CrashPlan/app.log"

#If value is 0, no user is logged in to CrashPlan
CrashPlanLoggedIn="$(/usr/bin/awk '/USER/{getline; gsub(",",""); print $1; exit }' $CrashPlanAppLog)"

# Gets CrashPlan username
CrashPlanUser="$(/usr/bin/awk '/USER/{getline; gsub(",",""); print $2; exit }' $CrashPlanAppLog)"

# Checks if Code42 Client is Running
CrashPlanRunning="$(/usr/bin/pgrep "Code42Service")"


# Reports CrashPlan Status and Username
if [[ -n "${CrashPlanRunning}" ]]; then
    CrashPlanStatus="On, "
    if [[ "${CrashPlanLoggedIn}" -eq 0 ]]
    then
        CrashPlanStatus+="Not Logged In"
    else
        CrashPlanStatus+="Logged In: ${CrashPlanUser}"
    fi
else
    CrashPlanStatus="Off"
fi

echo "<result>${CrashPlanStatus}</result>"

Code42 Last Backup Date:

#!/bin/zsh

# An extension attribute script to report the last completed backup date by CrashPlan
# Defaults to "1901-01-01 00:00:01" if backup has never completed

# Sets location of all CrashPlan History Logs
CrashPlanLogs=$(/bin/ls /Library/Logs/CrashPlan/history.log*)

# Runs a loop to check CrashPlan history logs for the date and time of most recent Completed Backup
# If found, converts the date format, and reports it. 
# If no completed backup is found, it goes to a previous log.
# If no completed backup is found, it defaults to 1901-01-01 00:00:01

for LINE in $CrashPlanLogs; do
    CrashPlanDate=$(/usr/bin/awk '/Completed backup/{print $2, $3}' $LINE | /usr/bin/tail -n1)

    if [ -z "$CrashPlanDate" ]; then
        CrashPlanResult="1901-01-01 00:00:01"
        continue 
    else
        CrashPlanResult=$(/bin/date -j -f "%m/%d/%y %l:%M%p" "$CrashPlanDate" "+%Y-%m-%d %k:%M:%S")
        break
    fi
done

echo "<result>${CrashPlanResult}</result>"

Code42 Backup Set Name:

#!/bin/zsh

# A script to report CrashPlan Backup Set Name
# The extension attribute will result in 3 potential values depending on what's found:
# No Backup Found:
#   No backup found.
# <Computer Name as reported in CrashPlan>:
#   The computer name that CrashPlan reports in the web console.
# Not Installed:
#   CrashPlan is not installed.

# CrashPlan Application Path
CrashPlanPath="/Applications/Code42.app"

# Check if CrashPlan is installed before anything else
if [[ ! -d "$CrashPlanPath" ]]; then
    echo "<result>Not Installed</result>"
    exit 0
fi

# Sets value of CrashPlan Application Log
CrashPlanAppLog="/Library/Logs/CrashPlan/app.log"

# Checks app.log for Backup Set Name and reports it
if [ -f "$CrashPlanAppLog" ]; then
    CrashPlanBackupName="$(/usr/bin/awk -F,  '/COMPUTERS/{getline; gsub(/^[ 	]+|[ 	]+$/,"",$2);  print $2}' "$CrashPlanAppLog")"

    if [ "$CrashPlanBackupName" = "" ]; then
        CrashPlanBackupName="No Backup Name Found"
    fi
else
    CrashPlanBackupName="No Backup Name Found"
fi

echo "<result>${CrashPlanBackupName}</result>"

Rhio
New Contributor III

I like those @bpavlov , unfortuately there are things you can't parse from the Logs or the local machine well (like percentage complete vs just a full backup). Also since the data is housed and accessed through the Console to the Storage Nodes I view it as the "Source of Truth" vs the client which is why I've chosen to go this route, but I do like your EA especially for checking the Service and will probably use that if it's okay with you!

bpavlov
Honored Contributor

@Rhio You got me curious so I just checked and the percentage complete of the backup is located in the following log: /Library/Logs/CrashPlan/app.log
It's under the section that looks something like:

DESTINATIONS
43, PROe Cloud, US, userId=1, private=hxa-iad.crashplan.com:4282, public=hxa-iad.crashplan.com:443, connectedFor=1min 0sec, cache=/Library/Caches/CrashPlan/43
 - setId=1: 37.69079106002676% complete

We don't care about percentage complete so much as we do about when the last backup occurred. But you could easily make an EA based on the local data from that file.

As for the source of truth, my understanding from speaking to support is that the client speaks to the storage nodes and the nodes then update the admin console. In other words, the most accurate information is going to come either from the client itself or the storage node (which I don't believe the API will necessarily access). I found this out because we dealt with an issue in the last year where the client was backing up fine but the admin console was out of sync and reporting that some clients hadn't backed up in X days. The issue in this case is that the admin console was having trouble gathering data from the specific node these clients were backing up to. Support was able to take care of it and the notifications stopped and the admin console started updating correctly again. They specifically said that should I ever see issues like that to always go by what's on the client and not the admin console.

Feel free to use any of the scripts posted.

DouglasWard-IA
New Contributor III

This is very helpful. I would love to see a version of this for percentage complete, per the above posts by @bpavlov and @Rhio . My awk foo is not sufficient for the task, however.

I've been able to get this awk command to work in terminal...

awk -F  '/[0-9]% complete/{print $3}' /Library/Logs/CrashPlan/app.log

... but I can't seem to get that to work in the script.

joethedsa
Contributor II

The current version of the Code42 app will alert you if Code42 doesn't have access to the full disk on Mojave and later. This can be configured as a configuration profile but I believe there are some devices where it is not set in my environment. There is a way to see the "fullDiskAccess" status of a device using the Code42 API. It would give you the result of either "true" or "false". I would like to get that into an extension attribute but I'm not sure how to strip the true or false text strings which is in a JSON format. The JSON format looks something like this:

{ "data": { "deviceGuid": "<Guid_Is_Here>", "name": "fullDiskAccess", "value": "true" }, "metadata": { "date": "2020-12-16T15:48:05.753-06:00", "headers": [] } }

The command to get it looks like this: (example is from the Code42 site here)

#!/bin/sh

GUID=`cat /Library/Application Support/CrashPlan/.identity | sed -n 's/guid=//p'`

curl -X GET 
  '<SERVER_URL>/api/v12/agent-state/view-by-device-guid?deviceGuid=$GUID&propertyName=fullDiskAccess' 
  -H 'cache-control: no-cache' 
  -H 'content-type: application/json' 
  -u '<username>' | python -m json.tool

I believe I will need to add a few more pipes with "awk" and "sed" but am unsure of the syntax to extract the "true" or "false" that I need. Any help would be appreciated.

beeboo
Contributor

@joethedsa Does that command work or how did you tweak it? i added my server URL and GUID but i get no response from the curl.

The only thing that looks to me is the /api/v12/ and that agent-state isnt in the official documentation for https://console.us.code42.com/apidocviewer

joethedsa
Contributor II

Hi @beeboo,
Sadly, I have not had any success getting this in an EA. I can successfully get data if I run the command via Terminal locally. Even if I could get the output into an EA, there would have to be some "cleanup" with the output because it would have to much unwanted data as you can see in my post from 12/16/2020. I would imagine that would require the commands of sed, grep, awk but I'm not sure how to implement them so all I get for an output is the values of either "True" or "False"

bpavlov
Honored Contributor

Just wanted to share these extension attributes that we're using (updated from what I had posted earlier from in the discussion):

One of these includes the backup percentage which may interest @AdminIA and @Rhio

beeboo
Contributor

to add, support got back to me and the only way to run the code is to run it as a local user in Code42, so my mistake was using my username as the username instead of a local C42 one

saikat_tripathi
New Contributor II

We have Code42 cloud in our environment. We have dependency where user has to enter his email id to initiate backup process. Is there any way we can automate the process where post code42 installation it will start backup automatically to code42 console. We use local user to login to mac.