Run as console user....

nessts
Valued Contributor II

This post provided a way for us to launch stuff as the console user in 10.10 and older versions. However I am noticing that its not working quite as good on 10.11. Has anybody been playing with running stuff as the user on the new OS during setup or pushing updates out?

9 REPLIES 9

mm2270
Legendary Contributor III

Hmm. I haven't had the time to really play with 10.11 much yet. Have you also tried the python method posted by Jacob on that thread? Or is that actually what you were referring to up above? I seriously hope our ability to run things as the logged in user will not be completely shut down in 10.11 or that's going to put a real damper on stuff.
Maybe time to upvote this FR some more to see if JAMF can assist with this.

sean
Valued Contributor

This won't work in 10.11. You'll need to change the way you do this. Try this process:

  1. Create a /Library/LaunchAgent pointing to a script.
  2. Load the daemon
  3. Reload the daemon

1) Start with a standard simple script, eg.

#!/bin/bash

/Applications/terminal-notifier.app/Contents/MacOS/terminal-notifier -message message -title title

exit 0

Create a LaunchAgent plist that points to this script and runs at load:

<?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.mydomain.test</string>
    <key>Program</key>
    <string>/[pathtoscript]/script.sh</string>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

2) As root, run the following:

#!/bin/bash

current_user=`stat -f%u /dev/console`

launchctl bootstrap gui/$current_user /Library/LaunchAgents/com.mydomain.test.plist

exit 0

3) To rerun the daemon run:

#!/bin/bash

current_user=`stat -f%u /dev/console`

launchctl kickstart gui/$current_user/com.mydomain.test

exit 0

Notice, that when you kickstart the process you refer to the label not the file.

Of course, you don't want a million different plists per message. You could have a preference plist that contains flags for messages that you read from the original script and based on reply, use a 'case' statement to provide the relevant message. Just remember, as it is set to RunAtLoad, then it will run at first login, so you'll want a reset: perhaps at logout, check process time of loginwindow, etc and less than x default message or no message.

mm2270
Legendary Contributor III

@sean That seems like a good process. Its reminiscent of something I had put together specifically for calling terminal-notifier some time back, before I found out how to use launchctl bsexec. So we may need to revisit this process once the Captain cometh.

The process I was using looked like this.

Have a LaunchAgent in /Library/LaunchAgents/ that had a WatchPath of a local script file. The script file was initially an empty file and the LaunchAgent did not have the RunAtLoad key enabled. It only ran when the watched file was modified.
A separate script run from Casper would write out my actual script into the WatchPath'ed script file. IOW, I echoed the contents of my terminal-notifier script, complete with variables being pulled down from Casper's parameter assignments, into the script file. As soon as the LaunchAgent saw the change it would fire and run the script, displaying the message. Included in the actual script contents was a final line (after a couple of seconds pause) that would empty the script again by doing echo "" > /path/to/script.sh So, after it created and ran the script, it would wait for it to run, then empty the contents again so it couldn't be called by accident later. This actually worked nicely, but as it involved needing a LaunchAgent deployed to every Mac and for said Agent to remain active, I ended up tabling it in favor of the launchctl bsexec method.

I may brush off this process and test it against 10.11 to see how it fares. I can't see why it would not work. Its using a standard LaunchAgent process to run the script as the logged in user.

nessts
Valued Contributor II

I will write a test up today and see if it works thanks.

nessts
Valued Contributor II

@sean thank you, that works great. Here is a perl sub-routine to do this all in line and only once... in case anybody is interested.

