Python Script Help - run osascript as another user

mbezzo
Contributor III

Hi All,
Hoping someone has already solved this problem and is willing to share! I'm reworking a great python script from @glee_edin (thanks!) (GitHub link) and need to run osascript as the logged in user. I've never done anything in python before, so I'm coming up a little short. I've got the function looking like this so far: (ignore the bad quoting - the forum isn't parsing it correctly)

#!/usr/bin/python

def user_wants_to_defer(defer_until, updates):
    answer = """
        osascript -e '
            tell application "System Events"
                with timeout of 8947878 seconds
                set result to (display dialog "The following updates are available and require a restart:
%s

You can choose to restart and install them now, or delay until: %s

Updates will automatically be installed at that time if they haven't already been installed." with title "Required macOS Updates Available" with icon ("path/to/icon.icns" as POSIX file) buttons {"Restart Later", "Restart Now"} default button "Restart Now")
                end timeout
            end tell
            if button returned of result = "Restart Later" then
                return "2"
            else
                return "0"
            end if
        '
        """ % (updates, defer_until.strftime("%H:%M on %a, %d %b"))

in bash I'd just do this:

#!/bin/sh
su - "${loggedInUser}" -c osascript -e <<EOT
     do stuff here
EOT

Anybody know of something similar? Hopefully this makes sense... :)

Thanks!
att

18 REPLIES 18

glee_edin
New Contributor

Hello! Ah, yes - this is a change I'd like to make myself actually, and remove the dependency on jamfhelper.

You can do something like this in python:

 from subprocess import Popen, PIPE
    script = """tell application "Finder"
                activate
                   display dialog "Hello!"
               end tell
             """
    proc = Popen(['sudo', '-u', current_user(), 'osascript', '-'],
                        stdin=PIPE,
                        stdout=PIPE)
    out = proc.communicate(script)[0]

(edited to include subprocess imports)

glee_edin
New Contributor

Btw, I am significantly reworking the script at the moment:

https://github.com/UoE-macOS/jss/pull/12

if you're interested in contributing I'd be very happy to take a pull request!

mbezzo
Contributor III

Awesome, thank you!

donmontalvo
Esteemed Contributor III

@mbezzo If you're tossing up a list to select from, might want tot stick with System Events (as opposed to Finder) so you retain focus.

--
https://donmontalvo.com

mbezzo
Contributor III

Thanks, @donmontalvo I use System Events in all of my scripts just for that reason! :)

And @glee_edin, this is LITERALLY my first time working with Python - you definitely don't want me working on that script yet! That being said, I really like the script and want to get it working with osascript, so I'm going to keep chipping away at it. If you do continue to work on it, feel free to comment the crap out of it and/or get osascript working for a pop up and I'll gladly test it out/provide feedback!

Thanks all,
Matt

glee_edin
New Contributor

@mbezzo - you might like this:

https://github.com/UoE-macOS/jss/blob/master/coreconfig-softwareupdate-run.py

I've converted the user interaction to use osascript, except when we install unattended updates, where I still need to use JamfHelper to lock out the screen (actually, now I think about it, I think there's a utility as part of ARD that can do the same thing...)

Please feel free to open issues if you think of any improvements that you're not comfortable implementing yourself. The script is a bit of a mess - one day soon I'll clean it up to pass pylint and do some refactoring

mbezzo
Contributor III

Oooo, awesome!!! THANK YOU! Hoping to spend some time on this today. :)

mbezzo
Contributor III

