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

Alfred 3.4.1 - Quick launcher for apps a...
Alfred is an award-winning productivity application for OS X. Alfred saves you time when you search for files online or on your Mac. Be more productive with hotkeys, keywords, and file actions at... Read more
Maintenance 2.2.7 - System maintenance u...
Maintenance is a system maintenance and cleaning utility. It allows you to run miscellaneous tasks of system maintenance: Check the status of the hard disk Repair permissions Run periodic scripts... Read more
NeoOffice 2017 - Mac-tailored, OpenOffic...
NeoOffice is a complete office suite for OS X. With NeoOffice, users can view, edit, and save OpenOffice documents, PDF files, and most Microsoft Word, Excel, and PowerPoint documents. NeoOffice 3.x... Read more
SyncTwoFolders 2.2.4 - Syncs two user-sp...
SyncTwoFolders simply synchronizes two folders. It supports synchronization across mounted network drives and it is a possibility to run a simulation showing in a log what will be done. Please visit... Read more
Hopper Disassembler 4.2.13- - Binary dis...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more
iFFmpeg 6.4.3 - Convert multimedia files...
iFFmpeg is a comprehensive media tool to convert movie, audio and media files between formats. The FFmpeg command line instructions can be very hard to master/understand, so iFFmpeg does all the hard... Read more
Firefox 55.0.2 - Fast, safe Web browser.
Firefox offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals and casual... Read more
FileZilla 3.27.1 - Fast and reliable FTP...
FileZilla (ported from Windows) is a fast and reliable FTP client and server with lots of useful features and an intuitive interface. Version 3.27.1: Fixed Vulnerabilities: Change client... Read more
Merlin Project 4.2.7 - $289.00
Merlin Project is the leading professional project management software for OS X. If you plan complex projects on your Mac, you won’t get far with a simple list of tasks. Good planning raises... Read more
Dashlane 4.8.4 - Password manager and se...
Dashlane is an award-winning service that revolutionizes the online experience by replacing the drudgery of everyday transactional processes with convenient, automated simplicity - in other words,... Read more

Roll to Win with Game of Dice’s new upda...
Joycity’s hit Game of Dice gets a big new update this week, introducing new maps, mechanics, and even costumes. The update sets players loose on an exciting new map, The Cursed Tower, that allows folks to use special Runes mid-match. If you feel... | Read more »
Bottom of the 9th (Games)
Bottom of the 9th 1.0.1 Device: iOS iPhone Category: Games Price: $4.99, Version: 1.0.1 (iTunes) Description: Play the most exciting moment of baseball in this fast-paced dice and card game! | Read more »
The best apps for viewing the solar ecli...
If you somehow missed the news, many parts of the United States will be witness to a total solar eclipse on August 21 for the first time in over 90 years. It'll be possible to see the eclipse in at least some capacity throughout the continental U... | Read more »
The 5 best mobile survival games
Games like ARK: Survival Evolved and Conan Exiles have taken the world of gaming by storm. The market is now flooded with hardcore survival games that send players off into the game's world with nothing but maybe the clothes on their back. Never... | Read more »
Portal Walk (Games)
Portal Walk 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Portal Walk is adventure and relaxing platform game about Eugene. Eugene stuck between worlds and trying to find way back home.... | Read more »
Technobabylon (Games)
Technobabylon 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: City of Newton, 2087. Genetic engineering is the norm, the addictive Trance has replaced almost any need for human interaction,... | Read more »
5 reasons why 2v2 is the best mode in Cl...
Supercell has been teasing fans with 2v2 windows that allow players to team up for limited periods of time. The Summer of 2v2 was just this past July, but players are already clamoring for more of that sweet, sweet team-based action. The fans have... | Read more »
The best deals on the App Store this wee...
It seems like the week's only just started, and yet here we are with a huge pile of discounted games to sort through. There are some real doozies on sale this week. We're talking some truly stellar titles. Let's take a look at four of the best... | Read more »
Cat Quest Guide - How to become a purrfe...
Cat Quest is an absolutely charming open-world RPG that's taken the gaming world quite by storm. This game about a world populated by furry kitty warriors is actually a full-length RPG with sturdy mechanics and a lovely little story. It's certainly... | Read more »
Silly Walks Guide - How to strut your st...
Silly Walks is an all new adventure game that lives up to its name. It sees you playing as a variety of snack foods as you teeter-totter your way to rescue your friends from the evil blender and his villainous minions. It's all very . . . well... | Read more »

