Tuesday, January 24, 2023

Good bye loginhooks, Hello launchdaemons...

Recently our team began testing macOS Ventura (13.x) in our production testing environment. One of the first issues we noticed was that macOS no longer was going to run our loginhooks, either log in or log out...we knew it was coming.

Shell scripts have the ability to trap the exit of a script and perform additional things. We can exploit that to make a Login and Logout script with the same file. 

https://community.jamf.com/t5/jamf-pro/how-can-i-use-launchd-to-run-logout-script/td-p/42209

So, let's launch the scripts using /Library/LaunchAgents/org.example.loginhook.plist pointing to the scripts? Sure, that'll work...but no root access for anything we wanted to do "special" for the user. Examples of such might be mounting a shadow disk image to /Applications/, setting display or system sleep times, rebooting on specific conditions, etc...

Ok, how about we add a /Library/LaunchDaemon/org.example.login.plist that launches a signed app, which launches a script to run scripts with admin rights. (Am I the baddie?) We also add a PathState to watch for a /tmp/org.example.login file. When that's created, it runs the script. In this case, we wrap the script in an app.

Platypus.app provides a quick method to wrap a script and codesign it for deployment:

sudo codesign -f -s "Developer ID" -v orgLoginHook.app

Once the app is created and signed, it's placed in /Library/Example/Hools/orgLoginHook.app. This works, but the user can't then stop the script at logout...it just goes on and on. I can trigger the LaunchDaemon by writing a file in /tmp/ (PathState), but it won't stop if I delete the files. In fact, the PathState just gets triggered again when it's deleted. Luckily, it's still running...

How does a standard user stop a LaunchDaemon? 

sudo launchctl stop org.example.login

It's possible to allow the standard user to use sudo in the context. (This can't be the best way?) Add the following line to a new file named /etc/sudoers.d/org:

ALL ALL=NOPASSWD: /bin/launchctl stop org.example.login

Users can now stop the script running and trigger the logout events. Logout Hooks are restored.

Comment and let me know what I'm doing wrong or how you also like to abuse your operating system. 

Thanks, rusty.


::Files::

/Library/LaunchDaemons/org.example.login.plist

<?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.example.login</string>
    <key>PathState</key>
    <dict>
<key>/tmp/org.example.login</key>
<true/>
    </dict>
    <key>RunAtLoad</key>
    <false/>
    <key>ProgramArguments</key>
    <array>
 <string>/Library/Example/Hooks/orgLoginHook.app/Contents/MacOS/orgLoginHook</string>
    </array>
</dict>
</plist>

/Library/LaunchAgents/org.example.loginhook.plist

<?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.example.loginhook</string>
<key>ProgramArguments</key>
<array>
<string>/Library/Example/Hooks/orgLogin.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

orgLoginHook
#! /bin/zsh
#3.0
# runs everything in /Library/Example/Hooks beginning with LI (for LogIn), LO (for Logout)
#--------------------------------------------------------------------------------------------------
#-- Log - Echo messages with date and timestamp
#--------------------------------------------------------------------------------------------------
Log () {
logText=$1
# indent lines except for program entry and exit
if [[ "${logText}" == "-->"* ]];then
echo 
logText="${logText}`basename $0`: launched..."
else
if [[ "${logText}" == "<--"* ]];then
logText="${logText}`basename $0`: ...terminated" 
else
logText="   ${logText}"
fi
fi
date=$(/bin/date)
echo "${date/E[DS]T /} ${logText}"
}
loginmain () {
Log "-->"
if [[ -d ${HOOKSDIR} ]]; then
for hook in ${HOOKSDIR}/LI*; do
if [[ -s ${hook} && -x ${hook} ]]; then
Log "Executing ${hook} for $loggedInUserID..."
# run the item
${hook} "$loggedInUser"
if [[ $? -ne 0 ]]; then
Log "$0 ${hook} failed!"
fi
fi
done
fi
Log "<--" >> /Library/Example/org/login.log
}

