How to scope a script policy to devices which ran another policy?

atomczynski
Valued Contributor

Currently I have two policies in Self Service and I'm looking for a way to install automatically.
Policy A: Installs Google Chrome
Policy B: Allows Google Chrome to self update

As an automated install, how can I create a scope of devices which ran Policy A?

Thank you.

7 REPLIES 7

hdsreid
Contributor III

will a smart group containing devices with Chrome installed not do the trick?

atomczynski
Valued Contributor

Chrome is already installed, but older versions (from different sources, some auto update, some do not).

PeterG
Contributor II

Dummy receipts. The first policy can drop a receipt somewhere and the second policy will be scoped on 'if the receipt exists. (smartgroup)
The second policy can delete the dummy receipt to 'clean up' after executing.

larry_barrett
Valued Contributor

Step one: Make an extension attribute

#!/usr/bin/python
import json
import urllib2
import os.path
import plistlib

url = 'http://omahaproxy.appspot.com/all.json'
resp = urllib2.urlopen(url)


data = json.loads(resp.read())

for each in data:
    if each.get("os") == "mac":
        versions = each.get("versions")
        for version in versions:
            if version.get("channel") == "stable":
                latest = (version.get("current_version"))
                print latest

print os.path.exists("/Applications/Google Chrome.app")

plistloc = "/Applications/Google Chrome.app/Contents/Info.plist"

pl = plistlib.readPlist(plistloc)
pver = pl["CFBundleShortVersionString"]
print pver

if latest == pver:
        print "<result>Latest</result>"

else:
        print "<result>Old</result>"

Step 2: Make 3 Smart Groups:
1) Google Chrome Current (where extension attribute = current)
2) Google Chrome - Needs Update (where extension attribute is NOT = current)
3) Google Chrome - Missing

Scope your update script to group 2, your install to group 3. Here's the update script:

