TweetFollow Us on Twitter

Self-Installing Applications

Volume Number: 19 (2003)
Issue Number: 8
Column Tag: Programming

Self-Installing Applications

Or "How I became Installer Free"

by Kenneth H. Wieschhoff, Jr.

Introduction

The last step in any software development process is to create an installer. This can be Apple's PackageMaker, InstallerVise by MindVision, and the like. By making your application self-installing you can update your frameworks, libraries, (and even kernel extensions) dynamically prior to running your main application. Your users can run your application anywhere and you can include incremental updates to your software. This makes a more enjoyable user experience.

The Secret

In short.... Bundles! To the user your application looks like a single item, but in reality it can be an entire suite of tools. You create an Installer application that checks your support files (frameworks, kexts, libraries, et. al.) are present and up to date. Missing/outdated items are installed using Apple's Authorization Services API. Finally, this Installer launches the main application and quits. You store all the items, including the main application, in the Resources Folder within the Installer application.

The Steps

Here's a checklist of thing to do:

Create the self-installer application. Add your main application's icon.

Write code in main to verify your support items are installed and up to date, and then launch the main application.

Create the scripts which will install a given support item. Authorization Services will run these scripts for you at a privileged level.

Create "move" scripts to assist the build process, which move your support files and main application from their respective build directories into the Resources folder within the Installer.

Create the Self-Installer application

When I'm developing a software product that contains many different parts, I usually create a folder to hold all the pieces. In addition to making source control easier to manage, it will simplify creating the "move" scripts. More on that later.

Here's an example of a folder layout for MyApp:

MyApplication
MyApp (the main (real) application)
MyFramework
MySelfInstaller (also produces an application called MyApp)

You'll use Project Builder to create a new "Cocoa Application" in your (new) MySelfInstaller directory. Let's have a look at the main code:

Listing 1: main.m

main.m
Check if support items exist and are up-to-date and then launch the main application.
#import <Cocoa/Cocoa.h>
#include <Security/Authorization.h>
#include <Security/AuthorizationTags.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/param.h>
bool            CheckItemUpToDate(char *where, char *what);
OSStatus      DoInstall(char *script);
void            LaunchMainApplication();
AuthorizationRef gAuthorizationRef = 0;
int main(int argc, const char *argv[])
{
   NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
   OSStatus    stat = noErr;
   if ( !CheckItemUpToDate("/Library/Frameworks/",
             "MyFramework.framework"))
      stat = DoInstall("InstallFramework.sh");
   if ( stat == noErr)
      LaunchMainApplication();
  
   // Release the authorization reference    
   if(gAuthorizationRef)
      AuthorizationFree(gAuthorizationRef,
                         kAuthorizationFlagDefaults);
   [pool release];
   
   return (EXIT_SUCCESS);
}

Main checks that the framework is installed on the user's system and installs it if it's out of date or missing. It then launches the main application and then quits. (Noticeably absent in this example is some way to alert the user that installation of an item failed.)

Listing 2: main.m

main.m
The CheckItemUpToDate checks to see if a given support item is up to date.
bool CheckItemUpToDate(char *where, char *what){
   // Guilty until proven innocent.
   bool         installNotNeeded = false;
   // Create the path to the installed support item.
   NSString      *fullPath = 
         [NSString stringWithFormat:@"%s%s", where, what];
   // Get the url of the "SupportItems" folder
   // within the Resources folder of our application
   CFURLRef      url =
          CFBundleCopyResourceURL(CFBundleGetMainBundle(),
             CFSTR("SupportItems"), NULL, NULL);
   // Get the path to the support item within 
   // our own Resources bundle
   NSString      *supportPath = 
         [NSString stringWithFormat:@"%@%s", 
            [((NSURL *)url) path], what];
   // Get the bundle of the installed support item
   NSBundle      *instExt = [NSBundle bundleWithPath:fullpath];
   // If the extension exists...
   if (instExt) {
      //...load it's dictionary.
      NSDictionary *installedDict = [instExt infoDictionary];
    
      // If the dictionary exists...
      if (installedDict) {
         // Get the short version string
         NSString *version = [installedDict
                objectForKey:@"CFBundleShortVersionString"];
         if (version){
            // Do the entire thing again for the support tool
            NSBundle *local = 
               [NSBundle bundleWithPath:toolpath];
                
            if ( local) {
               NSDictionary *locDict = [local infoDictionary];
                    
               if (locDict) {
                  NSString * locVers = [locDict objectForKey:
                                  @"CFBundleShortVersionString"];
             // Compare the two strings for a match.
                  if ([locVers compare:version] ==NSOrderedSame)
                     installNotNeeded = true;
            }
         }
      }
   }
}
   return installNotNeeded;
}

