Mavericks Wallpaper - the bash version

franton
Valued Contributor III

Where I work has two separate desktop background policies: a standard one for all people and a custom one for certain departments. 10.9 really threw some spanners in the works when Apple shifted from MCX to their sqlite3 database to control this.

However thanks to being persistently bugged by Casper warning email messages around 50+ times per day, I think I have it! I should point out there are solutions out there for this, but they're mainly for the Munki crowd and written in python and in one case, Ruby. I am not a Ruby man. Or Python.

Anyway I like the idea of as few policies to do the job as possible. Also, as few scripts as possible too. So before I release this on my github ... here's what I have.

#!/bin/bash

# Script to set desktop background for students

# Author : r.purves@arts.ac.uk
# Version 1.0 : Initial Version
# Version 1.1 : 23/04/2014 - Massive reworking to use applescript for 10.8 and below, modify the db for 10.9+
# Version 1.2 : 24/04/2014 - Removed applescript because of osascript parsing issues. replaced with mcx.

OSversion=$( sw_vers | grep ProductVersion: | cut -c 20-20 )
currentuser=$( ls -l /dev/console | awk '{print $3}' )

if [ "$4" = "custom" ];
then

if [[ "$OSversion" -ge "9" ]];
then

sqlite3 /Users/$currentuser/Library/Application Support/Dock/desktoppicture.db << EOF
UPDATE data SET value = "/Users/Shared/Background/custombg.jpeg";
.quit
EOF

killall Dock

else
defaults write com.apple.desktop Background '{default = {ImageFilePath = "/Users/Shared/Background/custombg.jpeg"; };}'
killall Dock
fi

else

if [[ "$OSversion" -ge "9" ]];
then

sqlite3 /Users/$currentuser/Library/Application Support/Dock/desktoppicture.db << EOF
UPDATE data SET value = "/Library/Desktop Pictures/default_grey2560x1600.jpeg";
.quit
EOF

killall Dock

else
defaults write com.apple.desktop Background '{default = {ImageFilePath = "/Library/Desktop Pictures/default_grey2560x1600.jpeg"; };}'
killall Dock
fi

fi
exit 0

A little explanation as I haven't finished annotating my code yet. If you put the word "custom" in parameter 4 when calling the script, you'll get the custom background file or else you get the standard one.

For 10.8 computers and under, the script setting is done through an osascript call which has been detailed on many other posts on jamfnation. Since Casper scripts run as root, i'm using "su -l" to run the command as the currently logged in user which seems to help A LOT.

For 10.9 computers and above, i'm directly manipulating the currently logged in user's desktoppictures.db file and reprogramming ALL entries in the data table. To force the change through, I'm killing the dock process. It's not the most seamless process in the world, but it does work. This has not been tested for multi monitor support but it should theoretically work.

Credits and kudos for providing the information to make this all work go to the following:
http://1klb.com/blog/desktop-background-on-os-x-109-mavericks.html for his work on the database file side of things
/url">@rtrouton][/url for his work located at [http://derflounder.wordpress.com/2013/10/26/mavericks-desktop-background-picture-settings-moved-from-librarypreferencescom-apple-desktop-plist/

1 ACCEPTED SOLUTION

franton
Valued Contributor III

Dear All,

I've modified the script above to remove the osascript stuff and replaced with the legacy mcx method.

View solution in original post

30 REPLIES 30

franton
Valued Contributor III

Please also take extreme note of the fact that the database file does NOT need the spaces in the file path escaped whereas the Applescript version does!

scottb
Honored Contributor

Kudos! Something we've been ignoring but I will try this for sure. Thanks for posting it up!

franton
Valued Contributor III

@boettchs You're very welcome! It's obviously very specific to my own requirements but it should be easily tailored to yours. Obviously test like crazy!

Not applicable

This looks great - thanks for posting.

mm2270
Legendary Contributor III

Thanks for posting. Although we don't have a need to adjust or manage the desktop picture here, I think the general model of this script could be adapted to work with other sqlite databases that Apple is using (which there seem to be more of with each new OS)

Also just wanted to quickly mention that your "OSversion" line could be simplified to just this-

sw_vers -productVersion | cut -d. -f2

gregneagle
Valued Contributor

This is clever.

But an advantage of Python scripts like this one: https://github.com/grahamgilbert/macscripts/tree/master/set_desktops is that they use documented Apple APIs and therefore are likely to continue to work even if Apple changes how the data is stored.

You don't even have to understand the Python - you can just use it -- in a Bash script if you want. In that case it's really no different than calling the `sqlite3` command from inside a Bash script (and less complicated, to boot):

#!/bin/bash
/path/to/set_desktops.py --path /Users/Shared/Wallpapers/my_wallpaper.png

freddie_cox
Contributor III