# runs a command as the console user
# in: command
# out: $rc
sub runAsConsoleUser {
    my $consoleuser = getConsoleUser();
    my $uid = getpwnam($consoleuser);
    my $command = shift;
    (my $name = $command) =~ s#.*/##;
    $name =~ s#s##g;
    my $la = "/Library/LaunchAgents/com.hpe.temp$name.plist";
    my $rc;
    if ((defined $consoleuser) && (defined $command)) {
        syslog('notice', "Running %s as uid %d on console", $command, $uid);
        print "opening $la
";
        open LA, ">$la" or die "$progname: launch agent: $!
";
        print LA "<?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.hpe.temp$name</string>
            <key>Program</key>
            <string>$command</string>
            <key>RunAtLoad</key>
            <true/>
        </dict>
        </plist>";
        close LA;
        $rc = system("launchctl bootstrap gui/$uid "$la"");
        syslog('notice', "return code from process %s is %d", $command, $rc);
        if ($rc) {
            syslog('notice', "return code from bootstrap %d, trying kickstart", $rc);
            $rc = system("launchctl kickstart gui/$uid/com.hpe.temp$name");
        }
        unlink $la;
        return $rc;
    }
    else {
        syslog('notice', "Something Not defined in properly cannot run as user");
    }
}

sean
Valued Contributor

Yeah, thought about a WatchPath to, just depends on individual application.

Of course, the script itself could deny a run.

Eg.

#!/bin/bash

defaults read /Library/Preferences/com.mycompnay.terminal-notifier ShouldIRun

if TRUE.....

run stuff

defaults write /Library/Preferences/com.mycompnay.terminal-notifier ShouldIRun -bool FLASE

fi

exit 0

Example plist below. Run trigger script that re-runs the LaunchAgent and sets the MessageType, e.g.

/[pathtoscript]/script.sh ASUSMessage
#!/bin/bash

current_user=`stat -f%u /dev/console`
the_message_type="$1"

defaults write /Library/Preferences/com.mycompnay.terminal-notifier MessageType $the_message_type

launchctl kickstart gui/$current_user/com.mydomain.test

exit 0

Then the script in the LaunchAgent reads the MessageType, reads the relevant string and then applies that to the '-message' option of terminal notifier, e.g..

#!/bin/bash

run_check=`defaults read /Library/Preferences/com.mycompnay.terminal-notifier ShouldIRun`

if [ $run_check = 1 ]
then
        my_message_type=`defaults read /Library/Preferences/com.mycompnay.terminal-notifier MessageType`
        my_message=`defaults read /Library/Preferences/com.mycompnay.terminal-notifier $my_message_type`

        /Applications/terminal-notifier.app/Contents/MacOS/terminal-notifier -message "$my_message" -title "Company Message"

        defaults write /Library/Preferences/com.mycompnay.terminal-notifier ShouldIRun -bool FLASE
fi

exit 0
# cat /Library/Preferences/com.mycompnay.terminal-notifier.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>MessageType</key>
        <string>ASUSMessage</string>
        <key>ASUSMessage</key>
        <string>Software updates ready</string>
        <key>PasswordExpiry</key>
        <string>Please change your password</string>
        <key>ShouldIRun</key>
        <true/>
</dict>
</plist>

RunAtLoad can stay as yes and at login nothing will happen, unless you set the 'ShouldIRun' flag to TRUE. This way you can trigger a message event at the login window that won't activate until the user logs in, whilst a WatchPath wouldn't provide a message whilst at login window (which may of course be your desired effect).

A better method would be to create the MessageType as an array, then you could stack messages, but the above provides the basics.

Essentially, change the trigger script to accept multiple entries, you could use a while loop with 'shift' and PlistBuddy to populate. Then use PlistBuddy in the LaunchAgent script, you could read item 0 of the array and then delete item 0 - repeat until item 0 is non-existent.

b3nnb
New Contributor

Has anyone tried running terminal-notifier this way in 10.11.4 yet? following the launchctl option i am getting this error

Script result: Could not find service "com.company.launchnotification" in domain for login: 100019

wondering if anyone has any knowledge of this or a work around.

mm2270
Legendary Contributor III

@b3nnb You may want to read through this thread. https://jamfnation.jamfsoftware.com/discussion.html?id=9902
In particular look at the posts near the bottom where @Josh.Smith mentions the right way to use launchctl as of 10.11 for running commands as the user. This method has been working very well for me on any systems at 10.10 and up.

donmontalvo
Esteemed Contributor III

Worthwhile read...

LAUNCHCTL 2.0 SYNTAX
https://babodee.wordpress.com/2016/04/09/launchctl-2-0-syntax/

--
https://donmontalvo.com