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.

 
AAPL
$524.94
Apple Inc.
+5.93
MSFT
$40.01
Microsoft Corpora
-0.39
GOOG
$536.10
Google Inc.
-20.44

MacTech Search:
Community Search:

Software Updates via MacUpdate

Tweetbot 1.5.1 - Popular iOS twitter cli...
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more
Mac DVDRipper Pro 4.1.7 - 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
PDFpenPro 6.2 - Advanced PDF toolkit for...
PDFpenPro allows users to edit PDF's easily. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Create... Read more
PDFpen 6.2 - Edit and annotate PDFs with...
PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more
Monolingual 1.5.9 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Starcraft II: Wings of Liberty 1.1.1.180...
Download the patch by launching the Starcraft II game and downloading it through the Battle.net connection within the app. Starcraft II: Wings of Liberty is a strategy game played in real-time. You... Read more
Sibelius 7.5.0 - Music notation solution...
Sibelius is the world's best-selling music notation software for Mac. It is as intuitive to use as a pen, yet so powerful that it does most things in less than the blink of an eye. The demo includes... Read more
Typinator 5.9 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
MYStuff Pro 2.0.16 - Create inventories...
MYStuff Pro is the most flexible way to create detail-rich inventories for your home or small business. Add items to MYStuff by dragging and dropping existing information, uploading new images, or... Read more

Latest Forum Discussions

See All

Have a Special Dead Trigger 2 Easter Bas...
Have a Special Dead Trigger 2 Easter Basket Full of Goodies, Courtesy of Madfinger Games Posted by Rob Rich on April 18th, 2014 [ permalink ] Dead Trigger 2 | Read more »
Zynga Launches Brand New Farmville Exper...
Zynga Launches Brand New Farmville Experience with Farmville 2: Country Escape Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
David. Review
David. Review By Cata Modorcea on April 18th, 2014 Our Rating: :: MINIMALISTIC IN A DIFFERENT WAYUniversal App - Designed for iPhone and iPad David is a minimalistic game wrapped inside of a soothing atmosphere in which the hero... | Read more »
Eyefi Unveils New Eyefi Cloud Service Th...
Eyefi Unveils New Eyefi Cloud Service That Allows Users to Share Media Across Personal Devices Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
Tales from the Dragon Mountain: The Lair...
Tales from the Dragon Mountain: The Lair Review By Jennifer Allen on April 18th, 2014 Our Rating: :: STEADY ADVENTURINGiPad Only App - Designed for the iPad Treading a safe path, Tales from the Dragon Mountain: The Lair is a... | Read more »
Yahoo Updates Flickr App with Advanced E...
Yahoo Updates Flickr App with Advanced Editing Features and More Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
My Incredible Body - A Kid's App to...
My Incredible Body - A Kid's App to Learn about the Human Body 1.1.00 Device: iOS Universal Category: Education Price: $2.99, Version: 1.1.00 (iTunes) Description: Wouldn’t it be cool to look inside yourself and see what was going on... | Read more »
Trials Frontier Review
Trials Frontier Review By Carter Dotson on April 18th, 2014 Our Rating: :: A ROUGH LANDINGUniversal App - Designed for iPhone and iPad Trials Frontier finally brings the famed stunt racing franchise to mobile, but how much does its... | Read more »
Evernote Business Notebook by Moleskin I...
Evernote Business Notebook by Moleskin Introduced – Support Available in Evernote for iOS Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
Sparkle Unleashed Review
Sparkle Unleashed Review By Jennifer Allen on April 18th, 2014 Our Rating: :: CLASSY MARBLE FLINGINGUniversal App - Designed for iPhone and iPad It’s a concept we’ve seen before, but Sparkle Unleashed is a solidly enjoyable orb... | Read more »

Price Scanner via MacPrices.net

iMacs on sale for up to $160 off MSRP this we...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
iPad Airs on sale this weekend for up to $100...
Best Buy has WiFi iPad Airs on sale for $50 off MSRP and WiFi + Cellular iPad Airs on sale for $100 off MSRP on their online store for a limited time, with prices now starting at $449. Choose free... Read more
Apple restocks refurbished Mac minis starting...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more
Hyundai Brings Apple CarPlay To The 2015 Sona...
Hyundai Motor America has announced it will bring Apple CarPlay functionality to the 2015 Sonata. CarPlay is pitched as a smarter, safer and easier way to use iPhone in the car and gives iPhone users... Read more
Updated iPads Coming Sooner Than We Had Thoug...
MacRumors, cites KGI securities analyst Ming Chi Kuo, well-respected as an Apple product prognisticator, saying that Apple will introduce an upgraded iPad Air and iPad mini in 2014/Q3, meaning the... Read more
Toshiba Unveils New High And Low End Laptop M...
Toshiba has announced new laptop models covering both the high-end and low-end of the notebook computer spectrum. Toshiba 4K Ultra HD Laptop Toshiba’s new Satellite P55t features one of the world’s... Read more
Save up to $270 with Apple refurbished 13-inc...
The Apple Store has Apple Certified Refurbished October 2013 13″ Retina MacBook Pros available starting at $1099, with models up to $270 off MSRP. Apple’s one-year warranty is standard, and shipping... Read more
Apple now offering refurbished iPad mini with...
The Apple Store has Certified Refurbished 2nd generation iPad minis with Retina Displays now available starting at $339. Apple’s one-year warranty is included with each model, and shipping is free.... Read more
Microsoft Blinks – Drops Microsoft Office 365...
Microsoft has dropped the annual subscription fee for Microsoft Office 365 Personal – which is needed in order to create and edit documents in Microsoft Office for iPad. However, Apple’s iOS and OS X... Read more
New AVG Vault Apps for iOS and Android Help K...
AVG Technologies N.V. an online security company for 177 million active users, has announced the launch of its latest mobile application, AVG Vault. The free app introduces an innovative user... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Retail - Manager - Holyoke - Apple I...
Job Summary Keeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you’re a master of them all. In the store’s fast-paced, Read more
*Apple* Retail - Manager - Apple (United Sta...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you're a master of them all. In the store's fast-paced, dynamic Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Retail - Market Leader - Cincinnati...
…challenges of developing individuals, building teams, and affecting growth across Apple Stores. You demonstrate successful leadership ability - focusing on excellence Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.