Thanks everyone for their input and discussion on this topic.

I have been putting off desktop background update/maintenance as we drift away from MCX. Also as we get closer to a widespread 1:1 student deployment I don't worry (or care) as much about those student's desktop backgrounds being managed. However, I do like having a unified look in our remaining labs. This discussion will give me a place to start!

franton
Valued Contributor III

Thank you @gregneagle !

I actually investigated Graham's python script but i'm in the situation where our version of Casper doesn't really deal with python scripts too well. If it had, I would have used it instead. Storing scripts and other utilities on the desktop clients is sadly also right out for us. I won't bore you with the details as to why, suffice to say it's not a technical issue.

@mm2270 That's a good point but I was hacking this thing up quickly. Refinement comes later once it works ;)

franton
Valued Contributor III

Dear All,

I've modified the script above to remove the osascript stuff and replaced with the legacy mcx method.

wmateo
Contributor

cant this all work with a simple configuration profile? and the com.apple.desktop plist?

franton
Valued Contributor III

No as desktop wallpapers are set in a database file instead of the plist now.

alex_drinkwater
New Contributor

Hi Franton,

Thanks very much for posting this.
I'm trying to get something based on it to work, but getting odd errors. Here's the script:

#!/bin/bash
# Set Mavericks Desktop Picture for user.
# Based on script https://jamfnation.jamfsoftware.com/discussion.html?id=10418
# Run from Login hook
# Alex Drinkwater 2014

currentuser=$3
picturepath=/Library/Desktop Pictures/backgroundDefault.jpg

if [ ! -z "$4" ]
then
    if [ -f "$4" ]
    then
        picturepath="$4"
    fi
fi

sudo sqlite3 /Users/$currentuser/Library/Application Support/Dock/desktoppicture.db << EOF
UPDATE data SET value = "$picturepath";
.quit
EOF

killall Dock

exit 0

It's odd. I've been testing it by running it in the terminal, supplying the relevant arguments.

It seems to work on some machines, and not on others.

All the machines were built from the same image, and have the same policies applied.

I've tried running the script from local admin and network-authenticated non-admin accounts. On the machines where the script works, it works for both. On the other machines, it doesn't work for either.

Anyone any clues about what might be going wrong?

Alex

alex_drinkwater
New Contributor

It's worth noting that if you don't need to conditionally change the desktop picture, but just want to be able to preset a particular background, it seems from my brief research, to be possible simply to setup the desktop picture as you want for a model user, and capture, package and deploy the desktoppicture.db to other users and machines via a policy. Seems to work on machines with identical monitors, at least.

If you do need to change the picture, you could make a package that conditionally installed one desktoppicture.db file or another for each user.

a.

andysemak
Contributor

please excuse the n00bness.

In one area of our University, they require a different wallpaper to be used to the one that is deployed as part of our base image.

i use the bash script above to force this change at logon and it works very well. the users have now asked that a different wallpaper is used for 2 accounts that are created via policy post install. How can I adapt this script so that when the user logs in with either the 'graphicsadmin' or 'edit' accounts they get a different wallpaper to the one that is used for all other accounts (local and ADS).

Many thanks,

Andy

franton
Valued Contributor III

Hi @andysemak ,

The trick is to modify the script to use a supplied username from Casper itself, then use different policies to do the script calling.

iamkmc
New Contributor III