#!/usr/bin/env python
# encoding: utf-8
"""
chrome-enable-autoupdates.py
This script enables system wide automatic updates for Google Chrome.
It should work for Chrome versions 18 and later. No configuration needed
as this is originally intended as a munki postinstall script.
Created by Hannes Juutilainen, hjuutilainen@mac.com

History:

2019-08-05, Andy Duss - Fix keystone_registration_framework_path to point to correct directory 2017-09-01, Hannes Juutilainen - Ignore errors when installing keystone 2015-09-25, Niklas Blomdalen - Modifications to include old KeystoneRegistration installation (python version) 2014-11-20, Hannes Juutilainen - Modifications for Chrome 39 2012-08-31, Hannes Juutilainen - Added --force flag to keystone install as suggested by Riley Shott 2012-05-29, Hannes Juutilainen - Added more error checking 2012-05-25, Hannes Juutilainen - Added some error checking in main 2012-05-24, Hannes Juutilainen - First version """ import sys import os import subprocess import plistlib from distutils.version import LooseVersion chrome_path = "/Applications/Google Chrome.app" info_plist_path = os.path.realpath(os.path.join(chrome_path, 'Contents/Info.plist')) brand_path = "/Library/Google/Google Chrome Brand.plist" brand_key = "KSBrandID" tag_path = info_plist_path tag_key = "KSChannelID" version_path = info_plist_path version_key = "KSVersion" class Usage(Exception): def __init__(self, msg): self.msg = msg def chrome_installed(): """Check if Chrome is installed""" if os.path.exists(chrome_path): return True else: return False def chrome_version(): """Returns Chrome version""" info_plist = plistlib.readPlist(info_plist_path) bundle_short_version = info_plist["CFBundleShortVersionString"] return bundle_short_version def chrome_update_url(): """Returns KSUpdateURL from Chrome Info.plist""" info_plist = plistlib.readPlist(info_plist_path) update_url = info_plist["KSUpdateURL"] return update_url def chrome_product_id(): """Returns KSProductID from Chrome Info.plist""" info_plist = plistlib.readPlist(info_plist_path) product_id = info_plist["KSProductID"] return product_id def keystone_registration_framework_path(): """Returns KeystoneRegistration.framework path""" if LooseVersion(chrome_version()) >= LooseVersion("76"): keystone_registration = os.path.join(chrome_path, 'Contents', 'Frameworks') keystone_registration = os.path.join(keystone_registration, 'Google Chrome Framework.framework') keystone_registration = os.path.join(keystone_registration, 'Frameworks', 'KeystoneRegistration.framework') keystone_registration = os.path.join(keystone_registration, 'Versions', 'Current') elif LooseVersion(chrome_version()) >= LooseVersion("75") and LooseVersion(chrome_version()) < LooseVersion("76"): keystone_registration = os.path.join(chrome_path, 'Contents/Frameworks/') keystone_registration = os.path.join(keystone_registration, 'Google Chrome Framework.framework/Versions') keystone_registration = os.path.join(keystone_registration, chrome_version()) keystone_registration = os.path.join(keystone_registration, 'Frameworks/KeystoneRegistration.framework') else: keystone_registration = os.path.join(chrome_path, 'Contents/Versions') keystone_registration = os.path.join(keystone_registration, chrome_version()) keystone_registration = os.path.join(keystone_registration, 'Google Chrome Framework.framework') keystone_registration = os.path.join(keystone_registration, 'Frameworks/KeystoneRegistration.framework') return keystone_registration def keystone_install(): """Install the current Keystone""" install_script = os.path.join(keystone_registration_framework_path(), 'Resources/ksinstall') if not os.path.exists(install_script): install_script = os.path.join(keystone_registration_framework_path(), 'Resources/install.py') keystone_payload = os.path.join(keystone_registration_framework_path(), 'Resources/Keystone.tbz') if os.path.exists(install_script) and os.path.exists(keystone_payload): ksinstall_process = [ install_script, '--install', keystone_payload, '--force' ] p = subprocess.Popen(ksinstall_process, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (results, error) = p.communicate() if results: print results if p.returncode != 0: if error: print >> sys.stderr, "%s" % error print >> sys.stderr, "Keystone install exited with code %i" % p.returncode # Since we used --force argument, succeed no matter what the exit code was. return True else: print >> sys.stderr, "Error: KeystoneRegistration.framework not found" return False def register_chrome_with_keystone(): """Registers Chrome with Keystone""" ksadmin = "/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/MacOS/ksadmin" if os.path.exists(ksadmin): ksadmin_process = [ ksadmin, '--register', '--productid', chrome_product_id(), '--version', chrome_version(), '--xcpath', chrome_path, '--url', chrome_update_url(), '--tag-path', tag_path, '--tag-key', tag_key, '--brand-path', brand_path, '--brand-key', brand_key, '--version-path', version_path, '--version-key', version_key ] p = subprocess.Popen(ksadmin_process, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (results, error) = p.communicate() if error: print >> sys.stderr, "%s" % error if results: print results if p.returncode == 0: return True else: return False else: print >> sys.stderr, "Error: %s doesn't exist" % ksadmin return False def main(argv=None): if argv is None: argv = sys.argv try: # Check for root if os.geteuid() != 0: print >> sys.stderr, "This script must be run as root" return 1 if not chrome_installed(): print >> sys.stderr, "Error: Chrome is not installed on this computer" return 1 if keystone_install(): print "Keystone installed" else: print >> sys.stderr, "Error: Keystone install failed" return 1 if register_chrome_with_keystone(): print "Registered Chrome with Keystone" return 0 else: print >> sys.stderr, "Error: Failed to register Chrome with Keystone" return 1 except Usage, err: print >> sys.stderr, err.msg print >> sys.stderr, "for help use --help" return 2 if __name__ == "__main__": sys.exit(main())

And a picture of my policy

7992505616ac4fd2bf675fda49587b4a

Once in a while the policy will fail. Its pretty rare. Just go to logs in the policy and sort by status to see if any failed. Just flush them and it will try the next time.

4a6f0784a3bf4ba18eb51639e8764447

mm2270
Legendary Contributor III

It's not necessary to make either a dummy receipt or an Extension Attribute for this. If Policy A is installing a product, like Chrome, and it's a package you built, like in Composer, you can set up a custom package identifier for your Composer source (one gets assigned automatically to it), which gets collected in Jamf Pro, assuming you collect inventory at the end of Policy A. And you can then set up a Smart Group to look for that Package identifier. Use the "Packages Installed by Casper" (yes, legacy criteria name) option, and then click the (…) button to bring up a list of package identifier names. Assuming it was installed on at least one Mac in your managed fleet, it should show up there to select it, or you can punch it in manually if you know the full identifier name. Here's an example of a package I created a little while back of Google Chrome - Google_Chrome_77.0.3865.120.pkg
Once that criteria is added in, and any others you may need to define the Smart Computer Group, you should be able to scope that group to Policy B.

The dummy receipt and Extension Attribute method is definitely valid, and will also work, but it's really only needed for policies that don't actually install a package, like ones that only run a script or create an account or something to that effect.

atomczynski
Valued Contributor

The previous admin would periodically download and package Chrome using Composer. The technicians would also download current version and install manually. I'm looking to simplify and install current stable version.

I'm using the following script to download and install Google Chrome from the stable branch.

#####################################################################################################
#
# ABOUT THIS PROGRAM
#
# NAME
#   GoogleChromeInstall.sh -- Installs the latest Google Chrome version
#
# SYNOPSIS
#   sudo GoogleChromeInstall.sh
#
####################################################################################################
#
# HISTORY
#
#   Version: 1.0
#
#
####################################################################################################
# Script to download and install Google Chrome.
# Only works on Intel systems.
dmgfile="googlechrome.dmg"
volname="Google Chrome"
logfile="/Library/Logs/GoogleChromeInstallScript.log"
url='https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg'
# Are we running on Intel?
if [ '`/usr/bin/uname -p`'="i386" -o '`/usr/bin/uname -p`'="x86_64" ]; then
        /bin/echo "--" >> ${logfile}
        /bin/echo "`date`: Downloading latest version." >> ${logfile}
        /usr/bin/curl -s -o /tmp/${dmgfile} ${url}
        /bin/echo "`date`: Mounting installer disk image." >> ${logfile}
        /usr/bin/hdiutil attach /tmp/${dmgfile} -nobrowse -quiet
        /bin/echo "`date`: Installing..." >> ${logfile}
        ditto -rsrc "/Volumes/${volname}/Google Chrome.app" "/Applications/Google Chrome.app"
        /bin/sleep 10
        /bin/echo "`date`: Unmounting installer disk image." >> ${logfile}
        /usr/bin/hdiutil detach $(/bin/df | /usr/bin/grep "${volname}" | awk '{print $1}') -quiet
        /bin/sleep 10
        /bin/echo "`date`: Deleting disk image." >> ${logfile}
        /bin/rm /tmp/"${dmgfile}"
else
    /bin/echo "`date`: ERROR: This script is for Intel Macs only." >> ${logfile}
fi
exit 0

and this script to allow Chrome to self update

# This script enables system wide automatic updates for Google Chrome.




# Written by Ryan Ball after somehow becoming responsible for it

# Spawned from this thread: https://www.jamf.com/jamf-nation/discussions/23323/how-to-update-chrome-automatically

# Bash version of this script (which I also contributed a tiny piece to):
#   https://github.com/hjuutilainen/adminscripts/blob/master/chrome-enable-autoupdates.py

chromePath="/Applications/Google Chrome.app"
chromeVersion=$(/usr/bin/defaults read "$chromePath/Contents/Info.plist" CFBundleShortVersionString)
chromeMajorVersion=$(/usr/bin/awk -F '.' '{print $1}' <<< "$chromeVersion")
updateURL=$(/usr/bin/defaults read "$chromePath/Contents/Info.plist" KSUpdateURL)
productID=$(/usr/bin/defaults read "$chromePath/Contents/Info.plist" KSProductID)
exitCode="0"

# Check if Chrome is installed
if [[ ! -e "$chromePath" ]]; then
    echo "Error: $chromePath not found"
    exit 1
fi

# Determine KeystoneRegistration.framework path
if [[ $chromeMajorVersion -ge 75 ]] ; then
    frameworkPath="$chromePath/Contents/Frameworks/Google Chrome Framework.framework/Versions/Current/Frameworks/KeystoneRegistration.framework"
    resourcesPath="$chromePath/Contents/Frameworks/Google Chrome Framework.framework/Versions/Current/Resources"
else
    frameworkPath="$chromePath/Contents/Versions/$chromeVersion/Google Chrome Framework.framework/Versions/A/Frameworks/KeystoneRegistration.framework"
    resourcesPath="$chromePath/Contents/Versions/$chromeVersion/Google Chrome Framework.framework/Versions/A/Resources"
fi

# Check if framework exists
if [[ ! -e "$frameworkPath" ]]; then
    echo "Error: KeystoneRegistration.framework not found"
    exit 1
fi

# Install the current Keystone
if ! "$frameworkPath/Resources/ksinstall" --install "$frameworkPath/Resources/Keystone.tbz" --force 2>/dev/null ; then
    exitCode="$?"
    echo "Error: Keystone install failed with code $exitCode"
    exit "$exitCode"
else
    echo "Keystone installed"
fi

# Registers Chrome with Keystone
if ! /Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/MacOS/ksadmin 
    --register 
    --productid "$productID" 
    --version "$chromeVersion" 
    --xcpath "$chromePath" 
    --url "$updateURL" 
    --tag-path "$chromePath/Contents/Info.plist" 
    --tag-key "KSChannelID" 
    --brand-path "/Library/Google/Google Chrome Brand.plist" 
    --brand-key "KSBrandID" 
    --version-path "$chromePath/Contents/Info.plist" 
    --version-key "KSVersion"
then
    exitCode="$?"
    echo "Error: Failed to register Chrome with Keystone - code $exitCode"
    exit "$exitCode"
else
    echo "Registered Chrome with Keystone"
fi

exit 0

mm2270
Legendary Contributor III

Ah, well in that case you'll need to resort to using a dummy receipt and Extension Attribute to capture all this.

You may already know this, but there has been a lot of discussion lately on the safety in using scripts that just download applications and/or installers from sources using curl on other threads, like this one. If you haven't already, I'd advise at least looking those points over to be aware of the possible security implications of doing this. Always good to be in the know.