CheckItemUpToDate compares the Short Version Strings of the installed item against the item packaged in the bundle. Updating the version string causes the item to be re-installed. Up to date items are not re-installed.

The MAGIC

The Authorization Services API provides a way to execute a script that needs privileges.

Listing 3: main.m

main.m
DoInstall gets authorization from the user to execute a script at a privileged level and executes 
the script.

OSStatus DoInstall(char *scriptName)
{
   char myToolPath[MAXPATHLEN];
   char *myArguments[2] = {NULL, NULL};
   FILE *myPipe = NULL;
   char myReadBuffer[128];
   AuthorizationFlags myFlags = kAuthorizationFlagDefaults;
   AuthorizationItem myItems[] = { 
      // For this example we're using the standard 
      // command interpreter 
{kAuthorizationRightExecute, 
      strlen("/bin/sh"), "/bin/sh", 0}};
AuthorizationRights myRights = {
      sizeof(myItems)/sizeof(AuthorizationItem), myItems };
   OSStatus myStatus; 
    
   // Tell the developer what script is being run
   printf("Running install script %s\n", scriptName);
      
   // Store the authorization in a global so we don't keep
   // asking the user for it once it's given.
  if ( gAuthorizationRef == 0) {
      // Create the authorization reference.
      myStatus = AuthorizationCreate
                     (   NULL, 
                        kAuthorizationEmptyEnvironment,
                         myFlags, 
                        &gAuthorizationRef);
      if(myStatus != errAuthorizationSuccess) goto bail;
        
      // Set flags to create our authorization environment.
      // Request to be pre-authorized to run any tool.
      myFlags =   kAuthorizationFlagDefaults |
                      kAuthorizationFlagInteractionAllowed |
                       kAuthorizationFlagPreAuthorize |
                       kAuthorizationFlagExtendRights;
    
       // This puts the Authorization dialog on the screen
      // and adds the authorization rights to the
       // authorization reference, gAuthorizationRef.
      myStatus = AuthorizationCopyRights 
                     (   gAuthorizationRef, 
                        &myRights, 
                        NULL, 
                        myFlags, 
                        NULL );
      if(myStatus != errAuthorizationSuccess) goto bail;
   }
    
  // If we have a valid authorization reference...
   if ( gAuthorizationRef) {
      myFlags = kAuthorizationFlagDefaults;
   // Get the path for the script to run 
   myStatus = GetScriptPath(myToolPath, scriptName);
      if(myStatus) goto bail;
      myArguments[0] = myToolPath;
      // Finally, execute our script.
      myStatus = AuthorizationExecuteWithPrivileges
                     (   gAuthorizationRef, 
                        "/bin/sh", 
                        myFlags, 
                        myArguments, 
                        &myPipe);
   
      if(myStatus == errAuthorizationSuccess) {
         for(;;) {
            // Scripts send output back through myPipe which is
             // redisplayed on standard out
            int bytesRead = read
                                    (fileno(myPipe),
                               myReadBuffer, 
                              sizeof(myReadBuffer))
      // No more data!
            if(bytesRead < 1) 
               break;
            write(fileno(stdout), myReadBuffer, bytesRead);
         }
      } 
      else 
         printf("AuthorizationExecuteWithPrivileges "
                     "returned %ld\n", myStatus);
   }
bail:
   return myStatus;
}

DoInstall creates an Authorization reference if it doesn't already exist, and uses that authorization reference to allow the user to add privileges to execute the shell script passed to it. In addition, any output produced by the script will be echoed on standard out. This can be a valuable debugging aid when a script is giving you trouble as you can use standard shell commands like "echo" and "pwd" in your script and see the output in your Run window in Project Builder.

