Posted on 04-23-2014 12:44 PM
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/
Solved! Go to Solution.
Posted on 04-24-2014 01:49 AM
Dear All,
I've modified the script above to remove the osascript stuff and replaced with the legacy mcx method.
Posted on 04-23-2014 12:45 PM
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!
Posted on 04-23-2014 12:50 PM
Kudos! Something we've been ignoring but I will try this for sure. Thanks for posting it up!
Posted on 04-23-2014 12:53 PM
@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!
Posted on 04-23-2014 04:43 PM
This looks great - thanks for posting.
Posted on 04-23-2014 05:02 PM
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
Posted on 04-23-2014 06:18 PM
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
Posted on 04-23-2014 06:50 PM
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!
Posted on 04-24-2014 12:01 AM
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 ;)
Posted on 04-24-2014 01:49 AM
Dear All,
I've modified the script above to remove the osascript stuff and replaced with the legacy mcx method.
Posted on 07-24-2014 11:19 AM
cant this all work with a simple configuration profile? and the com.apple.desktop plist?
Posted on 07-24-2014 11:24 AM
No as desktop wallpapers are set in a database file instead of the plist now.
Posted on 07-30-2014 10:44 AM
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
Posted on 07-31-2014 01:27 PM
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.
Posted on 09-12-2014 02:37 AM
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
Posted on 09-12-2014 03:17 AM
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.
Posted on 10-03-2014 09:47 AM
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?
Posted on 10-03-2014 10:02 AM
Please look at my original example again. You'll notice I'm not escaping out the file path with a in the sql commands.
Posted on 10-03-2014 10:19 AM
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.
Posted on 10-03-2014 03:45 PM
Trying to do variable substitution will not work at you're not in a shell environment when you run those SQLite commands!
Posted on 01-28-2015 05:12 AM
Posted on 01-28-2015 08:03 AM
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.
Posted on 02-26-2016 12:40 PM
@ 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
Posted on 02-26-2016 01:40 PM
@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...
Posted on 02-26-2016 01:49 PM
@ScottyBeach Not a bash version, but this from @gregneagle might work.
Posted on 03-07-2016 07:27 AM
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
Posted on 08-25-2018 01:53 AM
Hi,
Did anybody checked the latest revision of @seanhansell on High Sierra or Mojave?
Can't seem to get it to work.
Posted on 08-25-2018 01:48 PM
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)
Posted on 08-26-2018 12:48 PM
@marklamont thats Graham Gilbert’s script, as mentioned earlier. https://github.com/grahamgilbert/macscripts/blob/master/set_desktops/set_desktops.py
Posted on 09-04-2018 11:55 AM
@Ninyo My script is still working for me. :)
Posted on 01-22-2020 11:38 AM
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.
#!/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()