I attempted to run @franton][/url 's script which is posted above after making a few changes. Got an error "unexpected end of file". Attempted to run the following commands below manually, and still wasn't able to get the wallpaper to stick:

sqlite3 /Users/localaccount/Library/Application Support/Dock/desktoppicture.db UPDATE data SET value = "wallpaper directory entered here";
.quit

killall Dock

Not sure what steps to take from here but editing the plist and this local database aren't working for me. Did anyone else have luck with another method?

franton
Valued Contributor III

Please look at my original example again. You'll notice I'm not escaping out the file path with a in the sql commands.

iamkmc
New Contributor III

I ran into the same error "unexpected end of file".

My current script:

#!/bin/bash

# Script to set desktop background for SoulCycle Studio Macs running 10.9 and older OS Versions of OS X by user

# Variables
OSversion=$( sw_vers -productVersion | cut -d. -f2 )
CurrentUser=$( ls -l /dev/console | awk '{print $3}' )
StudioWall="/Library/Desktop Pictures/SoulCycle_Retail_Wallpaper.jpg"
Plist="~/Library/Preferences/com.apple.desktop.plist"

# Code excution
if [[ "$OSversion" -ge "9" ]]; then sqlite3 /Users/$CurrentUser/Library/Application Support/Dock/desktoppicture.db << EOF UPDATE data SET value = "$StudioWall"; EOF

killall Dock

then rm -rf /Users/$CurrentUser/Library/Application Support/Dock/desktoppicture.db defaults write $Plist Background '{default = {ImageFilePath = "/Library/Desktop Pictures/SoulCycle_Retail_Wallpaper.jpg"; };}'

killall Dock

fi

exit 0

As a work around I'm packaging the desktopbackgroup.db as a dmg from a current configured user and deploying it with FUT.

franton
Valued Contributor III

Trying to do variable substitution will not work at you're not in a shell environment when you run those SQLite commands!

jthurwood
New Contributor III

Hi @franton

Have you managed to get this working with a second display?

Thanks

Joe

franton
Valued Contributor III

I'm not currently in a position to test, as I don't personally own one and i'm not currently employed. At some point I need to rework things a bit like:

wallpaper="/path/your_picture.jpg" /usr/bin/sqlite3 "~/Library/Application Support/Dock/desktoppicture.db "update data set value = '$wallpaper'"

That code needs a lot more refinement. I would have a look at the Graham Gilbert's python script that @gregneagle mentioned earlier too.

ScottyBeach
Contributor

@ franton:
That very nice. Thanks for it.
For bonus points, how would we add a directory of a dozen photos to the SQLite DB and switch them every boot? The directory full of pics need to be treated like,e some sort of array? The setting to change at boot a separate setting in a defaults write command?
Enquiring (not very good scripting) minds want to know.
- Scott

franton
Valued Contributor III

@ScottyBeach May I refer you to this? @rtrouton has some example code that is better suited than mine is. https://derflounder.wordpress.com/2013/10/26/mavericks-desktop-background-picture-settings-moved-fro...

bentoms
Release Candidate Programs Tester

@ScottyBeach Not a bash version, but this from @gregneagle might work.

seanhansell
Contributor

I've forked and modified this script in order to variablize the Desktop Picture path.

If you're interested, you can view progress on the fork here:
https://github.com/SeanHansell/Casper-Change-Desktop

A documentation, logging, and comment pass is already coming. The main difference with this version is that instead of passing a binary flag (in this case the word "custom"), pass the path to the Desktop Picture at Parameter 4.

#!/bin/bash

# Script to set desktop background via Casper. Forked from the original work of Richard Purves <r.purves@arts.ac.uk>.

# Maintainer : Sean Hansell <sean@morelen.net>
# Version 1.0 : Initial Version
# Version 1.1 : 2014-04-23 - Massive reworking to use Applescript for 10.8 and below or modify the sqlite DB for 10.9+
# Version 1.2 : 2014-04-24 - Removed AppleScript because of osascript parsing issues. Replaced with MCX.
# Version 1.3 : 2016-03-07 - Overhaul to variablize the desktop picture path.

# Casper Static Variables
desktop_picture="${4}" # Path to Desktop Picture. Define this in Casper.

# Dynamic Variables
os_version=$( sw_vers | grep ProductVersion: | awk '{print $2}' | sed 's/./ /g' | awk '{print $2}' )
current_user=$( ls -l /dev/console | awk '{print $3}' )
current_user_home=$( dscl . -read "/Users/${current_user}" NFSHomeDirectory | sed 's/NFSHomeDirectory: //' )
desktop_db="${current_user_home}/Library/Application Support/Dock/desktoppicture.db"
desktop_domain="${current_user_home}/Library/Preferences/com.apple.desktop"
desktop_plist="${desktop_domain}.plist"

if [[ -z "${desktop_picture}" ]]
then
    echo "desktop_picture variable is empty. Nothing to do."
    exit 1
fi

if (( $os_version > 8 ))
then
    sqlite3 "${desktop_db}" << EOF
UPDATE data SET value = "${desktop_picture}";
.quit
EOF
else
    defaults delete "${desktop_domain}" Background
    defaults write "${desktop_domain}" Background '{default = {ImageFilePath = "'"${desktop_picture}"'";};}'
    chown "${current_user}" "${desktop_plist}"
fi

killall Dock

echo "Desktop picture changed to ${desktop_picture}"
exit 0
- Sean

Ninyo
New Contributor III
New Contributor III

Hi,
Did anybody checked the latest revision of @seanhansell on High Sierra or Mojave?
Can't seem to get it to work.

marklamont
Contributor III

I found, can't remember where, and use this to set ours for different users by passing through the parameters from a bash script, works well on 10.12 and 10.13.
call it from bash like this python scriptname --path wallpaperpath

#!/usr/bin/python

'''Uses Cocoa classes via PyObjC to set a desktop picture on all screens.
Tested on Mountain Lion and Mavericks. Inspired by Greg Neagle's work: https://gist.github.com/gregneagle/6957826

See:
https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/classes/NSWorkspace_Class/Reference/Reference.html

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURL_Class/Reference/Reference.html

https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/classes/NSScreen_Class/Reference/Reference.html
'''

from AppKit import NSWorkspace, NSScreen
from Foundation import NSURL
import argparse
import sys

parser = argparse.ArgumentParser(description='Sets the desktop picture on all screens')
parser.add_argument('--path', help='The path of the image')
args = vars(parser.parse_args())

if args['path']:
    picture_path = args['path']
else:
    print >> sys.stderr, 'You must supply a path for the desktop picture'
    exit(-1)

# generate a fileURL for the desktop picture
file_url = NSURL.fileURLWithPath_(picture_path)

# make image options dictionary
# we just make an empty one because the defaults are fine
options = {}

# get shared workspace
ws = NSWorkspace.sharedWorkspace()

# iterate over all screens
for screen in NSScreen.screens():
    # tell the workspace to set the desktop picture
    (result, error) = ws.setDesktopImageURL_forScreen_options_error_(
                file_url, screen, options, None)
    if error:
        print error
        exit(-1)

grahamrpugh
Release Candidate Programs Tester

@marklamont thats Graham Gilbert’s script, as mentioned earlier. https://github.com/grahamgilbert/macscripts/blob/master/set_desktops/set_desktops.py

seanhansell
Contributor

@Ninyo My script is still working for me. :)