Price Scanner via MacPrices.net

13-inch 2.3GHz MacBook Pros on sale for $100...
Amazon has the new 2017 13″ 2.3GHz MacBook Pros on sale today for $100 off MSRP, each including free shipping: – 13″ 2.3GHz/128GB Space Gray MacBook Pro (MPXQ2LL/A): $1199.99 $100 off MSRP – 13″ 2.... Read more
New iOS 11 Productivity Features Welcome But...
The iOS community is in late summer holding mode awaiting the September arrival of the iPhone 8 and iOS 11. iOS 11 public betas have been available for months — number six was released this week —... Read more
Samsung Electronics Launches New Portable SSD...
Samsung Electronics America, Inc. has announced the launch of Samsung Portable SSD T5 – its newest portable solid state drive (PSSD) that raises the bar for the performance of external memory... Read more
TrendForce Reports YoY Gain of 3.6% for 2Q17...
Market research firm TrendForce reports that the global notebook shipments for this second quarter registered a sequential quarterly increase of 5.7% and a year-on-year increase of 3.6%, totaling 39.... Read more
Sale! 10-inch iPad Pros for $50 off MSRP, no...
B&H Photo has 10.5″ iPad Pros in stock today and on sale for $50 off MSRP. Each iPad includes free shipping, and B&H charges sales tax in NY & NJ only: – 10.5″ 64GB iPad Pro: $599, save $... Read more
Sale! 2017 13-inch Silver 2.3GHz MacBook Pro...
Amazon has new 2017 13″ 2.3GHz/128GB Silver MacBook Pro on sale today for $100 off MSRP including free shipping. Their price is the lowest available for this model from any reseller: – 13″ 2.3GHz/... Read more
WaterField Unveils Collaboratively-Designed,...
In collaboration with customers and seasoned travelers, San Francisco maker WaterField Designs set out to create the preeminent carry-on system to improve the experience of frequent fliers. The... Read more
Miya Notes Mac-Client for Google Keep (Launch...
MacPlus Software has announced te launch of Miya Notes for Google Keep 1.0, a powerful Mac-client for Google Keep. Millions of people use Google Keep on their phones and online, but a convenient Mac... Read more
Apple refurbished iMacs available starting at...
Apple has previous-generation Certified Refurbished 2015 21″ & 27″ iMacs available starting at $849. Apple’s one-year warranty is standard, and shipping is free. The following models are... Read more
2017 13-inch MacBook Airs on sale for $100 of...
B&H Photo new 2017 13″ MacBook Airs on sale today for $100 off MSRP, starting at $899: – 13″ 1.8GHz/128GB MacBook Air (MQD32LL/A): $899, $100 off MSRP – 13″ 1.8GHz/256GB MacBook Air (MQD42LL/A... Read more

Jobs Board

Development Operations and Site Reliability E...
Development Operations and Site Reliability Engineer, Apple Payment Gateway Job Number: 57572631 Santa Clara Valley, California, United States Posted: Jul. 27, 2017 Read more
Frameworks Engineering Manager, *Apple* Wat...
Frameworks Engineering Manager, Apple Watch Job Number: 41632321 Santa Clara Valley, California, United States Posted: Jun. 15, 2017 Weekly Hours: 40.00 Job Summary Read more
*Apple* Customer Experience (ACE) Leader - A...
…management to deliver on business objectivesTraining partner store staff on Apple products, services, and merchandising guidelinesCoaching partner store staff on Read more
*Apple* Solutions Consultant (ASC) - Poole -...
Job Summary The people here at Apple don't just create products - they create the kind of wonder that's revolutionised entire industries. It's the diversity of those Read more
Business Development Manager, *Apple* iClou...
Job Summary Apple is seeking an entrepreneurial person to help grow the Apple iCloud business, a service that is integral to the Apple customer experience. Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.