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

Herald 5.0.2 - Notification plugin for M...
Note: Versions 2.1.3 (for OS X 10.7), 3.0.6 (for OS X 10.8), and 4.0.8 (for OS X 10.9) are no longer supported by the developer. Herald is a notification plugin for Mail.app, Apple's Mac OS X email... Read more
DEVONthink Pro 2.8.3 - Knowledge base, i...
Save 10% with our exclusive coupon code: MACUPDATE10 DEVONthink Pro is your essential assistant for today's world, where almost everything is digital. From shopping receipts to important research... Read more
Boom 2 1.0.1 - System-wide pro audio app...
Boom 2 is a system-wide volume booster and equalizer app that is designed especially for OS X 10.10 Yosemite. It comes with a smart interface, self-calibrates itself according to your Mac, offers... Read more
Apple Security Update 2015-001 - For OS...
Apple Security Update 2015-001 is recommended for all users and improves the security of OS X. For detailed information about the security content of this update, please visit: http://support.apple.... Read more
Drive Genius 4.0.1 - Powerful system uti...
Drive Genius 4 gives you faster performance from your Mac while also protecting it. The award-winning and improved DrivePulse feature alerts you to hard drive issues before they become major problems... Read more
Yosemite Cache Cleaner 9.0.3 - Clear cac...
Yosemite Cache Cleaner is an award-winning general purpose tool for OS X. YCC makes system maintenance simple with an easy point-and-click interface to many OS X functions. Novice and expert users... Read more
xScope 4.1.1 - Onscreen graphic measurem...
xScope is powerful set of tools that are ideal for measuring, inspecting, and testing on-screen graphics and layouts. Its tools float above your desktop windows and can be accessed via a toolbar,... Read more
OS X Yosemite 10.10.2 - Apple's lat...
OS X Yosemite is Apple's newest operating system for Mac. An elegant design that feels entirely fresh, yet inherently familiar. The apps you use every day, enhanced with new features. And a... Read more
Apple iOS 8.1.3 - The latest version of...
The latest version of iOS can be downloaded through iTunes. Apple iOS 8 comes with big updates to apps you use every day, like Messages and Photos. A whole new way to share content with your family.... Read more
SpamSieve 2.9.19 - Robust spam filter fo...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more

Storm & Skye – Magical Adventure Sto...
Storm & Skye – Magical Adventure Story For Kids Review By Amy Solomon on January 28th, 2015 Our Rating: iPhone App - Designed for the iPhone, compatible with the iPad The first book in the Storm & Skye series includes... | Read more »
New WOTA: U-Boat Compass ‘Wets’ Your App...
New WOTA: U-Boat Compass ‘Wets’ Your Appetite for WOTA: Wolves of the Atlantic. Posted by Jessica Fisher on January 27th, 2015 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Warship Solitaire Review
Warship Solitaire Review By Nadia Oxford on January 27th, 2015 Our Rating: :: A WAR OF NUMBERSUniversal App - Designed for iPhone and iPad Warship Solitaire might lack frills and looks, but it offers some thought provoking... | Read more »
Keep Track of Your Hectic Work Schedule...
Keep Track of Your Hectic Work Schedule With Shifts Posted by Jessica Fisher on January 27th, 2015 [ permalink ] iPhone App - Designed for the iPhone, compatible with the iPad | Read more »
All Star Quarterback Review
All Star Quarterback Review By Campbell Bird on January 27th, 2015 Our Rating: :: PLAY YOUR PARTUniversal App - Designed for iPhone and iPad Train your way to the Super Bowl in this free-to-play sports management game.   | Read more »
Sentinel 4: Dark Star Adds New Campaign...
Sentinel 4: Dark Star Adds New Campaign and More Posted by Ellis Spice on January 27th, 2015 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Wheel & Deal Review
Wheel & Deal Review By Tre Lawrence on January 27th, 2015 Our Rating: :: SIMPLE FUNUniversal App - Designed for iPhone and iPad A simple arcade shooter that has a retro feel.   | Read more »
Join the NAVY in a Big New Update for WW...
Join the NAVY in a Big New Update for WW2: Sandbox. Strategy & Tactics Posted by Jessica Fisher on January 27th, 2015 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Earthworm Alchemy Review
Earthworm Alchemy Review By Campbell Bird on January 27th, 2015 Our Rating: :: FEED THE WORMUniversal App - Designed for iPhone and iPad Feed this funny looking worm and dodge bombs in this charming, free-to-play arcade game.   | Read more »
Graphic Novel App netwars – The Butterfl...
Graphic Novel App netwars – The Butterfly Attack has Received Some New Episodes Posted by Jessica Fisher on January 27th, 2015 [ permalink ] | Read more »

