Try something like this. Not a guarantee to work, but for the moment at least it should. Things may change in the future though.
#!/bin/sh
loggedInUser=$( ls -l /dev/console | awk '{print $3}' )
loggedInPID=$( ps -axj | awk "/^$loggedInUser/ && /Dock.app/ {print $2;exit}" )
agentloaded=$(/bin/launchctl bsexec "${loggedInPID}" sudo -iu "${loggedInUser}" "launchctl list | grep local.keepAlive.ADPassMon | awk '{print $NF}'")
if [ "${agentloaded}" ]; then
echo "<result>Loaded</result>"
else
echo "<result>not loaded or installed</result>"
fi
Note there is no reason to check the grep output in the first if block I believe, since if grep locates the item in the launchctl list output, it results in a true state, meaning the if/then really only needs to see if the "$agentloaded" variable resulted in true (or not) to echo back the result you want.
Here's my style of getting console user name (there's a one liner somewhere on jamfnation! : )
P.S. Backtick style not at as pretty as $( )
#!/bin/sh
#get uid of console owner
eval $(stat -s /dev/console)
#get username
consoleUsername=$(id -un $st_uid)
#grep for string as console user
agentloaded=$(su $consoleUsername -c "launchctl list | grep local.keepAlive.ADPassMon")
#if string is not empty
if [ -n "${agentloaded}" ]; then
echo "<result>Loaded</result>"
else
echo "<result>not loaded or installed</result>"
fi
Thanks @mm2270 @yr_joelbruner for your help,
It made for a great end to the week when it all came together!
Hi @yr_joelbruner
I've applied your method to a script that loads the launch agent if required, what seems to be happening is that the script fails if the Mac is at the screensaver lock but runs happily if the it's not.
Could you advise a way to get around this ?
the script
#!/bin/sh
#get uid of console owner
eval $(stat -s /dev/console)
#get username
consoleUsername=$(id -un $st_uid)
#Load LaunchAgent as console user
(su $consoleUsername -c "launchctl load /Library/LaunchAgents/local.adpassmon.job.plist")
echo "loaded"
exit 0
when it fails the output is
Executing Policy ADMonPass re-load Launch Agent...
Running script ADMonPass Load LaunchAgent...
Script exit code: 0
Script result: Could not open job overrides database at: /private/var/db/launchd.db/com.apple.launchd/overrides.plist: 13: Permission denied
launch_msg(): Socket is not connected loaded
@May I'm wondering why you are doing this for ADPassMon? I just use a LaunchAgent that loads at login. Maybe you have users who are unloading the LaunchAgent. You probably need a check to see if anyone owns the console and an if statement to exit gracefully if nobody owns it.
Hi @jhbush1973
We need to make sure the application stays open regardless so we have the LaunchAgent set to keep alive, my main concern is that with the tiniest bit of Googling anyone can learn how to unload the launch agent and quit the app if they want.
I'm wondering if it's possible to use a Global LaunchDaemon rather than LaunchAgent to keep the application alive ? that way non admin users couldn't unload it and also if it didn't load it would be simpler to re-load through a policy as root.
Google time!
I'm not sure if it would work to load it as a LaunchDaemon. I haven't used ADPassMon so I don't know for sure, but I think it needs to be run as the logged in user, hence why it was designed to be called by a LaunchAgent. But you're correct that launchctl unload can be run by any user to unload a global LaunchAgent, so its not a guarantee it will be running.
As for the error you ran into, have you tried using the launchctl bsexec method I mentioned above. Not certain it will get past the error or not, but worth a try.
@May Hmmm, who knew screensaver would interfere with user launchd loading? Perhaps bsexec would get past that? Otherwise why not have ADPassMon load as a login policy? That works for me...
You could use the bits of the script above to detect if the user ID is above 1024 (or whatever threshold makes sense) so you know the user is an ActiveDirectory user and not local. Then use the su command to fire off an "open /path/to/ADPassMon.app" as the console user? That'll work.
And if they happen to quit it, well that's their darn fault!
You could always have a Self Service policy to open ADPassMon using the same console user detection routines and "open" as the console user, if ADPAssMon is squirreled away someplace non-obvious to the user (usually that's... beyond the Desktop :)
Here is what I use for my launchagent which we put in /Library/LaunchAgents/ It makes sure that ADPassMon cannot be quit which sounds like what you're trying to achieve.
<?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>com.company.adpassmon</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Utilities/ADPassMon.app/Contents/MacOS/ADPassMon</string>
</array>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
Not sure if it helps.
@bpavlov Getting a LaunchAgent installed with KeepAlive is easy. Preventing users from locating and disabling that LaunchAgent is a different story.
A user (with the right knowledge) can run:
launchctl unload /Library/LaucnhAgents/com.company.addpassmon.plist
Note no 'sudo' required to unload it.


@mm2270 @yr_joelbruner @bpavlov
It looks like it's not possible to get a LaunchDaemon to launch the application.
I think i've found the reason for the LaunchAgent occasionally not loading
(blaming the screensaver was a late afternoon caffeine fuelled assumption!)
The LaunchAgent that i was using to keep the app alive was created by Launch Control,
comparing @bpavlov's LaunchAgent i noticed the Launch Control created one uses /usr/sbin/open and
the other uses the path to the binary.
The load command has been working 100% so far with the latter LaunchAgent,
the one question i have is are there any implications with launching the application directly from the binary rather than the Application itself?
Thanks for all your input!
@mm2270 Sure, that's true in a lot of situations. I'm not sure there's a better method that isn't too convoluted though.
@May A launch daemon runs as root whereas launch agents run as the user. LaunchDaemons will run immediately upon boot up and do not require someone to log in whereas LaunchAgents do. You shouldn't run into any problems running the application directly from the binary as far as I'm aware. I've been running it this way for quite a few months now. But perhaps @bentoms can provide more insight.
@yr_joelbruner do you realise you can run
stat -f%Su /dev/console
instead of
eval $(stat -s /dev/console); id -un $st_uid
@sean Like I said "(there's a one liner somewhere on jamfnation! : )" and like manna from heaven here it is... Very nice thanks gets rid of an unneeded eval
And to expound on what has been revealed:
The -f flag specifies formatted output, the capital S further specifies "string" output, without it the output is numerical
consoleUsername=$(stat -f %Su /dev/console)
consoleUserID=$(stat -f %u /dev/console)
To apply it all into script to run in a login policy (perhaps this could work for you @May?)
#!/bin/bash
consoleUserID=$(stat -f %u /dev/console)
#user ID is sufficiently high to denote Active Directory
if [ "$consoleUserID" -gt 1024 ]; then
consoleUsername=$(stat -f %Su /dev/console)
su $consoleUsername -c "open /Applications/ADPAssMon.app"
fi
Finally got it working how i want using a combination of all your inputs, thank you!!
@yr_joelbruner @mm2270 @bpavlov @sean
using this LaunchAgent
(preferred this method as it played nicer with launchctl load and also launching via the binary quits the app when the agent is unloaded)
<?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>KeepAlive</key>
<true/>
<key>Label</key>
<string>local.keepAlive.ADPassMon</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/open</string>
<string>-W</string>
<string>/Applications/ADPassMon.app</string>
</array>
</dict>
</plist>
This script to initially load it, then used in a policy to re-load it if neccessary (smart group based on EA at bottom)
#!/bin/sh
loggedInUser=$( stat -f%Su /dev/console )
loggedInPID=$( ps -axj | awk "/^$loggedInUser/ && /Dock.app/ {print $2;exit}" )
(/bin/launchctl bsexec "${loggedInPID}" sudo -iu "${loggedInUser}" "launchctl load /Library/LaunchAgents/local.adpassmon.job.plist")
Extension attribute to see if Agent is loaded
#!/bin/sh
loggedInUser=$( stat -f%Su /dev/console )
loggedInPID=$( ps -axj | awk "/^$loggedInUser/ && /Dock.app/ {print $2;exit}" )
agentloaded=$(/bin/launchctl bsexec "${loggedInPID}" sudo -iu "${loggedInUser}" "launchctl list | grep local.keepAlive.ADPassMon")
#if string is not empty
if [ -n "${agentloaded}" ]; then
echo "<result>Loaded</result>"
else
echo "<result>not loaded or installed</result>"
fi
Coffee time!
FWIW, ADPassMon needs to be run as the user.
It has various calls to things like $USER which would be unhappy if ran via root.
It's great you've found a solution & that you're using ADPassMon
just wanted to chime in on the user running the app.