Launch the application

This is accomplished by calling [NSWorkspace launchApplication].

Listing 4: main.m

main.m

LaunchMainApplication gets a path to the main application located within the bundle and launches it.

void LaunchMainApplication() {
   // Get the ref to the main app in the resources folder
   CFURLRef url = CFBundleCopyResourceURL(
                            CFBundleGetMainBundle(),
                            CFSTR("MyApp.app"), 
                           NULL, NULL);
    NSString         *path = [NSString stringWithFormat:
                         @"%@Contents/MacOS/MyApp", 
                        [((NSURL *)url) path]];
    
  [[NSWorkspace sharedWorkspace] launchApplication:path];
}

An installer script

Once the user has granted the application permission to perform privileged commands, you can do anything you need to install your support item in the script.

    ***Warning***. Your script now has omniscient powers! You can cause irreparable damage to the user's system in your script if you're not careful.

Listing 5: InstallFramework.sh

InstallFramework.sh

Here's an example script which copies a framework to the system and calls update_prebinding to 
calculate the locations of functions within a library so applications will launch faster.

#!/bin/sh
# check if the global frameworks directory exists, 
# create it if not
if [ ! -d /Library/Frameworks ]; then
     # create
   mkdir /Library/Frameworks
     # set group to the administrative group
   chgrp staff /Library/Frameworks
     # allow group users to modify
   chmod 775 /Library/Frameworks
fi
# make sure the framework exists within our application
if [ -d \
   MyApp.app/Contents/Resources/SupportItems/\
MyFramework.framework ]; then
   # check if the framework exists in /Library/Frameworks
    # delete it if it is
  if [ -d /Library/Frameworks/MyFramework.framework ]; then
    rm -rf /Library/Frameworks/MyFramework.framework
   fi
    
   # copy our framework to the system   
   cp -Rp \   
      MyApp.app/Contents/Resources/SupportItems/\
Myframework.framework \
      /Library/Frameworks
   
   # change file modes on the new framework  
   chmod -R ogu+r \
       /Library/Frameworks/MyFramework.framework
  
   # call update_prebing  
    /usr/bin/update_prebinding  -files \
          /Library/Frameworks/MyFramework.framework
else
      # Perhaps you forgot to add the file to the framework?
    echo MyFramework.framework is missing from build!
fi

Last Step - "Move" Scripts

When you're building the Installer, Project Builder makes it easy for you to move your support items and main application into the Resources folder by adding an extra step to the build process. Select the Targets tab on the main project window and select the <MySelfInstaller> target. From the Project Menu, select "New Build Phase" and the "New Shell Script Build Phase" submenu item.

