Script to Copy preference file to all users profiles and to the default template

rky
New Contributor

I have build a package using Apple PackageMaker, this will install an application and a preference (plist) file to the current logged in user. Now I wanted to add a post-flight script to the package, which can copy the same plist (for ex: com.test.plist) form the current logged in user to other user account on a machine also to the default user template (/System/Library/user template/...../Library/preferences) so any new user account created will have same preference file copied.
I am new to scripting on Macs, please help in scripting the task.

Thank You

2 ACCEPTED SOLUTIONS

mm2270
Legendary Contributor III

Hi.

So looking at the script from the link I posted, I see that the method used is kind of old and isn't really addressing just copying a single plist, more to copy an entire folder of settings, so there are much better methods to get a plist down to all accounts.

Ideally, what you want to do is place the plist file into something like a /private/tmp/ location or /private/var/plist or some such rather than into a specific account, and then copy from the tmp location to all local accounts. But, if you're certain the admin user exists on your machines and its always going to be a valid location to copy the plist from, you can do something like this.

#!/bin/sh

localUsers=$( dscl . list /Users UniqueID | awk '$2 >= 501 {print $1}' | grep -v admin )

for userName in "$localUsers"; do
     cp /Users/admin/Library/Preferences/org.mypreference.plist /Users/$userName/Library/Preferences/
     chown $userName /Users/$userName/Library/Preferences/org.mypreference.plist
     cp /Users/admin/Library/Preferences/org.mypreference.plist /System/Library/User Template/English.lproj/Library/Preferences/
done

I didnt test this at all. Literally wrote it right into my post, so I can't be sure all the syntax is correct, etc. So please test it and see if it does what you want.

That said, is this something you're planning on deploying via Casper? Or is it for some other manual installation process? if the former, why not just use the DMG format and FEU/FUT options?

If the pkg + script method is necessary, I'd encourage you to look at some more posts here on JAMFNation as I know there are other threads that cover this in greater detail.

View solution in original post

mm2270
Legendary Contributor III

@rahul, are you running the script with root privileges? I suspect you are or it wouldn't be able to copy the plist into /System/Library/User Template/, but I just want to be sure.
If it is being run with sudo, then you can try changing to a while read/do loop like this-

#!/bin/sh

localUsers=$( dscl . list /Users UniqueID | awk '$2 >= 501 {print $1}' | grep -v admin )

echo "$localUsers" | while read userName; do
     cp /private/var/org.mypreference.plist /Users/$userName/Library/Preferences/
     chown $userName /Users/$userName/Library/Preferences/org.mypreference.plist
     cp /private/var/org.mypreference.plist /System/Library/User Template/English.lproj/Library/Preferences/
done

I've had occasional issues with for/do loops not working, and the above is one other way to approach it if that's the case. Since you said it can copy the file with a direct path like to /System/Library/User Template/, the only difference when copying to user directories is the loop, so that seems like the next logical thing to try.

View solution in original post

27 REPLIES 27

mm2270
Legendary Contributor III

Hi. There are a number of threads here on JAMFNation discussing this with posted solutions. Here's an old one I just pulled up in a search, but there are others. Search around:

https://jamfnation.jamfsoftware.com/discussion.html?id=11

rky
New Contributor

Hi, Thanks for your quick response. In the below script, How should I map the path for my plist from the current logged in user?

#!/bin/sh
#
# Copy stuff to existing/future users, set ownership and permissions.
#
# Thanks to Tom Larkin and Steve Wood and the others at JAMF Nation.

# Copy to User Template and set root:wheel ownership

/usr/bin/ditto /tmp/USERCRAP /System/Library/User Template/English.lproj
/usr/sbin/chown -R root:wheel /System/Library/User Template/English.lproj

# Copy to existing user directories and set ownership and permissions.

for i in $(/bin/ls /Users | sed -e '/Shared/d' -e '/Deleted Users/d' -e '/.localized/d'
-e '/.DS_Store/d' -e '/.com.apple.timemachine.supported/d' -e '/Adobe/d' -e '/Library/d');

do

/usr/bin/ditto /tmp/USERCRAP /Users/$i
/usr/sbin/chown -R $i:staff /Users/$i

done

exit 0

rky
New Contributor