@glee_edin,
Alright, Trying your new script as is (so I can see how it works first) but I'm not getting a pop-up, here's the terminal output (I'm just hard coding the variables nomrally passed by the JSS):

SEA-VM-BEZZTST1:~ bezzo$ sudo python /Users/bezzo/Desktop/NewSoftwareUpdates 2.py 
Password:
Checking for updates
Processing macOS High Sierra 10.13.2 Supplemental Update
Downloading macOS High Sierra 10.13.2 Supplemental Update- 
091-58876 is already downloaded
Setting up the updates index file
Setting up 091-58876 to install at logout
Setting updates to run on logout
Created deferral file - Ok to defer until 2018-01-30 08:41:32.933540
User elected to defer update
SEA-VM-BEZZTST1:~ bezzo$

Any ideas?

Thanks!
Matt

glee_edin
New Contributor

Ah, looks like things are different in 10.13:

osascript[940] <Error>: AppleEvents: received mach msg which wasn't complex type as expected in getMemoryReference.

The sudo trick might not work any longer (though if it works in bash, it ought to be ok in python). I've brought up a 10.13 test box now which has uncovered another couple of bugs. I'll let you know when I've got it working properly on 10.13.

mbezzo
Contributor III

Sounds good - REALLY appreciate your work on this!

mbezzo
Contributor III

FWIW, I recall some time ago that osascript's max timeout was 8947878 seconds - not sure if that's changed but in general that's what I'd recommend for "unlimited" timeouts in osascript.

mm2270
Legendary Contributor III

Since around 10.11 I've been using the launchctl asuser command to run commands as the user. This can apply to Applescript calls, but also to regular bash calls, such as running a security binary command as the logged in user to affect their login keychain. It generally works well.
I don't know how that would be incorporated into a python script though, since the syntax is going to be different from bash.

donmontalvo
Esteemed Contributor III

FWIW, our template for cases were we need to create a Launch Agent and launch an item as the current user...

#!/bin/bash

application="/Applications/MyApplication.app"
launchagent="/Library/LaunchAgents/com.company.myapplication.plist"

# Install Launch Agent

echo '<?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>Disabled</key>
    <false/>
    <key>Label</key>
    <string>com.company.myapplication</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Applications/MyApplication.app/Contents/MacOS/MyApplication</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <dict>
        <key>SuccessfulExit</key>
        <false/>
    </dict>
</dict>
</plist>' > "$launchagent"
/bin/chmod 644 "$launchagent"

# Launch it for the current user

current_user=$( 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 [[ $current_user ]]; then

    current_uid=$(id -u $current_user)
    /bin/launchctl bootstrap gui/"$current_uid" "$launchagent"

    # Older versions of OSX <= 10.9 don't respond to the above command, use the deprecated open command to launch on them

    if [[ $? -ne 0 ]]; then
        /bin/launchctl asuser "$current_uid" open "$application"
    fi

fi

exit 0
--
https://donmontalvo.com

glee_edin
New Contributor

Thanks all - just to update the thread:

  • Using sudo turns out to work fine (though I think the launchctl option is probably much better these days)
  • The problem was that the icon I was trying to display doesn't exist on 10.13

However, more testing on 10.13 has revealed that the mechanism I was using to set up updates to install on logout no longer works, at least for some updates, on 10.13. In particular, the 10.13.2 (or 3) combo update is a 'staged install' which, currently, I can't find any way of setting up to install on logout. This is really frustrating, as I can't think of another, clean, way to 'force' the user into installing updates after a deferral limit. I guess it can be done with a logout policy but I can't think how we'd throw up a progress indicator in that case.

So, I'm kinda back to the drawing board on this one ... thanks for all the interest and advice!

glee_edin
New Contributor

For completeness, here's a version that almost, but not quite, works on 10.13:

https://github.com/UoE-macOS/jss/blob/10.13/coreconfig-softwareupdate-run.py

One interesting thing I noticed is that it's no longer possible to 'kill -HUP' the softwareupdate daemon or helper processes. I guess SIP now prevents it, but that's also a bit of a pain as that proved to be necessary in 10.12 to get the helper to notice when we downloaded updates.

Sigh. Methinks I may be 'fighting the system' a bit too much on this one.

mbezzo
Contributor III
This is really frustrating, as I can't think of another, clean, way to 'force' the user into installing updates after a deferral limit. I guess it can be done with a logout policy but I can't think how we'd throw up a progress indicator in that case.

What if the dialog box that we pop up once the deferral limit has been hit simply runs:

cmd_with_timeout([ SWUPDATE, '-i', '-a' ], 3600)

I'm not sure exactly how the behavior on this works when a reboot is required, does it just reboot? or prompt the user to choose to restart? If that's the case we're back to square one.

Hmm, or maybe once the deferral limit has hit, we start the installs, wait for them to finish, then pop a dialog with "a restart now to complete install" that's not optional with timeout if no answer?

Just sort of thinking out loud..

Thanks,
Matt

abrahamT
New Contributor III

@mbezzo Who is launching the python script? I have similar code, but I'm using a flag file to determine how many deferrals are left before the command just runs to install this application we are deploying. I ask about who is running the script, because when JAMF runs the script, it's running as root so no need to sudo.

While a user is logged in, they still get the prompt without having to su as the user. Is that not happening for you? Do you have more code you can share that I could take a look at?

mbezzo
Contributor III

Hi @abrahamT,
I'm talking specifically about osascript - in my experience (I haven't actually retested in a couple years to be fair...) I've always had to run the osascript dialog creation as the currently logged in user to get the dialogs to show. Maybe that's no longer true? To answer your question specifically, the script is being run as root. I don't have any specific code other than what @glee_edin has provided - I haven't been able to dive back into this for the last couple of weeks sadly! Hope to soon.

If you're willing to share your code, I'd love to see it. I still haven't found precisely what I want yet - most things are too complex or too simple... I want it to be just right! ;)

Thanks,
Matt