In the "Shell:" text area enter "/bin/sh" if this is the shell you normally work with. (Note you can use any shell you'd like). In the second text area enter "exec ./moveMyFramework.sh". Let's look at an example of the script:

Listing 6: moveMyFramework.sh

MoveMyFramework.sh

This script copies the framework from a sibling directory into our SupportItems folder.

#!/bin/sh
# if the framework already exists, delete it
if [ -d "build/MyApp.app/Contents/Resources/\
SupportItems/MyFramework.framework" ]; then
   rm -rf \
      "build/MyApp.app/Contents/Resources/\
SupportItems/MyFramework.framework"
fi
# Check the "SupportItems" folder actually exists
if [ ! -d \
   "build/MyApp.app/Contents/Resources/SupportItems" ]; then
   mkdir \
 "build/MyApp.app/Contents/Resources/SupportItems"
fi
# Finally, copy the framework
cp -Rp ../MyFramework/build/MyFramework.framework \
    build/MyApp.app/Contents/Resources/SupportItems

Don't get confused by the multiple references to "MyApp". Remember, the goal is to make the user unaware they're actually running multiple applications. You'll want to add your application's icons to the self-installer to complete the effect.

Conclusion

This approach has numerous advantages to rolling out new versions of your application and provides a very nice user experience, as the user only has to authorize once during the initial run of the application. Absent is a way to un-install the application and supporting files. This is left as an exercise for the reader.

References:

Authorization Services Reference:

http://developer.apple.com/documentation/Security/Reference/authorization_ref/


Ken Wieschhoff lives near Atlanta and works for Altea Therapeutics by day as an 8051 embedded programmer (and some Windows MFC/GUI stuff) and Eskape Labs by night where he does Cocoa programming (and occasionally device drivers) for their MyTV product line. Weekends you can find him scuba diving, riding his new Honda Valkyrie Rune, pickin' a guitar and eating grits. He can be reached at weesh@mindspring.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

PopChar 7.1 - Floating window shows avai...
We're also selling a 5-license family pack for only $25.99! PopChar helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to... Read more
BBEdit 11.1.1 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more
Picasa 3.9.139 - Organize, edit, and sha...
Picasa and Picasa Web Albums allows you to organize, edit, and upload your photos to the Web from your computer in quick, simple steps. Arrange your photos into folders and albums and erase their... Read more
Mac DVDRipper Pro 5.0.5 - Copy, backup,...
Mac DVDRipper Pro is the DVD backup solution that lets you protect your DVDs from scratches, save your batteries by reading your movies from your hard disk, manage your collection with just a few... Read more
NetShade 6.2 - Browse privately using an...
This promotion is for NetShade and 1 year of Proxy and VPN services NetShade is an anonymous proxy and VPN app+service for Mac. Unblock your Internet through NetShade's high-speed proxy and VPN... Read more
CrossOver 14.1.3 - Run Windows apps on y...
CrossOver can get your Windows productivity applications and PC games up and running on your Mac quickly and easily. CrossOver runs the Windows software that you need on Mac at home, in the office,... Read more
Little Snitch 3.5.3 - Alerts you about o...
Little Snitch gives you control over your private outgoing data. Track background activity As soon as your computer connects to the Internet, applications often have permission to send any... Read more
OmniGraffle Pro 6.2.3 - Create diagrams,...
OmniGraffle Pro helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use... Read more
OmniFocus 2.2 - GTD task manager with iO...
OmniFocus helps you manage your tasks the way that you want, freeing you to focus your attention on the things that matter to you most. Capturing tasks and ideas is always a keyboard shortcut away in... Read more
1Password 5.3.2 - Powerful password mana...
1Password is a password manager that uniquely brings you both security and convenience. It is the only program that provides anti-phishing protection and goes beyond password management by adding Web... Read more

MooVee - Your Movies Guru (Entertainmen...
MooVee - Your Movies Guru 1.0 Device: iOS iPhone Category: Entertainment Price: $1.99, Version: 1.0 (iTunes) Description: MooVee helps you effortlessly manage your movies, on your iPhone. | Read more »
Geometry Wars 3: Dimensions (Games)
Geometry Wars 3: Dimensions 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: Enjoy the next chapter in the award-winning Geometry Wars franchise and enjoy stunning, console-quality... | Read more »
CHAOS RINGS Ⅲ (Games)
CHAOS RINGS Ⅲ 1.0.0 Device: iOS Universal Category: Games Price: $19.99, Version: 1.0.0 (iTunes) Description: The newest addition to the popular smartphone RPG series is finally here! ・CHAOS RINGS Overview | Read more »
The Popular Insight Series of Travel Gui...
Getting around in a country when you can't understand the primary language can be tough. Fortunately there are several options available to help wold travellers with the important stuff like giving directions to a cab driver or asking where the... | Read more »
Desktop Dungeons is Now on the iPad Desp...
Desktop Dungeons has been a well-loved roguelike on PC for quite some time, and now it's finally available for the iPad! Just the iPad, though. Sorry iPhone users. [Read more] | Read more »
Moleskine Timepage – Calendar for iCloud...
Moleskine Timepage – Calendar for iCloud, Google & Exchange 1.0 Device: iOS iPhone Category: Productivity Price: $4.99, Version: 1.0 (iTunes) Description: The most elegant calendar for your pocket and wrist, Timepage is a... | Read more »
QuizUp Gets Social in its New Update
Plain Vanilla Corp has released a new and improved version of their popular trivia game, QuizUp. The app now emphasizes social play so you can challenge friends from all over the world. [Read more] | Read more »
The Deep (Games)
The Deep 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Swipe Controls Delve into the deep in this retro rogue-like! Swipe to move your diver around and keep away from the enemies as you... | Read more »
Sproggiwood (Games)
Sproggiwood 1.2.8 Device: iOS Universal Category: Games Price: $9.99, Version: 1.2.8 (iTunes) Description: Sproggiwood was developed for devices with at least 1GB of RAM. We recommend you only download Sproggiwood if your device... | Read more »
Battle of Gods: Ascension (Games)
Battle of Gods: Ascension 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: TURN-BASED TACTICAL COMBATFight tactical battles against the forces of Hades! In Battle of Gods: Ascension you play... | Read more »

Price Scanner via MacPrices.net

Apple refurbished 2014 13-inch Retina MacBook...
The Apple Store has Apple Certified Refurbished 2014 13″ Retina MacBook Pros available for up to $400 off original MSRP, starting at $979. An Apple one-year warranty is included with each model, and... Read more
What Would the ideal Apple Productivity Platf...
For the past four years I’ve kept a foot in both the Mac and iPad camps respectively. my daily computing hours divided about 50/50 between the two devices with remarkable consistency. However, there’... Read more
PageMeUp 1.2.1 Ten Dollar Page Layout Applica...
Paris, France-based Softobe, an OS X software development company, has announced that their PageMeUp v. 1.2.1, is available on the Mac App Store for $9.99. The license can be installed on up to 5... Read more
Eight New Products For USB Type-C Application...
Fresco Logic, specialists in advanced connectivity technologies and ICs, has introduced two new product families targeting the Type-C connector recently introduced across a number of consumer... Read more
Scripps National Spelling Bee Launches Buzzwo...
Scripps National Spelling Bee fans can monitor the action at the 2015 Spelling Bee with the new Buzzworthy app for iOS, Android and Windows mobile devices. The free Buzzworthy app provides friendly... Read more
13-inch 2.5GHz MacBook Pro on sale for $120 o...
B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $979 including free shipping plus NY sales tax only. Their price is $120 off MSRP, and it’s the lowest price for this model (except for Apple’... Read more
27-inch 3.3GHz 5K iMac on sale for $1899, $10...
B&H Photo has the new 27″ 3.3GHz 5K iMac on sale for $1899.99 including free shipping plus NY tax only. Their price is $100 off MSRP. Read more
Save up to $50 on iPad Air 2, NY tax only, fr...
B&H Photo has iPad Air 2s on sale for up to $50 off MSRP including free shipping plus NY sales tax only: - 16GB iPad Air 2 WiFi: $469 $30 off - 64GB iPad Air 2 WiFi: $549.99 $50 off - 128GB iPad... Read more
Updated Mac Price Trackers
We’ve updated our Mac Price Trackers with the latest information on prices, bundles, and availability on systems from Apple’s authorized internet/catalog resellers: - 15″ MacBook Pros - 13″ MacBook... Read more
New 13-inch 2.9GHz Retina MacBook Pro on sale...
B&H Photo has the 13″ 2.9GHz/512GB Retina MacBook Pro on sale for $1699.99 including free shipping plus NY tax only. Their price is $100 off MSRP, and it’s the lowest price for this model from... Read more

Jobs Board

Program Manager, *Apple* Community Support...
**Job Summary** Apple Support Communities ( discussions. apple .com) helps customers get the most from their Apple products and services by providing access to Read more
Senior Data Scientist, *Apple* Retail - Onl...
**Job Summary** Apple Retail - Online sells Apple products to customers around the world. In addition to selling Apple products with unique services such as iPad Read more
*Apple* Solutions Consultant - Retail Sales...
**Job Summary** As an Apple Solutions Consultant (ASC) you are the link between our customers and our products. Your role is to drive the Apple business in a retail Read more
*Apple* Watch SW Application Project Manager...
**Job Summary** The Apple Watch software team is looking for an Application Engineering Project Manager to work on new projects for Apple . The successful candidate Read more
Engineering Manager for *Apple* Maps on the...
…the Maps App Team get to take part in just about any new feature in Apple Maps, often contributing a majority of the feature work. In our day-to-day engineering work, we Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.