When I install my package, it installs a preference file to current user only. I need to copy the preference file to all user profiles and to the default template for any future users. This is the location I need to map in the script
/users/admin/library/preferences/org.mypreference.plist

mm2270
Legendary Contributor III

Hi.

So looking at the script from the link I posted, I see that the method used is kind of old and isn't really addressing just copying a single plist, more to copy an entire folder of settings, so there are much better methods to get a plist down to all accounts.

Ideally, what you want to do is place the plist file into something like a /private/tmp/ location or /private/var/plist or some such rather than into a specific account, and then copy from the tmp location to all local accounts. But, if you're certain the admin user exists on your machines and its always going to be a valid location to copy the plist from, you can do something like this.

#!/bin/sh

localUsers=$( dscl . list /Users UniqueID | awk '$2 >= 501 {print $1}' | grep -v admin )

for userName in "$localUsers"; do
     cp /Users/admin/Library/Preferences/org.mypreference.plist /Users/$userName/Library/Preferences/
     chown $userName /Users/$userName/Library/Preferences/org.mypreference.plist
     cp /Users/admin/Library/Preferences/org.mypreference.plist /System/Library/User Template/English.lproj/Library/Preferences/
done

I didnt test this at all. Literally wrote it right into my post, so I can't be sure all the syntax is correct, etc. So please test it and see if it does what you want.

That said, is this something you're planning on deploying via Casper? Or is it for some other manual installation process? if the former, why not just use the DMG format and FEU/FUT options?

If the pkg + script method is necessary, I'd encourage you to look at some more posts here on JAMFNation as I know there are other threads that cover this in greater detail.

rky
New Contributor

Wow...You are genius. This worked like a charm, Thank You. This works on the machine where we have a default admin user. However, is there way that I can add a script to find current logged in user first, then it find the preference file in the user profile, and then it copies to rest of the user's profile and default template? No, I will not be deploying it through Casper. I will be doing it via Apple Remote desktop.

rky
New Contributor

If I package my plist to location like "/private/var/plist". In such do I just need to replace the location for the file in the above script? Something like this...

#!/bin/sh

localUsers=$( dscl . list /Users UniqueID | awk '$2 >= 501 {print $1}' | grep -v admin )

for userName in "$localUsers"; do
     cp /private/var/org.mypreference.plist /Users/$userName/Library/Preferences/
     chown $userName /Users/$userName/Library/Preferences/org.mypreference.plist
     cp /private/var/org.mypreference.plist /System/Library/User Template/English.lproj/Library/Preferences/
done

mm2270
Legendary Contributor III

Yes, exactly. As I mentioned, this is generally a safer way to go about it, since /private/var/ always exists on a target system. As long as your installer package is dropping the plist file in that location, a postinstall or postflight script copying it from that path should always work.

rky
New Contributor

I tried as you said with the above script... it comes with the following error.
external image link

mm2270
Legendary Contributor III

I can't see your image. Its nothing you did wrong, just that where i am images from external sharing sources like Dropbox often get blocked. So you may need to describe the error.

rky
New Contributor

Oh...I shared the image via Dropbox link. When I run my package it copies the plist file to /private/var and then I believe, it's not able to map the /private/var location to initiate a copy to other user account's. I ran this script from "admin" account and "test" is another user account on machine for testing purpose.

Is there something we need to change in the second line of the script where it says "grep -v admin" ??
Here is the error when run the script in terminal:

usage: cp [-R [-H | -L | -P] ] [-fi | -n] [-apvX] source_file target_file cp [-R [-H | -L | -P] ] [-fi | -n] [-apvX] source_file ... target_directory
chown: test: No such file or directory
chown: test/Library/Preferences/org.mypreference.plist: No such file or directory

themacdweeb
Contributor

rahul,

there are a variety of ways to isolate and set to a variable all of the +500 user accounts on your mac. if the method that mm2270 suggested isn't working, try another. one option is

UserAccounts=`dscl . list /Users UniqueID | awk '$2 > 500 { print $1 }'`

use a little google-fu, i'm sure some research will turn up many, many other options. same on learning UNIX syntax.

rky
New Contributor

Hi..Thanks for helping me out on this. When I run the script, it comes up with the following error:

cp [-R [-H | -L | -P]] [-fi | -n] [-apvX] source_file target_file
cp [-R [-H | -L | -P]] [-fi | -n] [-apvX] source_file ... target_directory
chown: admin: No such file or directory
chown: admin/Library/Preferences/org.mypreference.plist: No such file or directory

Here is the script I tried,

#!/bin/sh

UserAccounts=`dscl . list /Users UniqueID | awk '$2 > 500 { print $1 }'`
for userName in "$UserAccounts"; do
     cp /private/var/org.mypreference.plist /Users/$userName/Library/Preferences/
     chown $userName /Users/$userName/Library/Preferences/org.mypreference.plist
done

donmontalvo
Esteemed Contributor III

Put the file into /tmp and go from there.

--
https://donmontalvo.com

rky
New Contributor

Hi..I just tried to put my preference file to /tmp directory. But the scripts comes up with the same error:

usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvX] source_file target_file cp [-R [-H | -L | -P]] [-fi | -n] [-apvX] source_file ... target_directory
chown: rahulyadav: No such file or directory
chown: rahulyadav/Library/Preferences/org.mypreference.plist: No such file or directory

Here's the script:

#!/bin/sh

UserAccounts=`dscl . list /Users UniqueID | awk '$2 > 500 { print $1 }'`
for userName in "$UserAccounts"; do
     cp /tmp/org.mypreference.plist /Users/$userName/Library/Preferences/
     chown $userName /Users/$userName/Library/Preferences/org.mypreference.plist
done

donmontalvo
Esteemed Contributor III

I don't get it, are you trying at all to troubleshoot this or are you asking this forum to do your work for you? :)

What has this got to do with Casper, as you responded you are deploying with ARD?

http://lmgtfy.com/?q=Unix+how+to

Your LinkedIN profile lists Apple certification. Apple transitioned to UNIX a dozen years ago...

--
https://donmontalvo.com

mscottblake
Valued Contributor

Where is your source file? The error you are posting indicates that it does not exist at the time of script execution.

The cp command is doing the actual copy, so you need to make sure that /tmp/org.mypreference.plist exists before you try to copy it.

mm2270
Legendary Contributor III

In agreement with msblake. The error you're seeing is the command line's way of trying to instruct you on the proper use of a command, in this case, cp.
There are a couple of possible causes I can think of why its failing. Probably some others I'm not thinking of as well.

1 - Your installer package is not actually placing the plist into the location the copy command is being directed to. Simple, but its often the simple things that are root cause.
2 - The script in the installer package was not set up to run after the installation, either as a postflight or postinstall script. Check that to make sure its not trying to run prior to the payload.
3- The plist file is not actually a plist file. While I doubt this is the case, the cp command in the script I put together is not using any recusrsive-ness, which would be necessary if the copy source is anything but a flat file. Meaning, if you target a folder, you need to include -R after cp. As I said, I doubt this is the case for a number of reasons, one being that the error you'd see would indicate this. But I would double-check to make sure what's getting delivered isn't showing up as a directory of some sort.