logoutmain () {
Log "-->"
if [[ -d ${HOOKSDIR} ]]; then
for hook in ${HOOKSDIR}/LO*; do
if [[ -s ${hook} && -x ${hook} ]]; then
Log "$0 Executing ${hook} for $loggedInUser... "
# run the item
${hook} "$loggedInUser"
if [[ $? -ne 0 ]]; then
Log "$0 ${hook} failed!"
fi
fi
done
fi
    rm /tmp/org.example.login
Log "<--"
}

export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin

HOOKSDIR="/Library/Example/Hooks"
loggedInUser=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }' )
onLogout() {
    echo 'Logging out' >> /Library/Example/org/login.log
    logoutmain $loggedInUser 2>&1 >> /Library/Example/org/login.log
    exit
}
loginmain $loggedInUser 2>&1 >> /Library/Example/org/login.log

trap 'onLogout' SIGINT SIGHUP SIGTERM
while true; do
    sleep 21600 &
    wait $!
done


orgLogin.sh
#! /bin/zsh
#3.0

#--------------------------------------------------------------------------------------------------
#-- Log - Echo messages with date and timestamp
#--------------------------------------------------------------------------------------------------
Log ()
{
logText=$1
# indent lines except for program entry and exit
if [[ "${logText}" == "-->"* ]];then
logText="${logText}`basename $0`: launched..."
else
if [[ "${logText}" == "<--"* ]];then
logText="${logText}`basename $0`: ...terminated" 
else
logText="   ${logText}"
fi
fi
date=$(/bin/date)
echo "${date/E[DS]T /} ${logText}"
}

loginmain () {
Log "-->"
    touch /tmp/org.example.login
Log "<--"
}

logoutmain () {
Log "-->"
    rm /tmp/org.example.login
    sudo launchctl stop org.example.login
Log "<--"
}

onLogout() {
    echo 'Logging out' >> /Library/Example/org/login.log
    logoutmain $loggedInUser 2>&1 >> /Library/Example/org/login.log
    exit
}

export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin
loggedInUser=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }' )

loginmain $loggedInUser 2>&1 >> /Library/Example/org/login.log

trap 'onLogout' SIGINT SIGHUP SIGTERM
while true; do
    sleep 21600 &
    wait $!
done

Tuesday, March 22, 2022

SPSS 27 Silent Install for macOS!

 

SPSS 27 comes in a convenient installer pkg, which seems to be easy to deploy and effective without a user logged in! Yay!

The application and license file paths have changed...however it's even easier to license for an Auth Code

"/Applications/IBM SPSS Statistics 27/Resources/Activation/licenseactivator" "${AUTHCODE}"


Finally, I have nothing to complain about this year...

Tuesday, June 16, 2020

AutoDesk 2021 Silent Install


Installing AutoDesk Suite

Despite COVID, Fall classes are quickly approaching and that means it's time to deploy new software! Up next, AutoDesk AutoDesk 2021, Maya 2020, and Mudbox 2020. 

AutoCAD 2021

After attempting to install using their --silent flag and it failing due to no user being logged in, I decided to rip apart the DMG and see what it does...
"Silent" Install flag:
https://knowledge.autodesk.com/support/autocad/troubleshooting/caas/CloudHelp/cloudhelp/2021/ENU/Installation-AutoCAD/files/Install-ACDMAC/Installation-AutoCAD-Install-ACDMAC-acdmac-install-product-silently-html-html.html
"Silent" install for the whole suite: 
http://help.autodesk.com/view/INSTALL_LICENSE/ENU/?guid=GUID-4D762D36-E521-4D8D-8A48-B41FE2DDF381 

After I was able to get the install done, I then had to figure out how to register it so it doesn't prompt the user. In the past, network Registration was done with a license file, but I'm not sure this is needed any longer: 
https://knowledge.autodesk.com/search-result/caas/sfdcarticles/sfdcarticles/Using-the-licpath-lic-file-for-Autodesk-reg-network-license-enabled-products.html

The license file didn't really work as I expected, it always prompted the user for the LM server. Eventually I found the following document on pointing to a license manager:

https://knowledge.autodesk.com/support/3ds-max/troubleshooting/caas/sfdcarticles/sfdcarticles/Use-Installer-Helper.html

This tool helps us point our software to the license manager and prevent the software from prompting the user. We use this for all of the AutoDesk software.



"AUTOCAD21_LICENSE_SERVER"="LIC_SERVER"
"AUTOCAD21_pk"="777M1"
"AUTOCAD21_pv"="2021.0.0.F"
#Set Locale
wait defaults write .GlobalPreferences AppleLocale -string "en-US"
# Clean Out OLD/Failed Installs
wait rm -R "/Applications/Autodesk/AutoCAD*"
/usr/bin/hdiutil attach -quiet -nobrowse -mountpoint "/tmp/AutoCAD" "PATH_TO/Autodesk_AutoCAD.dmg"
installer -pkg "/tmp/AutoCAD/Install Autodesk AutoCAD 2021 for Mac.app/Contents/Helper/ObjToInstall/autocad2021.pkg" -tgt /
installer -pkg "/tmp/AutoCAD/Install Autodesk AutoCAD 2021 for Mac.app/Contents/Helper/Packages/Licensing/AdskLicensing-10.1.0.3194-mac-installer.pkg" -tgt /
installer -pkg "/tmp/AutoCAD/Install Autodesk AutoCAD 2021 for Mac.app/Contents/Helper/Packages/AdSSO/AdSSO-v2.pkg" -tgt /
installer -pkg "/tmp/AutoCAD/Install Autodesk AutoCAD 2021 for Mac.app/Contents/Helper/ObjToInstall/lib.pkg" -tgt /
#Create a License Server File with the contents:
#
#SERVER {parameter "LICENSE_SERVER21"} 000000000000
#USE_SERVER
#
#Place the file in "/Library/Application Support/Autodesk/AdskLicensingService/777M1_2021.0.0.F/licpath.lic"
# Reload the Licensing Service
launchctl unload "/Library/LaunchDaemons/com.autodesk.AdskLicensingService.plist"
launchctl load "/Library/LaunchDaemons/com.autodesk.AdskLicensingService.plist"
installer -pkg "/tmp/AutoCAD/Install Autodesk AutoCAD 2021 for Mac.app/Contents/Helper/ObjToInstall/licreg.pkg" -tgt /

/usr/bin/hdiutil detach -force "/tmp/AutoCAD"
"/Library/Application Support/Autodesk/AdskLicensing/Current/helper/AdskLicensingInstHelper" change -pk $AUTOCAD21_pk -pv $AUTOCAD21_pv -ls "@${AUTOCAD21_LICENSE_SERVER}" -lm NETWORK

Maya 2020

Following AutoCad, I ripped apart Maya and found the following packages that needed installed:

"MAYA20_LICENSE_SERVER"="LIC_SERVER"
"MAYA20_pk"="657L1"
"MAYA20_pv"="2020.0.0.F"
/usr/bin/hdiutil attach -quiet -nobrowse -mountpoint "/tmp/Autodesk_Maya" "PATH_TO/Autodesk_Maya.dmg"
installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Maya/Maya_core2020.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Licensing/AdskLicensing-9.2.1.2399-mac-installer.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Licensing/adlmframework18.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Licensing/adlmapps18.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/AdSSO/AdSSO-v2.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Licensing/adlmflexnetclient.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Maya/Maya_AdLMconf2020.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Maya/MtoA.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Maya/bifrost.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Maya/SubstanceInMaya-2.1.2-2020-Darwin.pkg" -tgt / installer -pkg "/tmp/Autodesk_Maya/Install Maya 2020.app/Contents/Helper/Packages/Maya/motion-library.maya-1.1.0.pkg" -tgt /
mkdir -p "/Library/Application Support/Autodesk/AdskLicensingService/657L1_2020.0.0.F"
#Create a License Server File with the contents: # #SERVER "${MAYA20_LICENSE_SERVER"} 000000000000 #USE_SERVER # #Place the file in "/Library/Application Support/Autodesk/AdskLicensingService/657L1_2020.0.0.F/licpath.lic"
/usr/bin/hdiutil detach -force "/tmp/Autodesk_Maya"
"/Library/Application Support/Autodesk/AdskLicensing/Current/helper/AdskLicensingInstHelper" change -pk "$MAYA20_pk" -pv "$MAYA20_pv" -lm NETWORK -ls "@${MAYA20_LICENSE_SERVER"}"


