-
-
Save korylprince/be2e09e049d2fd721ce769770d983850 to your computer and use it in GitHub Desktop.
#!/usr/local/munki/munki-python | |
# change the above path to your own python if you don't have Munki installed | |
""" | |
Merges add_servers into current favorites and removes remove_servers. | |
Run as root to update all users or as normal user to update just that user. | |
""" | |
import os | |
import getpass | |
import subprocess | |
import uuid | |
import Foundation | |
favorites_path = "/Users/{user}/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.FavoriteServers.sfl2" | |
# add these servers if they don't exist already | |
# use a tuple: ("<name>", "<path>") to set a name for the favorite. | |
# otherwise just use a string and the path will be used as the name | |
add_servers = (("My Name", "smb://server.example.com/share"), "vnc://server.example.com") | |
# remove these servers if they exist. Use a wildcard (*) at the end to remove all shares | |
# does not support ("<name>", "<path>") syntax | |
remove_servers = ("smb://old.example.com/*", "vnc://old.example.com") | |
def get_users(): | |
"Get users with a home directory in /Users" | |
# get users from dscl | |
dscl_users = subprocess.check_output(["/usr/bin/dscl", ".", "-list", "/Users"]).splitlines() | |
# get home directories | |
homedir_users = os.listdir("/Users") | |
# return users that are in both lists | |
users = set(dscl_users).intersection(set(homedir_users)) | |
return [u.strip() for u in users if u.strip() != ""] | |
def set_favorites(user, add_servers, remove_servers): | |
"Set the Server Favorites for the given user" | |
# read existing favorites file | |
data = Foundation.NSKeyedUnarchiver.unarchiveObjectWithFile_(favorites_path.format(user=user)) | |
# reformat add_servers to [(name, path), ...] | |
new_add_servers = [] | |
for s in add_servers: | |
if len(s) == 2: | |
new_add_servers.append(s) | |
else: | |
new_add_servers.append((s, s)) | |
add_servers = new_add_servers | |
existing_servers = [] | |
# read existing items safely | |
if data is not None: | |
data_items = data.get("items", []) | |
# read existing servers | |
for item in data_items: | |
name = item["Name"] | |
url, _, _ = Foundation.NSURL.initByResolvingBookmarkData_options_relativeToURL_bookmarkDataIsStale_error_(Foundation.NSURL.alloc(), item["Bookmark"], Foundation.NSURLBookmarkResolutionWithoutUI, None, None, None) | |
existing_servers.append((name, url)) | |
# get unique ordered list of all servers | |
all_servers = [] | |
# add existing_servers to list, updating name if necessary | |
for s in existing_servers: | |
try: | |
idx = [a[1] for a in add_servers].index(s[1]) | |
all_servers.append((add_servers[idx][0], s[1])) | |
except ValueError: | |
all_servers.append(s) | |
# add add_servers if not present already | |
for s in add_servers: | |
if s[1] not in [e[1] for e in existing_servers]: | |
all_servers.append(s) | |
# remove old servers: exact match | |
# matches "smb://old.domain" exactly | |
all_servers = [s for s in all_servers if s[1] not in remove_servers] | |
# remove old servers: shares | |
# matches "smb://old.domain/*" | |
all_servers = [s for s in all_servers if len( | |
[True for r in remove_servers if r.endswith("*") and str(s[1]).startswith(r[:-1])] | |
) < 1] | |
# generate necessary structures | |
items = [] | |
for name, path in all_servers: | |
item = {} | |
item["Name"] = name | |
url = Foundation.NSURL.URLWithString_(str(path)) | |
bookmark, _ = url.bookmarkDataWithOptions_includingResourceValuesForKeys_relativeToURL_error_(0, None, None, None) | |
item["Bookmark"] = bookmark | |
# generate a new UUID for each server | |
item["uuid"] = str(uuid.uuid1()).upper() | |
item["visibility"] = 0 | |
item["CustomItemProperties"] = Foundation.NSDictionary.new() | |
items.append(Foundation.NSDictionary.dictionaryWithDictionary_(item)) | |
data = Foundation.NSDictionary.dictionaryWithDictionary_({ | |
"items": Foundation.NSArray.arrayWithArray_(items), | |
"properties": Foundation.NSDictionary.dictionaryWithDictionary_({"com.apple.LSSharedFileList.ForceTemplateIcons": False}) | |
}) | |
# write sfl2 file | |
Foundation.NSKeyedArchiver.archiveRootObject_toFile_(data, favorites_path.format(user=user)) | |
# loop through users and set favorites | |
if __name__ == "__main__": | |
# if running as root, run for all users. Otherwise run for current user | |
user = getpass.getuser() | |
if user == "root": | |
users = get_users() | |
else: | |
users = [user] | |
for user in users: | |
try: | |
set_favorites(user, add_servers, remove_servers) | |
# fix owner if ran as root | |
if user == "root": | |
os.system(("chown {user} " + favorites_path).format(user=user)) | |
print("Server Favorites set for " + user) | |
except Exception as e: | |
# if there's an error, log it an continue on | |
print("Failed setting Server Favorites for {0}: {1}".format(user, str(e))) | |
# kill sharedfilelistd process to reload file. Finder should be closed when this happens | |
os.system("killall sharedfilelistd") |
#!/usr/local/munki/munki-python | |
# change the above path to your own python if you don't have Munki installed | |
""" | |
Overwrites server favorites with servers. | |
Run as root to update all users or as normal user to update just that user. | |
""" | |
import os | |
import getpass | |
import subprocess | |
import uuid | |
import Foundation | |
favorites_path = "/Users/{user}/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.FavoriteServers.sfl2" | |
# Use a tuple: ("<name>", "<path>") to set a name for the favorite. | |
# Otherwise just use a string and the path will be used as the name | |
servers = (("My Name", "smb://server.example.com/share"), "vnc://server.example.com") | |
def get_users(): | |
"Get users with a home directory in /Users" | |
# get users from dscl | |
dscl_users = subprocess.check_output(["/usr/bin/dscl", ".", "-list", "/Users"]).splitlines() | |
# get home directories | |
homedir_users = os.listdir("/Users") | |
# return users that are in both lists | |
users = set(dscl_users).intersection(set(homedir_users)) | |
return [u.strip() for u in users if u.strip() != ""] | |
def set_favorites(user, servers): | |
"Set the Server Favorites for the given user" | |
# generate necessary structures | |
items = [] | |
for server in servers: | |
name = server[0] if len(server) == 2 else server | |
path = server[1] if len(server) == 2 else server | |
item = {} | |
item["Name"] = name | |
url = Foundation.NSURL.URLWithString_(path) | |
bookmark, _ = url.bookmarkDataWithOptions_includingResourceValuesForKeys_relativeToURL_error_(0, None, None, None) | |
item["Bookmark"] = bookmark | |
# generate a new UUID for each server | |
item["uuid"] = str(uuid.uuid1()).upper() | |
item["visibility"] = 0 | |
item["CustomItemProperties"] = Foundation.NSDictionary.new() | |
items.append(Foundation.NSDictionary.dictionaryWithDictionary_(item)) | |
data = Foundation.NSDictionary.dictionaryWithDictionary_({ | |
"items": Foundation.NSArray.arrayWithArray_(items), | |
"properties": Foundation.NSDictionary.dictionaryWithDictionary_({"com.apple.LSSharedFileList.ForceTemplateIcons": False}) | |
}) | |
# write sfl2 file | |
Foundation.NSKeyedArchiver.archiveRootObject_toFile_(data, favorites_path.format(user=user)) | |
# loop through users and set favorites | |
if __name__ == "__main__": | |
# if running as root, run for all users. Otherwise run for current user | |
user = getpass.getuser() | |
if user == "root": | |
users = get_users() | |
else: | |
users = [user] | |
for user in users: | |
try: | |
set_favorites(user, servers) | |
# fix owner if ran as root | |
if user == "root": | |
os.system(("chown {user} " + favorites_path).format(user=user)) | |
print("Server Favorites set for " + user) | |
except Exception as e: | |
# if there's an error, log it an continue on | |
print("Failed setting Server Favorites for {0}: {1}".format(user, str(e))) | |
# kill sharedfilelistd process to reload file. Finder should be closed when this happens | |
os.system("killall sharedfilelistd") |
It does, but requires you give permissions to read the files. I put some info on the thread here.
If you wanted to automate this, you'd probably need to use a profile to add those permissions automatically.
Hey sorry for such a newb question I have 0 experience with coding or scripts. How/where would I enter the smb locations of the servers in this script. Would it go here where it says servers? servers = (( "smb://server.example.com/share")) Do I just delete the example and put the server addresses here?
I obviously need a course on scripting and python but this seems to do exactly what I am hoping so figured I would ask.
Replace the servers = ...
line with:
servers = ("smb://server1.example.com", "smb://server2.example.com", "smb://server3.example.com")
replacing the serverX.example.com
's with your hostnames.
I have no idea if this works on Big Sur+ or not. Last version I tested it on was Catalina.
Using Monterey, using macadamia's python3 so I updated the first line to match my python3 install. Since python3 uses unicode I replaced all the unicode(
with str(
, I've got the servers I want added to add_server, and I've commented out remove_servers since I don't need to remove any right now. When I run it chokes at line 26, def get_users(). I don't know enough about python at this point.
Foundation does not appear to be a part of Python3 and older Python seems deprecated in Monterey 12.3.1, what can you use instead of Foundation? Any ideas?
I've updated these scripts for Python3. They've been tested on Monterey 12.3.1 with Munki's python. You'll need to point the shebang (first line) to a similarly built Python if you want to continue using these scripts.
The only alternative is if someone writes a compiled CLI tool (e.g. Objective-C, Swift) that uses the same Foundation APIs.
Adding servers is working great! Thanks for the updates! But, how does remove_servers work?
If you want to remove a server from the existing list, then include it in the remove_servers list"
# remove these servers if they exist. Use a wildcard (*) at the end to remove all shares
# does not support ("<name>", "<path>") syntax
remove_servers = ("smb://old.example.com/*", "vnc://old.example.com")
Hate to be a bother... Python is not in my quiver and I'm trying to figure out what I can when I have the time. But on a Monterey machine I'm getting this error now...
Failed setting Server Favorites for adm: 'NoneType' object has no attribute 'bookmarkDataWithOptions_includingResourceValuesForKeys_relativeToURL_error_'
No matching processes belonging to you were found
I'm using MacAdmin's python with /usr/local/bin/managed_python3 and its obviously getting to this point at least.
@damacguy my guess is that you have not used a valid URL in add_servers
or remove_servers
(for merge.py) or servers
(for overwrite.py).
That error is happening because Foundation.NSURL.URLWithString_
is failing to return a parsed URL.
Indeed! One of the links the user provided has spaces in it and I didn't think anything of it (facepalm). Thanks for super fast response. 🥇
Q: Have you tested this code on Ventura? I'm assuming it still works because you're using munki's python3. I'm just debating if it is worth it to install another version of python3 just to have the necessary code to make this work.
I've not had a chance to test this. I haven't actually used this code in several years, though I've tried to keep it updated for others. I don't see any reason it wouldn't work though, assuming you have a python3 installed.
Kory - thanks for maintaining this. Could I ask you a favor? Could you update the script to allow it to accept variables passed to it? I'm not a python guy, but I almost got it working with the macadmin's python3 and taking variables $4 thru $11 to add servers. Maybe $4 could declare if the rest are adding or removing or something. way over my head at this point but I'm working to learn more - got my books and stack overflow.
I tried the overwrite.py script in Ventura 13.2 and it worked, but only if not run as root. I found that the reason is that subprocess.check_output() returns the user list as "bytes". I got it working by adding ",encoding='UTF-8'" so that line 26 looks like this:
dscl_users = subprocess.check_output(["/usr/bin/dscl", ".", "-list", "/Users"],encoding='UTF-8').splitlines()
may I know how can I change the add_servers pointing to my specific local folder ? let's say I have the ABC folder inside the Documents, and would like to stick it into the SideBar
Hello,
does this wonderful script also works with 10.15+?. I am testing this on a 10.15.6 machine and it seams not to work.
Regards
Henning