Price Scanner via MacPrices.net

New Good Management Suite Simplifies Enterpri...
Good Technology has announced the availability of the Good Management Suite, a comprehensive cross-platform solution for organizations getting started with mobile business initiatives. Built on the... Read more
15-inch 2.0GHz Retina MacBook Pro (refurbishe...
The Apple Store has Apple Certified Refurbished previous-generation 15″ 2.0GHz Retina MacBook Pros available for $1489 including free shipping plus Apple’s standard one-year warranty. Their price is... Read more
15-inch 2.5GHz Retina MacBook Pro on sale for...
Amazon has the 15″ 2.5GHz Retina MacBook Pro on sale for $2319.99 including free shipping. Their price is $180 off MSRP, and it’s the lowest price available for this model from any reseller. Read more
iPad Turns Five – The ”Book Mystique
Five years ago this week, the late Steve Jobs took the stage at an Apple special press event to unveil the first Apple iPad — a slab-shaped, hand-holdable device that was to shake the personal... Read more
Sale! 15-inch Retina MacBook Pros for up to $...
B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for $150 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $1889.99... Read more
Apple refurbished Mac minis for up to $150 of...
The Apple Store has restocked Apple Certified Refurbished 2014 Mac minis, with models available starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: - 1.4GHz... Read more
Apple Releases New Tranche Of OS Updates For...
Apple has released incremental bugfix and security updates for its most recent Mac and iOS versions. OS X Yosemite 10.10.2 Update Apple says this update includes the following improvements: Resolves... Read more
Samsung To Be Main Supplier Of A9 Chips For i...
Korea’s Maeil Business Newspaper reports that Samsung has beat out its chipmaking rivals, Taiwan’s Qualcomm and TSMC, as supplier of chipsets for the next generations of both its own Galaxy S6 and... Read more
Save up to $340 on iMacs with Apple Certified...
The Apple Store has Apple Certified Refurbished iMacs available for up to $340 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. These are the best prices on... Read more
AppleCare Protection Plans on sale for up to...
B&H Photo has 3-Year AppleCare Warranties on sale for up to $105 off MSRP including free shipping plus NY sales tax only: - Mac Laptops 15″ and Above: $244 $105 off MSRP - Mac Laptops 13″ and... Read more

Jobs Board

*Apple* Systems Engineer - Pre Sales, Educat...
…is responsible for proactively providing technical expertise to drive sales of Apple solutions into assigned accounts. The SE architects, validates, and assists in Read more
*Apple* Solutions Consultant - Retail Sales...
As an ambassador of the Apple brand, the ASC is accountable for driving sales performance by: Connecting with customers. Discovering customers' needs and values. Showing Read more
*Apple* Solutions Consultant - Retail Sales...
As an ambassador of the Apple brand, the ASC is accountable for driving sales performance by: Connecting with customers. Discovering customers' needs and values. Showing Read more
*Apple* Tree Children's Center is hirin...
Apple Tree Learning Center is Hiring Preschool Teachers! Phoenix Childrens Academy is a national leader in the early childhood education industry and we are currently 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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.