Mudbox 2020

With Mudbox, I reduced to just the packages and the AdskLicensingInstHelper, which seemed to do all the things needed to point to the server and retrieve a license.

"MUDBOX20_LICENSE_SERVER"="LIC_SERVER"
"MUDBOX20_pk"="498L1" 
"MUDBOX20_pv"="2020.0.0.F"
rm -Rf "/Applications/Autodesk/Mudbox*"
/usr/bin/hdiutil attach -quiet -nobrowse -mountpoint "/tmp/Autodesk_Mudbox" "PATH_TO/Autodesk_Mudbox.dmg"
installer -pkg "/tmp/Autodesk_Mudbox/Install Mudbox 2020.app/Contents/Packages/ADLM/AdskLicensing-9.2.1.2399-mac-installer.pkg" -tgt / installer -pkg "/tmp/Autodesk_Mudbox/Install Mudbox 2020.app/Contents/Packages/ADLM/AdSSO-v2.pkg" -tgt / installer -pkg "/tmp/Autodesk_Mudbox/Install Mudbox 2020.app/Contents/Packages/ADLM/adlmapps18.pkg" -tgt / installer -pkg "/tmp/Autodesk_Mudbox/Install Mudbox 2020.app/Contents/Packages/ADLM/adlmflexnetclient.pkg" -tgt / installer -pkg "/tmp/Autodesk_Mudbox/Install Mudbox 2020.app/Contents/Packages/ADLM/adlmframework18.pkg" -tgt / installer -pkg "/tmp/Autodesk_Mudbox/Install Mudbox 2020.app/Contents/Packages/Mudbox/Mudbox_AdLMconf2020.pkg" -tgt / wait installer -pkg "/tmp/Autodesk_Mudbox/Install Mudbox 2020.app/Contents/Packages/Mudbox/Mudbox_core2020.pkg" -tgt /
wait /usr/bin/hdiutil detach -force "/tmp/Autodesk_Mudbox"
"/Library/Application Support/Autodesk/AdskLicensing/Current/helper/AdskLicensingInstHelper" change -pk "$MUDBOX20_pk" -pv "$MUDBOX20_pv" -lm NETWORK -ls "@${MUDBOX20_LICENSE_SERVER}"

Anyway, that's been my last four weeks! I hope this helps someone else, possibly even future me?! 

Wednesday, December 4, 2019

The end of 2019!

Wow, I felt like this year went bye so quickly. Life has a way of throwing everything at you that can't be compared to anything else I know. Still, I'm excited to see what 2020 has in store for us all!

With that, I wanted to share one trick I came up with real quick for the Terminal.app. I'm often navigating my files through the Terminal.app and need a quick way to open up larger files to edit. My choice of plain text editor is TextMate.app and my typical workflow was to "open ." to have the finder show the current folder and then drag the file to TextMate or double click it. Sometimes, depending on the file type association, I could type "open filename" and it would get me to TextMate.app, sometimes not. I started using "open -a TextMate.app filename", but it was repetitive. I could make an alias for "open -a TextMate.app" that would save me some time, but what if I wanted to make a new file and edit it?

The simple function below allows me to type "e filename" and open either a new or existing file. I've added this to my ~/.zprofile so it loads every time I open a shell window:
function e() { # edit existing or new document in textmate
filename="$1"
if [[ ! -e "$filename" ]]; then
	touch "$filename"
fi
open -a TextMate.app "$filename"
}

Hope it helps inspire or at least open documents quicker! If you have another app you prefer to use for text editing, you can change the "TextMate.app" to your preferred editor! Happy 2019 everyone, see you next year!