One simple way to troubleshoot this is to rebuild the package installer with no scripts. Install it on your test machine and see what gets dropped onto the system. (Hint: You can also just open it in Installer.app and use the File > Show Files menu option to show what's being installed where) You may find its not actually adding your plist file into the location you're expecting.
If it is putting it in the right place, make sure its not a bundle and make sure the script is being run after delivery.

rky
New Contributor

@ donmontalvo - Hi....The forum is really trying to help me, Thanks to all. The problem is I just don't know the scripting language and probably such a scenario would have already exists and I tried my best to google it b'fore coming to the forum. In my previous assignments, I never had a chance to learn or explore the UNIX scripting. It's a new learning curve for me....It would be great if you can suggest me some good resource/website or books for beginners on Unix scripting.

@ msblake and mm2270 - Thanks for helping me out on this. 1. I build the package without script, it delivers the application to /Applications and the plist to /private/var with no issues. I can verifies the plist present at the location we are trying to map in the script. 2. Once the plist is in place (/private/var/org.mypreference.plist), I tried to run the script manually from the terminal. It comes up with the same error.

I just find out, the script copies the plist file to user template (/System/Library/User Template) successfully. That mean, the file is present at the correct location, it just unable to copy to any existing user account...I am just wondering what could be wrong ??

#!/bin/sh

localUsers=$( dscl . list /Users UniqueID | awk '$2 >= 501 {print $1}' | grep -v admin )

for userName in "$localUsers"; do
     cp /private/var/org.mypreference.plist /Users/$userName/Library/Preferences/
     chown $userName /Users/$userName/Library/Preferences/org.mypreference.plist
     cp /private/var/org.mypreference.plist /System/Library/User Template/English.lproj/Library/Preferences/
done

mm2270
Legendary Contributor III

@rahul, are you running the script with root privileges? I suspect you are or it wouldn't be able to copy the plist into /System/Library/User Template/, but I just want to be sure.
If it is being run with sudo, then you can try changing to a while read/do loop like this-

#!/bin/sh

localUsers=$( dscl . list /Users UniqueID | awk '$2 >= 501 {print $1}' | grep -v admin )

echo "$localUsers" | while read userName; do
     cp /private/var/org.mypreference.plist /Users/$userName/Library/Preferences/
     chown $userName /Users/$userName/Library/Preferences/org.mypreference.plist
     cp /private/var/org.mypreference.plist /System/Library/User Template/English.lproj/Library/Preferences/
done

I've had occasional issues with for/do loops not working, and the above is one other way to approach it if that's the case. Since you said it can copy the file with a direct path like to /System/Library/User Template/, the only difference when copying to user directories is the loop, so that seems like the next logical thing to try.

rky
New Contributor

Yes, It WORKED with no issue's. Thank you very much for the help. Can you explain me what does "grep -v admin" do??

Thank again for all your help on this :)

mscottblake
Valued Contributor

grep -v localAdmin means skip the localAdmin user. Adding this allows you to skip certain users that don't need the file.

If you don't need to skip any users, you can remove | grep -v admin.

casey_wayne
New Contributor

Hi Rahul,

First, your are to be commended to for just diving in with shell scripting your way out of this problem. Unfortunately, that is often how you have to do it, just start...

Secondly, I think there are a couple of things you appear to be missing. Some of these things may already be obvious to you and in that case please forgive my redundant points:

  1. In Casper, we create disk images or packages and have Casper install those packages for us. The packages are created with Composer and quite frankly it rocks! Been using it for years to save time on the job. Once you create the package, you have Casper push out the files. And for user preference files there are two handy checkboxes FEU and FUT. FEU- fill existing users and FUT-fill user templates. So, with Casper, thats about all it takes. Now, it sounds like you not doing this with Casper, but ARD. Correct?

  2. On a Mac, there are "domains" that separate out where all the files go based on who is going to need those files: System, Computer, User, Network. Those are the domains. System domain is under /System. Computer under /Library. Users under /Users/<user short name>. And Network under /Network. Ignore the Network domain for now.

If you want to distribute a file to all the users on a system, I recommend keeping it easy and putting those files into the user's domain. In fact, the best place is the user's domain shared file or /Users/Shared. You have to be a little careful, because anything going in here has its permissions modified so that everyone can read the files, but that doesnt appear to be a problem in your example.

Also, you might not know how a user is created by the OS. When you go into User's and click on the "New User" + symbol, the OS copies all the files in /System/Library/User Template/English.lproj (assuming your doing this for a computer that isnt localized into another language). And it it puts them into a new folder called /Users/<user short name> and then it changes the permissions so that the new user can access these files.

So, if you want to put files into some place for all -new- users, that is the right place. Just be careful to match up the permissions to match all the other files that are in the user template so the OS doesnt make any mistakes using your preference file(s). Also be sure that your preference file doesnt contain anything that is specifically for your user as the OS will not go in and edit the preference file itself.

The script above will do the following...

localUsers ...
this line gets a list of the local users on the computers. This is the first time I have seen this method, and it is pretty cool, but it is just one way to get the info you need. Note: it does not get any system created users because they all have UID's below 501 and it eliminates anything with the word admin in it.

"$localusers"...
this is a do while loop that cycles through all the local user names one at a time.

cp /private...
this line copies the file from /private/var and puts it into /Users/<the user name> /Library/Preferences
I probably would have left out the last /, but whatever, I think the syntax above is cleaner

