How we do Asset management with our Jamf Pro

macninja_IO
New Contributor III

I've gotten request from multiple members of the Mac community to dive into how we do Asset management. 0a7cd1a3d5bc472893af52a0d20c3e86
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.

  1. Last logged in user
  2. Primary user
  3. Owner

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.

0 REPLIES 0