On Feb 7, 2007, at 11:19 PM, Bill Coderre wrote: I am a new Apple developer seeking some help about a PackageMaker issue. (Actually I am an ancient Apple developer, but the last time I did any was around 1986!)
I have an application for which I am creating an installation. I have successfully made a simple component package that works just fine.
I would now like to have the installer create an alias for the application on the desktop after the basic installation has completed. I understand that this is accomplished by a postinstall application.
[Or would it be postflight? I confess that I am not clear on the intended usage differences between these phases.]
[Another side question is whether I should be creating a symbolic link as opposed to an alias. I also do not understand the functional or user difference between the two.]
Postflight, postinstall, and postupgrade run after the installer finishes with copying files. Postflight is ALWAYS run. Postinstall is run when the installer is running in what is called "installation" mode. Postupgrade is run when the installer is running what is called "upgrade" mode.
These modes are not exactly like what the typical end-user expects. Instead, when the installer sees a receipt that matches the package being installed, that's upgrade mode. (There's two ways of matching. Originally, packages matched by name. Recently, we switched to matching by CFBundleIdentifier in the Info.plist)
There are two other things that are different between "install" and "upgrade" mode:
1) The button the user clicks to begin installation is marked "upgrade" and not "install" (I think we changed that recently -- now it always says install)
2) The installer compares the bill of materials of what's being installed with the bill of materials in the receipt. If there's a file that's in the old BOM but not the new one, the installer considers it "obsolete" and deletes it.
After all that, I forgot to tell you how to make an alias.
Don't make an alias. Make a symlink instead. The end user will not see a difference between the two, and the symlink is MUCH simpler to make. (I see Stephane disagrees.... Well...)
This basically boils down to a simple idea: ln -s /path/to/MyApp.app ~/Desktop/Myapp.app
ln is the unix command to make a link. -s means symbolic link. Hopefully you will never have to deal with any other kinds of links.
BUT, since it's the installer, and since there might be more than one user on the system, and since we're running as ROOT, and since there are security concerns, it gets a little more complicated.
Here's an example perl script to do it, in what I consider to be the RIGHT way: #!/usr/bin/perl
my $targetVolume = $ARGV[2];
############### # CHANGE THIS STUFF my $thingToAlias = $targetVolume ."/Applications/MyApp.app"; my $aliasTarget = "/Desktop/Myapp.app"; ###############
if (opendir(USERHANDLE, "/Users")) { while (defined(my $USER = readdir(USERHANDLE))) { # these are not real users next if $USER =~ /^\./; next if $USER eq "Shared";
my $aliasToMake = "/Users/" . $USER . $aliasTarget;
system("/usr/bin/su", $USER, "-c", "/bin/ln -s \"" . $thingToAlias . "\" \"" . $aliasToMake . "\""); } }
closedir(USERHANDLE);
my $aliasToMake = glob("~" . $aliasTarget); system("/usr/bin/su", $ENV{USER}, "-c", "/bin/ln -s \"" . $thingToAlias . "\" \"" . $aliasToMake . "\"");
exit 0;
Notes: $ARGV[2] is the perl _expression_ for the third command line argument the installer passes to postflight. It is the path to the target volume that the user selected before install commenced. Always use it, even when your script is restricted to "root volume only" because you never know when someone will change your that flag and allow install on some other volume!
Always specify the full path to unix commands such as /usr/bin/su or /bin/ln because that way you can be sure that the correct binary is running. Could be a security hole otherwise. If you don't know what the full path is, the unix command which <command> will tell you, ASSUMING that you haven't messed with your binary paths. (And if you don't know what that means, you probably haven't. Just check it on a "clean" machine.)
There's more than one user on some Mac OS X installations. Pretty much all the users have directories in /Users. I use the perl command opendir and the while loop to walk through those users. Then we make up the path to where to make the alias, and issue the command.
next if $USER =~ /^\./; translates to "skip processing this loop iteration if the user string starts with a period". Those aren't real users. Neither is "Shared".
Of course, if the logged-in user is using a network home directory, it's not in /Users. It can be found by decoding the standard unix shorthand ~ and the perl command to do that is glob. We get the user's userid from the USER environment variable that the installer sets.
We're using the perl command system to ask the shell to execute a command. As it happens, you COULD just have the command as one long string, BUT it is safer to separate the command and each of its switches and arguments into strings and separate them with commas.
Remember that your postflight script is probably running as ROOT, not as the current logged in user. Since ROOT might not have permissions to write to some users home directories (especially network home directories) and since we don't want to make an alias that's hard to delete for an unprivileged user, we use the unix command su to "simulate user" and run the command after the -c argument. (There's also some security reasons to use su, and there's actually some reasons to use su instead of sudo.)
The command to su needs to be one long string, otherwise su doesn't see the entire command to execute, and gets confused.
The command su ends up executing is very similar to the one we mentioned way up there somewhere. However, we're putting embedded quotation marks around the thing to alias and the alias to make. This is because some people have hard drive names with spaces in them, and because it might be possible for a user directory to have a space in it. Since system ends up giving commands to a shell to execute, the shell would get mighty confused by the spaces inside the file paths, since it uses spaces to delimit arguments. So we gotta quote them.
All preflight and postflight scripts MUST return an exit code of 0 ("no error") even if an error occurred. These scripts CANNOT stop or "undo" installation. It's up to the scripts to communicate the problem to the user, and since that's often very difficult, it's better to make sure that the scripts can never fail. OK, since that can never be 100% guaranteed, perhaps a better way of saying it is that the scripts must not be 100% necessary for the installed software to succeed. In this case, if the user doesn't get an alias on their desktop, oh well.
Note that all preflight and postflight "scripts" can actually be ANY unix executable: compiled program, shell script, perl script, etc. They MUST be named correctly, must NOT have any extension on the name, and must have the x (execute) permissions bit set for everyone.
I use perl because it makes these quoting issues simpler. Doing the above code in /bin/sh is possible, but more complicated.
perl also provides a pretty good syntax check if you say perl -Mstrict -wc One of the more commonly held beliefs is that perl's -w switch is not mandatory. At least, that's what man perl says. |