Posted on
09-28-2017
04:00 AM
- last edited
a week ago
by
kh-richa_mig
I've gotten request from multiple members of the Mac community to dive into how we do Asset management.
So this is our story.
You can also see some of it in my JNUC speech from last year Building the Mac at LEGO(R) (27:20)
For our Asset mgm we want to know 3 values.
These 3 values can be the same, and thats fine.
Every night we do a sync from Jamf Pro to our Asset mgm server which updates the device records.
Value 1 - Last logged in user
For the first value I have 2 Extension Attributes.
One to identify the username of the logged in user. This is the one I use.
And one with the full name of the user. This is primarily for ease og use when a supporter has to look up who the user is.
<computer_extension_attribute>
<name>EA - Last User - Username</name>
<description>This attribute displays the username of the logged in user.</description>
<data_type>String</data_type>
<input_type>
<type>script</type>
<platform>Mac</platform>
<script>#!/bin/bash
_LASTUSER_=$(last | grep -m1 console | awk '{ print $1}')
if [ "$_LASTUSER_" == "" ]; then
echo "<result>loginscreen</result>"
else
echo "<result>$_LASTUSER_</result>"
fi</script>
</input_type>
<inventory_display>User and Location</inventory_display>
<recon_display>Extension Attributes</recon_display>
</computer_extension_attribute>
This gives the user currently logged in at time of Recon.
<computer_extension_attribute>
<name>EA - Last User - Full Name</name>
<description>This attribute displays the Full Name of the logged in user.</description>
<data_type>String</data_type>
<input_type>
<type>script</type>
<platform>Mac</platform>
<script>#!/bin/bash
_DOMAIN_="DOMAIN"
_INT_CHECK_="INTERNAL.RESOURCE.COM"
if ping -c 1 "$_INT_CHECK_" &> /dev/null; then
_LASTUSER_=$(last | grep -m1 console | awk '{ print $1}')
if [ "$_LASTUSER_" == "" ]; then
echo "<result>No logins</result>"
fi
_FULLNAME_=$(dscl localhost -read /Active Directory/"$_DOMAIN_"/All Domains/Users/"$_LASTUSER_" | sed -n '/RealName/{n;p;}' | cut -c 2-)
if [ "$_FULLNAME_" == "" ]; then
echo "<result>$_LASTUSER_</result>"
else
echo "<result>$_FULLNAME_</result>"
fi
else
exit 0
fi
exit $?</script>
</input_type>
<inventory_display>User and Location</inventory_display>
<recon_display>Extension Attributes</recon_display>
</computer_extension_attribute>
This is primarily for ease of use for Support. This is not transferred to Asset mgm.
Value 2 - Primary User
The Primary User is identified as being the most frequent logged in user in a 14 login scope.
So if User 1 has logged in 4 times and User 2 has logged in 10 times. User 2 becomes the Primary User.
There are some exceptions to this. root, admin, _mbsetupuser and our local support account, among others, is never set as the Primary User.
For this value I use the Username field in the computer record in Jamf Pro.
All the usernames are stored in a SQLite db at /Library/Receipts/ASSETusers.db
Script - Auto - Assetmgm - 01 - Primary User
#!/bin/bash
# Created by Michael Loft Mikkelsen on 27/06/2017.
# README:
# - This script is part of the PrimaryUser framework this script will update the ASSETusersDB logintable and primary user table.
# It will update the JSS on changes.
# To cleanup run command /usr/bin/sqlite3 /Library/Receipts/ASSETusers.db "DELETE FROM login;" && /usr/bin/sqlite3 /Library/Receipts/ASSETusers.db "DELETE FROM 'primary';" && /usr/bin/sqlite3 /Library/Receipts/ASSETusers.db "VACUUM;"
# this will clear login and primary table and have the script update Primary user on the JSS record on next unlock/login.
#
# REFERENCE LINKS:
# - None
#
#
# Last update: 16/08/2017
#
# Version 1.0 - Script created Michael Loft Mikkelsen/LEGO
# Version 1.1 - Edited the number of logins before prompt from 3 to 2
# Version 1.1 - Redesigned to use sleepwatcher to run
# Version 1.2 - Rewritten from scratch
# Version 1.3 - Edited to always try to update Primary User in JSS
# Version 1.4 - Edited to update User and Location instead of Extension Attribute
# Version 1.5 - Code cleanup
# Version 1.6 - Now writes Primary user to primary table at every run
# Version 1.7 - When run. Logs to jamf.log
# *** ************************************************************************* ***
# Logging
# *** ************************************************************************* ***
# Put logs in /var/log/Assetmgm/primaryuser.log on the computer for debugging
if [ ! -d "/private/var/log/Assetmgm/" ]; then
mkdir /private/var/log/Assetmgm
fi
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>/private/var/log/Assetmgm/primaryuser.log 2>&1
# *** ************************************************************************* ***
# *** Edit this to your own preferences **************************************** ***
# the URL of your JSS, if you are behind a load balancer you can use the URL and do not put a slash on the end
# example = https://myjss.company.com:8443
_JSS_URL_='https://YOUR.JSS.COM'
_USERNAME_="APIUSERNAME"
_PASSWORD_="APIUSERPASSWORD"
# Title of Script
_TITLE_="$4"
# *** ************************************************************************* ***
# DB Variables
# *** ************************************************************************* ***
_DB_="/Library/Receipts/ASSETusers.db"
_LOGINTABLE_="login"
_SYSTEMUSERTABLE_="system"
_PRIMARYTABLE_="primary"
# *** ************************************************************************* ***
# Commands
# *** ************************************************************************* ***
_SQLITE_="/usr/bin/sqlite3"
_ECHO_="/bin/echo"
_PYTHON_="/usr/bin/python"
_SCUTIL_="/usr/sbin/scutil"
_DATE_="/bin/date"
_CURL_="/usr/bin/curl"
_PS_="/bin/ps"
# *** ************************************************************************* ***
# Do not edit below this line
# *** ************************************************************************* ***
timestamp() {
# Define a timestamp function
$_DATE_ "+%d-%m-%Y %H:%M:%S"
}
toLog() {
# Define timestamp
_TIMESTAMP_=$($_DATE_ +"%a %b %d %I:%M:%S")
# Get ComputerName
_COMP_=$($_SCUTIL_ --get ComputerName)
# Get Process info
_PID_=$($_PS_ aux | grep "[j]amf policy" | sed -n '1p' | awk '{print $2}')
# Log to /var/log/jamf.log
$_ECHO_ "$_TIMESTAMP_ $_COMP_ jamf[$_PID_]: Executing $_TITLE_" >> /var/log/jamf.log
}
getLoggedInUser() {
# Identify current logged in user
_LOGGEDINUSER_=$($_PYTHON_ -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");')
if [ -z "$_LOGGEDINUSER_" ];
then
$_ECHO_ "$(timestamp): No user logged in... Exiting"
exit $?
else
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is current logged in user"
fi
}
createDB(){
# Create databases if they dont exist. If they do move along
$_SQLITE_ "$_DB_" "CREATE TABLE IF NOT EXISTS '$_LOGINTABLE_' (ID INTEGER PRIMARY KEY,user TEXT,time DATETIME DEFAULT CURRENT_TIMESTAMP);"
$_SQLITE_ "$_DB_" "CREATE TABLE IF NOT EXISTS '$_SYSTEMUSERTABLE_' (ID INTEGER PRIMARY KEY,user TEXT,time DATETIME DEFAULT CURRENT_TIMESTAMP);"
$_SQLITE_ "$_DB_" "CREATE TABLE IF NOT EXISTS '$_PRIMARYTABLE_' (ID INTEGER PRIMARY KEY,user TEXT,time DATETIME DEFAULT CURRENT_TIMESTAMP);"
}
tableFill(){
declare -a systemUsers=("${!1}")
for i in "${systemUsers[@]}"
do
$_SQLITE_ "$_DB_" "INSERT INTO '$_SYSTEMUSERTABLE_'(user) SELECT '$i' WHERE '$i' not in (SELECT DISTINCT user FROM '$_SYSTEMUSERTABLE_' WHERE user='$i');"
done
}
tableCreate()
{
local systemUsers=(
"LOGINWINDOW"
"root"
"admin"
"_mbsetupuser"
)
tableFill systemUsers[@]
}
systemUseCheck() {
# Is logged user a system account?
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_SYSTEMUSERTABLE_' WHERE user = '$_LOGGEDINUSER_' LIMIT 1;" ) == *"$_LOGGEDINUSER_"* ]];
then
# If YES log & exit
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is a system account... Exiting"
exit $?
else
# If NO, add logged in user to login table
$_SQLITE_ "$_DB_" "INSERT INTO '$_LOGINTABLE_' (user) VALUES ('$_LOGGEDINUSER_');"
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is not a system account... Continue"
fi
}
setPrimaryUser(){
# Is PrimaryUser equal to most frequent user?
if [[ $($_SQLITE_ "$_DB_" "SELECT Q.User FROM (SELECT user FROM '$_LOGINTABLE_' ORDER BY ID DESC LIMIT 14) AS Q GROUP BY Q.user ORDER BY COUNT(Q.user) DESC LIMIT 1;") == "$_LOGGEDINUSER_" ]];
then
# If YES. Check if logged in user already is primary user
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_PRIMARYTABLE_' ORDER BY ID DESC LIMIT 1;") == "$_LOGGEDINUSER_" ]];
then
# If YES log to primary table
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is the PrimaryUser..."
$_SQLITE_ "$_DB_" "INSERT INTO '$_PRIMARYTABLE_' (user) VALUES ('$_LOGGEDINUSER_');"
else
# If NO log to primary user table
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is now the PrimaryUser... Logging"
$_SQLITE_ "$_DB_" "INSERT INTO '$_PRIMARYTABLE_' (user) VALUES ('$_LOGGEDINUSER_');"
fi
else
# If NO, add logged in user to primary table
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is not PrimaryUser... Exiting"
exit $?
fi
}
jssConnectionTest() {
_TEST_=$($_CURL_ -s -k -I ${_JSS_URL_}/JSSResource/computers/id/1 | awk '/HTTP/ { print $2}')
if [[ ${_TEST_} == '401' ]];
then
$_ECHO_ "$(timestamp): Can connect to JSS... Good to go"
else
$_ECHO_ "$(timestamp): JSS not reachable... Exiting"
exit 0
fi
}
getJSSInfo(){
# Get JSS ID
_NAME_="$($_SCUTIL_ --get ComputerName)"
_ID_="$($_CURL_ -s -k -H "Accept: application/json" -H "Content-Type: application/xml" -X GET ${_JSS_URL_}/JSSResource/computers/name/"$_NAME_" --user "$_USERNAME_":"$_PASSWORD_" | sed -e 's/,/ /g' -e 's/:/ /g'| awk '{print $4}')"
}
updateJSS() {
# Upload username to JSS
XML='<computer><location><username>'
XML+=${_LOGGEDINUSER_}
XML+='</username></location></computer>'
$_CURL_ -s -k ${_JSS_URL_}/JSSResource/computers/id/"$_ID_" --user "$_USERNAME_":"$_PASSWORD_" -H "Content-Type: text/xml" -X PUT -d "$XML" > /dev/null
$_ECHO_ "$(timestamp): JSS Updated..."
}
# *** ************************************************************************* ***
# Operators
# *** ************************************************************************* ***
toLog
getLoggedInUser
createDB
tableCreate
systemUseCheck
setPrimaryUser
jssConnectionTest
getJSSInfo
updateJSS
exit $?
This will update the Username field in the Jamf Pro. Jamf Pro will then make sure to get the user information from AD. This is then synced with Asset mgm.
If you have internal accounts you wish to ignore add them to the array in the tableCreate() function.
Be mindfull of the
# Title of Script
_TITLE_="$4"
I'll explain that at the end.
Value 2 - Owner
Only an employee is allowed to be Owner of a Mac. The Owner is the user responsible and accountable for the hardware.
We have quite a few external consultants short term freelancers. In their case they become the Primary Users. But the manager responsible for them becomes the Owner of the Mac.
For us it's easy to differentiate internals from externals. Externals have a number in their username.
I use a Extension Attribute to store the Owner information.
Be mindfull of the ID of the EA. This is required in the script.
<computer_extension_attribute>
<name>EA - Owner - Username</name>
<description/>
<data_type>String</data_type>
<input_type>
<type>Text Field</type>
</input_type>
<inventory_display>User and Location</inventory_display>
<recon_display>User and Location</recon_display>
</computer_extension_attribute>
The owner information is stored in the same SQLite db at /Library/Receipts/ASSETusers.db
Script - Auto - Assetmgm - 02 - Owner
#!/bin/bash
# Created by Michael Loft Mikkelsen on 27/06/2017.
# README:
# - This script identifies when a given user has logged in at least 3 times. It will then query the user if they are the primary user.
# If they are external consultants it will query AD for the users manager and indicate this user as owner.
# To cleanup run command "/usr/bin/sqlite3 /Library/Receipts/ASSETusers.db "DELETE FROM owner;" && /usr/bin/sqlite3 /Library/Receipts/ASSETusers.db "DELETE FROM ignore;" && /usr/bin/sqlite3 /Library/Receipts/ASSETusers.db "VACUUM;"" this will let the script request a new Owner.
#
# REFERENCE LINKS:
# - None
#
#
# Last update: 17/08/2017
#
# Version 1.0 - Script created Michael Loft Mikkelsen/LEGO
# Version 1.1 - Edited the number of logins before prompt from 3 to 2
# Version 1.2 - Redesigned to use sleepwatcher to run
# Version 1.3 - Code cleanup
# Version 1.4 - If a user is in ignore table but is the last 10 Primary user records. they get added as Owner
# Version 1.5 - When run. Logs to jamf.log
# Version 1.6 - When External user has manager set as owner. It will not prompt them
# *** ************************************************************************* ***
# Logging
# *** ************************************************************************* ***
# Put logs in /var/log/Assetmgm/setowner.log on the computer for debugging
if [ ! -d "/private/var/log/Assetmgm/" ]; then
mkdir /private/var/log/Assetmgm
fi
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>/private/var/log/Assetmgm/setowner.log 2>&1
# *** ************************************************************************* ***
# *** Edit this to your own preferences **************************************** ***
# the URL of your JSS, if you are behind a load balancer you can use the URL and do not put a slash on the end
# example = https://myjss.company.com:8443
_JSS_URL_='https://YOUR.JSS.COM'
_USERNAME_="APIUSERNAME"
_PASSWORD_="APIUSERPASSWORD"
# The ID of your Extension Attribute
_EAID_="83"
# Title of Script
_TITLE_="$4"
# Name of your Domain
_DOMAIN_="DOMAIN"
# *** ************************************************************************* ***
# DB Variables
# *** ************************************************************************* ***
_DB_="/Library/Receipts/ASSETusers.db"
_PRIMARYTABLE_="primary"
_OWNERTABLE_="owner"
_IGNORETABLE_="ignore"
_SYSTEMUSERTABLE_="system"
# *** ************************************************************************* ***
# Commands
# *** ************************************************************************* ***
_DSCL_="/usr/bin/dscl"
_ECHO_="/bin/echo"
_JAMFHELPER_='/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper'
_PYTHON_="/usr/bin/python"
_SCUTIL_="/usr/sbin/scutil"
_SQLITE_="/usr/bin/sqlite3"
_SYSCTL_="/usr/sbin/sysctl"
_DATE_="/bin/date"
_CURL_="/usr/bin/curl"
_PS_="/bin/ps"
# *** ************************************************************************* ***
# Local Resources
# *** ************************************************************************* ***
# Mac Icons
_ICONPATH_='/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/'
_MACBOOKAIR_="com.apple.macbookair-13-unibody.icns"
_MACBOOKPRO_="com.apple.macbookpro-15-retina-display.icns"
_MACBOOK_="com.apple.macbook-retina-silver.icns"
_MACPRO_="com.apple.macpro-cylinder.icns"
_MACMINI_="com.apple.macmini-unibody-no-optical.icns"
_IMAC_="com.apple.imac-unibody-27-no-optical.icns"
_UNKNOWN_="public.generic-pc.icns"
# *** ************************************************************************* ***
# Do not edit below this line
# *** ************************************************************************* ***
# Functions #####
timestamp() {
# Define a timestamp function
$_DATE_ "+%d-%m-%Y %H:%M:%S"
}
toLog() {
# Define timestamp
_TIMESTAMP_=$($_DATE_ +"%a %b %d %I:%M:%S")
# Get ComputerName
_COMP_=$($_SCUTIL_ --get ComputerName)
# Get Process info
_PID_=$($_PS_ aux | grep "[j]amf policy" | sed -n '1p' | awk '{print $2}')
# Log to /var/log/jamf.log
$_ECHO_ "$_TIMESTAMP_ $_COMP_ jamf[$_PID_]: Executing $_TITLE_" >> /var/log/jamf.log
}
getLoggedInUser() {
# Identify current logged in user
_LOGGEDINUSER_=$($_PYTHON_ -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");')
if [ -z "$_LOGGEDINUSER_" ];
then
$_ECHO_ "$(timestamp): No user logged in... Exiting"
exit $?
else
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is current logged in user"
fi
}
createDB(){
# Create databases if they dont exist. If they do, move along
$_SQLITE_ "$_DB_" "CREATE TABLE IF NOT EXISTS '$_OWNERTABLE_' (ID INTEGER PRIMARY KEY,user TEXT,time DATETIME DEFAULT CURRENT_TIMESTAMP);"
$_SQLITE_ "$_DB_" "CREATE TABLE IF NOT EXISTS '$_IGNORETABLE_' (ID INTEGER PRIMARY KEY,user TEXT,time DATETIME DEFAULT CURRENT_TIMESTAMP);"
}
alreadyOwner() {
# Verify this user is not already set as owner
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_OWNERTABLE_' ORDER BY ID DESC LIMIT 1;" ) == *"$_LOGGEDINUSER_"* ]];
then
# If YES. Log, update JSS & exit
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is the owner"
_OWNER_="$_LOGGEDINUSER_"
jssConnectionTest
getJSSInfo
updateJSS
exit $?
fi
}
ignoreCheck() {
# Verify this user is not in the ignore table
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_IGNORETABLE_' WHERE user = '$_LOGGEDINUSER_' LIMIT 1;" ) == *"$_LOGGEDINUSER_"* ]];
then
# If YES. Verify this user is not actual primary user
# Check if the 10th last Primary user set is not current logged in user
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_PRIMARYTABLE_' ORDER BY ID LIMIT 9,1;" ) == *"$_LOGGEDINUSER_"* ]];
then
# If YES verify the last 10 Primary user records are logged in user
if [[ $($_SQLITE_ "$_DB_" "SELECT DISTINCT user FROM '$_PRIMARYTABLE_' ORDER BY ID DESC LIMIT 10;") == "$_LOGGEDINUSER_" ]];
then
# If YES, set logged in user as Owner
$_ECHO_ "$(timestamp): Bypass ignore list due to amount of logins"
externalOrNot
modelInfo
# If they are internal employees they will be added as Owner of the Mac
if [[ "$_USERSTAT_" == INTERNAL ]];
then
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_" "set as Owner"
_OWNER_="$_LOGGEDINUSER_"
$_SQLITE_ $_DB_ "INSERT INTO '$_OWNERTABLE_' (user) VALUES ('$_OWNER_');"
jssConnectionTest
getJSSInfo
updateJSS
exit $?
else
# If they are external consultants or not a standard account. The manager will be added a Owner
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_" "is external or not a standard account and therefore cannot be set as owner"
$_ECHO_ "$(timestamp): Setting" "$_MANAGER_" "as owner"
_OWNER_="$_MANAGER_"
$_SQLITE_ $_DB_ "INSERT INTO '$_OWNERTABLE_' (user) VALUES ('$_OWNER_');"
jssConnectionTest
getJSSInfo
updateJSS
exit $?
fi
else
$_ECHO_ "$(timestamp): Ignoring $_LOGGEDINUSER_... Exiting"
exit $?
fi
else
$_ECHO_ "$(timestamp): Ignoring $_LOGGEDINUSER_... Exiting"
exit $?
fi
fi
}
systemUseCheck() {
# Is logged user a system account?
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_SYSTEMUSERTABLE_' WHERE user = '$_LOGGEDINUSER_' LIMIT 1;" ) == *"$_LOGGEDINUSER_"* ]];
then
# If YES log & exit
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is a system account... Exiting"
exit $?
else
# If NO, continue.
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is not a system account... Continue"
fi
}
loggedInUserCheck() {
# Verify the user has logged in 3 times in a row
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_PRIMARYTABLE_' ORDER BY ID DESC LIMIT 0,1;" ) == *"$_LOGGEDINUSER_"* ]]; # Last Primary user record
then
# If YES, continue
$_ECHO_ "$(timestamp): 1 login..."
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_PRIMARYTABLE_' ORDER BY ID DESC LIMIT 1,1;" ) == *"$_LOGGEDINUSER_"* ]]; # 2nd last Primary user record
then
# If YES, continue
$_ECHO_ "$(timestamp): 2 login..."
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_PRIMARYTABLE_' ORDER BY ID DESC LIMIT 2,1;" ) == *"$_LOGGEDINUSER_"* ]]; # 3rd last Primary user record
then
# If YES, log & continue
$_ECHO_ "$(timestamp): 3 login..."
$_ECHO_ "$(timestamp): Primary User check verified... Continue"
else
# If no, exit
$_ECHO_ "$(timestamp): Primary user requirements not met... Exiting"
exit $?
fi
else
# If no, exit
$_ECHO_ "$(timestamp): Primary user requirements not met... Exiting"
exit $?
fi
else
# If no, exit
$_ECHO_ "$(timestamp): Primary user requirements not met... Exiting"
exit $?
fi
}
externalOrNot() {
# Identify user account as being External or Non Standart Account
if [[ $($_ECHO_ "${_LOGGEDINUSER_}" | sed "s/[^0-9]//g") == "" ]];
then
# Set userstate as internal
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is a internal user"
_USERSTAT_="INTERNAL"
else
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_ is External or not a Standard account"
_USERSTAT_="EXTERNAL"
identifyManager
fi
}
identifyManager() {
# Get username of manager
if [[ "$_USERSTAT_" == "EXTERNAL" ]];
then
# Get Manager
_MANAGER_=$($_DSCL_ localhost -read /Active Directory/"$_DOMAIN_"/All Domains/Users/"$_LOGGEDINUSER_" | grep dsAttrTypeNative:manager: | sed -e 's/=/ /g' -e 's/,/ /' | awk '{print $3}')
if [[ $($_ECHO_ "${_MANAGER_}") == "" ]];
then
$_ECHO_ "$(timestamp): AD not reachable or other error... Exiting"
exit $?
else
$_ECHO_ "$(timestamp): $_MANAGER_ is indicated as being responsible for $_LOGGEDINUSER_"
fi
if [[ $($_SQLITE_ "$_DB_" "SELECT user FROM '$_OWNERTABLE_' ORDER BY ID DESC LIMIT 1;" ) == *"$_MANAGER_"* ]];
then
# If YES. Log, update JSS & exit
$_ECHO_ "$(timestamp): $_MANAGER_ is the owner"
_OWNER_="$_MANAGER_"
jssConnectionTest
getJSSInfo
updateJSS
exit $?
fi
else
# Exit function
return 1
fi
}
modelInfo() {
# Build Model-Icon associations
if [[ "$($_SYSCTL_ hw.model | grep -i "MACBOOKAIR")" == *"MacBookAir"* ]];
then
$_ECHO_ "$(timestamp): This Mac is a MacBook Air"
_MODELICON_="$_ICONPATH_""$_MACBOOKAIR_"
elif [[ "$($_SYSCTL_ hw.model | grep -i "MACBOOKPRO")" == *"MacBookPro"* ]]
then
$_ECHO_ "$(timestamp): This Mac is a MacBook Pro"
_MODELICON_="$_ICONPATH_""$_MACBOOKPRO_"
elif [[ "$($_SYSCTL_ hw.model | grep -i "MACBOOK")" == *"MacBook"* ]]
then
$_ECHO_ "$(timestamp): This Mac is a MacBook"
_MODELICON_="$_ICONPATH_""$_MACBOOK_"
elif [[ "$($_SYSCTL_ hw.model | grep -i "MACPRO")" == *"MacPro"* ]]
then
$_ECHO_ "$(timestamp): This Mac is a Mac Pro"
_MODELICON_="$_ICONPATH_""$_MACPRO_"
elif [[ "$($_SYSCTL_ hw.model | grep -i "IMAC")" == *"iMac"* ]]
then
$_ECHO_ "$(timestamp): This Mac is a iMac"
_MODELICON_="$_ICONPATH_""$_IMAC_"
elif [[ "$($_SYSCTL_ hw.model | grep -i "MACNMINI")" == *"MacMini"* ]]
then
$_ECHO_ "$(timestamp): This Mac is a MacMini"
_MODELICON_="$_ICONPATH_""$_MACMINI_"
else
$_ECHO_ "$(timestamp): This model is unknown..."
_MODELICON_="$_ICONPATH_""$_UNKNOWN_"
fi
}
responsibleUserOrNot() {
# Query the user if they are the primary user of the Mac
# Variables to build HUD notification
_JH_ARGS_=(
-windowType hud
-title "Responsible User"
-heading "RESPONSIBLE USER"
-icon "$_MODELICON_"
-windowPosition ul
-alignHeading left
-button1 "Yes"
-button2 "No"
-defaultButton 1
-cancelButton 2
-timeout 120
-countdown
-lockHUD
-description
)
_INTERNALMESSAGE_="Are you responsible this Mac, please press "Yes"
You will be registered in Assyst as being the owner of this Mac.
If you are not responsible for this Mac, please press "No""
_EXTERNALMESSAGE_="Is ""$_MANAGER_"" responsible this Mac, please press "Yes"
If you are not responsible for this Mac, please press "No""
# Identify which message to display
if [[ "$_USERSTAT_" == INTERNAL ]];
then
_MESSAGE_="$_INTERNALMESSAGE_"
else
_MESSAGE_="$_EXTERNALMESSAGE_"
fi
# Prompt user
"$_JAMFHELPER_" "${_JH_ARGS_[@]}" "$_MESSAGE_" > /dev/null
# If they select "No" they will be added to the Ignore table of the ASSETuser.db not be queried again
if [[ "$?" == "2" ]];
then
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_" "added to ignore List... Exiting"
$_SQLITE_ $_DB_ "INSERT INTO '$_IGNORETABLE_' (user) VALUES ('$_LOGGEDINUSER_');"
exit $?
else
# If they are internal employees they will be added as Owner of the Mac
if [[ "$_USERSTAT_" == INTERNAL ]];
then
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_" "set as Owner"
_OWNER_="$_LOGGEDINUSER_"
$_SQLITE_ $_DB_ "INSERT INTO '$_OWNERTABLE_' (user) VALUES ('$_OWNER_');"
else
# If they are external consultants or not a standard account. The manager will be added a Owner
$_ECHO_ "$(timestamp): $_LOGGEDINUSER_" "is external or not a standard account and therefore cannot be set as owner"
$_ECHO_ "$(timestamp): Setting" "$_MANAGER_" "as owner"
_OWNER_="$_MANAGER_"
$_SQLITE_ $_DB_ "INSERT INTO '$_OWNERTABLE_' (user) VALUES ('$_OWNER_');"
fi
fi
}
jssConnectionTest() {
_TEST_=$($_CURL_ -s -k -I ${_JSS_URL_}/JSSResource/computers/id/1 | awk '/HTTP/ { print $2}')
if [[ ${_TEST_} == '401' ]];
then
$_ECHO_ "$(timestamp): Can connect to JSS... Good to go"
else
$_ECHO_ "$(timestamp): JSS not reachable... Exiting"
exit 0
fi
}
getJSSInfo(){
# Get JSS ID
_NAME_="$($_SCUTIL_ --get ComputerName)"
_ID_="$($_CURL_ -s -k -H "Accept: application/json" -H "Content-Type: application/xml" -X GET ${_JSS_URL_}/JSSResource/computers/name/"$_NAME_" --user "$_USERNAME_":"$_PASSWORD_" | sed -e 's/,/ /g' -e 's/:/ /g'| awk '{print $4}')"
}
updateJSS() {
# Upload username to JSS
XML='<computer><extension_attributes><extension_attribute><id>'"$_EAID_"'</id><value>'
XML+=${_OWNER_}
XML+='</value></extension_attribute></extension_attributes></computer>'
$_CURL_ -s -k ${_JSS_URL_}/JSSResource/computers/id/"$_ID_" --user "$_USERNAME_":"$_PASSWORD_" -H "Content-Type: text/xml" -X PUT -d "$XML" > /dev/null
$_ECHO_ "$(timestamp): JSS Updated..."
}
# *** ************************************************************************* ***
# Operators
# *** ************************************************************************* ***
toLog
getLoggedInUser
createDB
alreadyOwner
ignoreCheck
systemUseCheck
loggedInUserCheck
externalOrNot
modelInfo
responsibleUserOrNot
jssConnectionTest
getJSSInfo
updateJSS
exit $?
The criteria to be prompted is, you are the Primary User of the Mac and have logged in at least 3 times.
If they ansver Yes and are internal employees they get added in the ASSETUser.db and the EA in Jamf Pro is updated. Then synced with Asset mgm.
If they answer Yes and are external consultant/Freelancer, they get added in the ASSETUser.db. It then does a AD lookup to identify the manager responsible. They then get adde to the EA in Jamf pro.
If they answer No. they get added to the ignore table and are not prompted again. unless they afterwards have 10 consecutive logins. Then they get registered as Owner anyway.
So now we have all 3 values.
Last User gets updated at every Recon.
Primary User and Owner gets updated with a login triggered Policy.
<policy>
<general>
<name>Policy - Set - Primary User & Owner</name>
<enabled>true</enabled>
<trigger>EVENT</trigger>
<trigger_login>true</trigger_login>
<frequency>Ongoing</frequency>
<offline>true</offline>
</general>
<scope>
<all_computers>true</all_computers>
</scope>
<scripts>
<size>2</size>
<script>a
<id>XX</id>
<name>Script - Auto - Assetmgm - 01 - Primary User</name>
<priority>After</priority>
<parameter4>Script - Auto - Assetmgm - 01 - Primary User</parameter4>
</script>
<script>
<id>XX</id>
<name>Script - Auto - Assetmgm - 02 - Owner</name>
<priority>After</priority>
<parameter4>Script - Auto - Assetmgm - 02 - Owner</parameter4>
</script>
</scripts>
</policy>
Be mindful to adjust ID and names accordingly.
To circle back a bit.
The reason I use the
# Title of Script
_TITLE_="$4"
In my scripts is because the jamf binary never logs when it runs a script in a Policy. Which really gave me grief at some point.
How I mitigated this was I always have a Parameter Label in my scripts called "Title of Script". I try to always use $4 for this.
Then in the Policy where you run the script you copy in the full name of the script into this label.
You will then in /var/log/jamf.log have a entry that looks like this.
Tue Sep 26 08:37:59 MacName jamf[9682]: Executing Script - Auto - Assetmgm - 02 - Owner
With this framework I can reliably trust the information coming in to Jamf Pro and further on to our Asset mgm.
It's intelligent enough that if the Mac is handed over to another user they will within a few logins become Primary User and then Owner.
One more thing though ;-)
How many times do your user log out and/reboot their Mac?
As you might know when you unlock you screen or wake you Mac from sleep, macOS (and OS X) doesn't register that as a login. Because its not, its a unlock. So expected behaviour.
What I found is that I wanted to simulate a behaviour from Windows (not something I usually aspire to, but for this it made sense)
I wanted the Mac to handle a unlock the same way as a login.
In my search i stublede upon Sleepwatcher. Shoutout to [Bernhard Baehr](mailto:bernhard.baehr@gmx.de) for this. Please don't stop supporting this. That would really ruin my workflow.
What Sleepwatcher does is, it listens for all sorts of triggers. Among those, screen going to sleep, and waking up (Y)
I deploy Sleepwatcher with a LaunchDaemon indicating which triggers I want it to listen for
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.mac.sleepwatcher</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/sbin/sleepwatcher</string>
<string>-V</string>
<string>-S /etc/rc.sleep</string>
<string>-W /etc/rc.wakeup</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
Then I have 2 script, one for sleep actions and one for wakup.
Sleep
#!/bin/bash
#
# rc.sleep
#
# script to be executed on sleep by the sleepwatcher daemon
#
_JAMF_="/usr/local/jamf/bin/jamf"
_PYTHON_="/usr/bin/python"
_LOGGEDINUSER_=$($_PYTHON_ -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");')
$_JAMF_ policy -action logout -username "$_LOGGEDINUSER_" -forceNoRecon
exit $?
Wakeup
#!/bin/bash
#
# rc.sleep
#
# script to be executed on sleep by the sleepwatcher daemon
#
_JAMF_="/usr/local/jamf/bin/jamf"
_PYTHON_="/usr/bin/python"
_LOGGEDINUSER_=$($_PYTHON_ -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");')
$_JAMF_ policy -action login -username "$_LOGGEDINUSER_" -forceNoRecon
exit $?
If you wanted to you could have Sleepwatcher run every script in a particular folder
_SCRIPTFOLDER_="/Library/Scripts"
if [ "$(ls -A $_SCRIPTFOLDER_)" ]; then
# Run every script in Scriptfolder
for script in "$_SCRIPTFOLDER_"/*.sh;
do
"$script" & echo "$(date): $script" >> /var/log/wakeupscripts.log &
done
wait
else
echo "$(date): $_SCRIPTFOLDER_ is empty" >> /var/log/wakeupscripts.log
fi
But be careful with this. They will be run as root.
So this turned out a bit longer then expected.
I might turn this into a blogpost at some point.
If you came this far I hope you found this useful.
Let me know what you think. I would love to get your feedback.