Run Extension Attribute as logged in user and script advice

May
Contributor III

Hi all

I'm trying to create an Extension Attribute to report back the status of a Global LaunchAgent which is in /Library/LaunchAgents

When run from the user context it reports back correctly but when run as root it does not, i've gone through most of the dicussions and tried adding in ls -l /dev/console | cut -d " " -f4 then sudo -u to the logged in user but my script fu is weak.

Can any one advise how to make the script below run the launchctl list command as the logged in user as an extension attribute ?

Cheers,
Andy

#!/bin/sh

agentloaded=`launchctl list | grep local.keepAlive.ADPassMon | awk '{print $NF}'`


if [ "${agentloaded}" == "local.keepAlive.ADPassMon" ]; then

    echo "<result>Loaded</result>"

else

    echo "<result>not loaded or installed</result>"

fi
1 ACCEPTED SOLUTION

yr_joelbruner
New Contributor III

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

View solution in original post

16 REPLIES 16

mm2270
Legendary Contributor III

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.

yr_joelbruner
New Contributor III

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

May
Contributor III

Thanks @mm2270 @yr_joelbruner for your help,

It made for a great end to the week when it all came together!

May
Contributor III

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

jhbush
Valued Contributor II

@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.

May
Contributor III

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!

mm2270
Legendary Contributor III

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.

yr_joelbruner
New Contributor III

@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 :)

bpavlov
Honored Contributor

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.

mm2270
Legendary Contributor III

@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.

May
Contributor III

eebe38047ff04e6b8ba1e44b62dd2ef3
df76435a84e74929b8201868cb7dbcb9

@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!

bpavlov
Honored Contributor

@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.

sean
Valued Contributor

@yr_joelbruner do you realise you can run

stat -f%Su /dev/console

instead of

eval $(stat -s /dev/console); id -un $st_uid

yr_joelbruner
New Contributor III

@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

May
Contributor III

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!

bentoms
Release Candidate Programs Tester

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.