chown ... chown is the command that changes ownership rights This is good, as we discussed above.

cp /private/var... this copies your pref file and puts it into the user template, the English one.

done
this line ends the loop. I would have added another two lines before the "done". I would have added chown root:wheel /System/Library/User Template/English.lproj/Library/Preferences/com.mypreferences.* chmod 600 /System/Library/User Template/English.lproj/Library/Preferences/com.mypreferences.*
These two lines match up the preferences of your com.mypreferences.plist file with the preference files already in the template. In fact, while we are at it, I would add this line right after the chown $userName... libe chmod 600 600 /Users/$userName/Library/Preferences/org.mypreference.plist
This line should match us the permissions of your preference file with the others already in there...

What I didnt see here was how you are getting your preference file (com.mypreference.plist) into the source location. In this script your source location is /private/var. You can use ARD to put it in there, but based on your responses, this is where your plan is falling down. The files are not getting onto your computer in the right place. So that this script can get them where they need to go.

Also, I would get used to running through your scripts with some dummy data on your test computer in the terminal window until you get a better feel of how they work. I do this all the time... Also, I think you need to get really really familiar with the Mac file structure, in particular learning the following commands and how they apply...

chown
chmod
dscl
ditto
and then some favorite things like...
osascript
in AppleScript "do shell script"
and something that I get a better impression of nearly every day... Automator..

and the basics of bash...
ls
cat
echo
while...do loops
if..then loops
grep
SET
whoami
who
kill
top
ps
cp
mv
mkdir (especially my favorite...mkdir -p)
-h or -help as in "grep -h"
and lest we forget the mother of all reference tools, "man" as in "man grep"

You can get quite a way just googling. You might also grab that OReilly book, Unix for Mac Admins or whatever its called.

mm2270
Legendary Contributor III

Do man grep in Terminal for explanations of the flags. -v is an invert regular expression match.

Originally I assumed you were copying the plist file from the "admin" account, not from /private/var/ and in that case, you would want to exclude the admin account from being one of the targetted locations to copy the plist to. Why copy it back to the location it grabbed it from in the first place?
So, the grep -v admin is telling it to generate a list of all other accounts on the Mac except for "admin" to loop through.
However, if you do plan on putting the plist into /var/ then you can remove that part of the line, including the "|" pipe before it.

Also keep in mind that because of how grep operates, it would match any account with "admin" in the name, meaning "administrator", "localadmin", "myadmin" and others if they existed would get excluded as well.

If you wanted to do exact match, there are a couple of options with grep. It can accept beginning and end identifiers, so something like this:

| grep -v ^admin$

would only exclude an account called "admin" and include anything else.

casey_wayne
New Contributor

Ok, thats funny. While I was blathering on, the last question got answered....

Glad its working now...

casey_wayne
New Contributor

There is one thing that I wonder though...if you have a user that does not have a user account on this computer, you will get an error. I know this is unlikely, but its not impossible. Also, if you have user home directories that are old and may be reused some day, you would be missing your preference file.

I guess I would use a different loop. I would do something like...

for ab in $(ls /Users | grep -v "admin"); do if [ $ab != "Guest" ] ; then echo $ab #or instead of echoing, copy your preference file.... fi done

There, I have taken a fairly easy question and completely over-answered it....

donmontalvo
Esteemed Contributor III

@rahul4.y wrote:

@ donmontalvo - Hi....The forum is really trying to help me, Thanks to all. The problem is I just don't know the scripting language and probably such a scenario would have already exists and I tried my best to google it b'fore coming to the forum. In my previous assignments, I never had a chance to learn or explore the UNIX scripting. It's a new learning curve for me....It would be great if you can suggest me some good resource/website or books for beginners on Unix scripting.

Understood, and that's fine, everyone gets here from a different place. :)

May also want to consider joining Apple's Installer-Dev mailing list. Lots of Apple (and third party vendor) engineers there.

Don

--
https://donmontalvo.com

rky
New Contributor

Hi Casey....Thank you for such a simple and elegant explanation, This is really helpful. I will dig much deeper into the basic of Unix.

@ MM2270 ...Thank's again for all your help and explanation :)

@ don ...Thanks for the mailing list. I am into the mailing list now.