- Sean

captam3rica
New Contributor III

Thanks for the Python script Graham Gilbert.

Not sure if it helps anyone else, but I made a few modifications.

I've tested on Catalina deploying from Jamf and it works great.

  • Removed the option to supply a path with --path.
  • Added a check to make sure that the file type is .png.
  • Update: Make sure to put the desktop photo in a place like /Library/Desktop Pictures and point the script to that location.
#!/usr/bin/python

"""A script to set the desktop wallpaper on macOS.
"""

###############################################################################
#
#   DESCRIPTION:
#
#
#       Uses Cocoa classes via PyObjC to set a desktop picture on all screens.
#       Tested on Mountain Lion and Mavericks. Inspired by Greg Neagle's work:
#
#           https://gist.github.com/gregneagle/6957826
#
#       Modified from @gramhamgilbert's script:
#
#       https://github.com/grahamgilbert/macscripts/blob/master/set_desktops/
#       set_desktops.py
#
#       Removed the ability to define a file path via argparse. Defines the
#       path to the image dynamically in the script. The image should live in
#       the same location where the script is being executed.
#
#       The onlything hardcoded in the script is the name of the image. In this
#       case the PICTURE_NAME is set to "stock_wallpaper.png"
#
#   SEE:
#
#       https://developer.apple.com/library/mac/documentation/cocoa/reference/
#       applicationkit/classes/NSWorkspace_Class/Reference/Reference.html
#
#       https://developer.apple.com/library/mac/documentation/Cocoa/Reference/
#       Foundation/Classes/NSURL_Class/Reference/Reference.html
#
#       https://developer.apple.com/library/mac/documentation/cocoa/reference/
#       applicationkit/classes/NSScreen_Class/Reference/Reference.html
#
###############################################################################


import sys
import os


from AppKit import NSWorkspace, NSScreen
from Foundation import NSURL

HERE = os.path.abspath(os.path.dirname(__file__))
PICTURE_NAME = "stock_wallpaper.png"

picture_path = os.path.join("/Library", "Desktop Pictures", PICTURE_NAME)


def verify_file_extension():
    """Verify that file extension is set to png"""
    if not PICTURE_NAME.endswith(".png"):
        print(
            "ERROR: Make sure that you are using a PNG file for your desktop "
            "image."
        )
        print("Picture Name: %s" % PICTURE_NAME)
        sys.exit(1)


def gen_file_url(path):
    """generate a fileURL for the desktop picture"""
    global file_url
    file_url = NSURL.fileURLWithPath_(picture_path)
    return file_url


def get_shared_workspace():
    """get shared workspace"""
    global ws
    ws = NSWorkspace.sharedWorkspace()
    return ws


def apply_desktop_wallpaper(ws, url):
    """Apply desktop wallpaper"""

    # make image options dictionary
    # we just make an empty one because the defaults are fine
    options = {}

    # iterate over all screens
    for screen in NSScreen.screens():
        # tell the workspace to set the desktop picture
        result = ws.setDesktopImageURL_forScreen_options_error_(
            file_url, screen, options, None
        )

        for item in result:

            if item is True:
                print("Wallpaper applied!")
                break

            elif item is False:
                print("Wallpaper NOT applied successfully ...")
                print(result)
                sys.exit(1)
            else:
                pass


def main():
    """The main event."""

    verify_file_extension()
    gen_file_url(path=picture_path)
    get_shared_workspace()
    apply_desktop_wallpaper(ws=ws, url=file_url)


if __name__ == "__main__":
    main()