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

TextSoap 8.4 - Automate tedious text doc...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more
Smultron 9.4 - Easy-to-use, powerful tex...
Smultron 9 is an elegant and powerful text editor that is easy to use. Use it to create or edit any text document. Everything from a web page, a note or a script to any single piece of text or code.... Read more
QuarkXPress 13.0.0.0 - Desktop publishin...
QuarkXPress 2017 is the new version that raises the bar for design and productivity. With non-destructive graphics and image editing directly within your layout, you no longer have to choose between... Read more
Brackets 1.9.0 - Open Source Web design...
Brackets is an Open-Source editor for Web design and development built on top of Web technologies such as HTML, CSS, and JavaScript. The project was created and is maintained by Adobe, and is... Read more
Audio Hijack 3.3.4 - Record and enhance...
Audio Hijack (was Audio Hijack Pro) drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio... Read more
Tunnelblick 3.7.1a - GUI for OpenVPN.
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
Amazon Chime 4.3.5721 - Amazon-based com...
Amazon Chime is a communications service that transforms online meetings with a secure, easy-to-use application that you can trust. Amazon Chime works seamlessly across your devices so that you can... Read more
BBEdit 11.6.6 - 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
BBEdit 11.6.6 - 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
Brackets 1.9.0 - Open Source Web design...
Brackets is an Open-Source editor for Web design and development built on top of Web technologies such as HTML, CSS, and JavaScript. The project was created and is maintained by Adobe, and is... Read more

Latest Forum Discussions

See All

Clash of Clans' gets a huge new upd...
Clash of Clans just got a massive new update, and that's not hyperbole. The update easily tacks on a whole new game's worth of content to the hit base building game. In the update, that mysterious boat on the edge of the map has been repaired and... | Read more »
Thimbleweed Park officially headed to iO...
Welp, it's official. Thimbleweed Park will be getting a mobile version. After lots of wondering and speculation, the developers confirmed it today. Thimbleweed Park will be available on both iOS and Android sometime in the near future. There's no... | Read more »
Pokémon GO might be getting legendaries...
The long-awaited legendary Pokémon may soon be coming to Pokémon GO at long last. Data miners have already discovered that the legendary birds, Articuno, Moltres, and Zapdos are already in the game, it’s just a matter of time. [Read more] | Read more »
The best deals on the App Store this wee...
If you’ve got the Monday blues we have just the thing to cheer you up. The week is shaping up to be a spectacular one for sales. We’ve got a bunch of well-loved indie games at discounted prices this week along with a few that are a little more... | Read more »
Honor 8 Pro, a great choice for gamers
Honor is making strides to bring its brand to the forefront of mobile gaming with its latest phone, the Honor 8 Pro. The Pro sets itself apart from its predecessor, the Honor 8, with a host of premium updates that boost the device’s graphical and... | Read more »
The 4 best outdoor adventure apps
Now that we're well into the pleasant, warmer months, it's time to start making the most of the great outdoors. Spring and summer are ideal times for a bit of trekking or exploration. You don't have to go it alone, though. There are plenty of... | Read more »
Things 3 (Productivity)
Things 3 3.0.1 Device: iOS iPhone Category: Productivity Price: $7.99, Version: 3.0.1 (iTunes) Description: Meet the all-new Things! A complete rethinking of the original, award-winning task manager – with a perfect balance between... | Read more »
Oddball mash-up Arkanoid vs Space Invade...
In a move no one was really expecting, Square Enix has put forth an Arkanoid/Space Invaders mash-up aptly titled Arkanoid vs Space Invaders. The game launched today on both iOS and Android and the reviews are actually quite good. [Read more] | Read more »
Arkanoid vs Space Invaders (Games)
Arkanoid vs Space Invaders 1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0 (iTunes) Description: LAUNCH SALE: GET THE GAME AT 20% OFF! Two of the most iconic classic games ever made meet in Arkanoid vs Space... | Read more »
The best new games we played this week
Things got off to a bit of a slow start this week, but as we steadily creep towards Friday a bunch of great games have started cropping up. If you're looking for a quality new release to play this weekend, we've got you covered. Here's a handy... | Read more »

Price Scanner via MacPrices.net

9.7-inch 2017 iPad available for $298, save $...
Sams Club has 32GB 9.7″ Apple iPads available for $298 for Sams Club members. That’s $21 off MSRP. Order online and choose free local store pickup (if available) or free shipping. Read more
touchbyte Releases PhotoSync 3.2 for iOS With...
Hamburg, Germany based touchbyte has announced the release of PhotoSync 3.2 for iOS, a major upgrade to the versatile and powerful app to transfer, backup and share photos and videos over the air.... Read more
Emerson Adds Touchscreen Display and Apple Ho...
Emerson has announced the next evolution of its nationally recognized smart thermostat. The new Sensi Touch Wi-Fi Thermostat combines proven smarthome technology with a color touchscreen display and... Read more
SurfPro VPN for Mac Protects Data While Offer...
XwaveSoft has announced announce the release and immediate availability of SurfPro VPN 1.0, their secure VPN client for macOS. SurfPro VPN allows Mac users to protect their internet traffic from... Read more
13-inch Touch Bar MacBook Pros on sale for $1...
B&H Photo has 13″ MacBook Pros in stock today for up to $150 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 13″ 2.9GHz/512GB Touch Bar MacBook Pro Space Gray (... Read more
Tuesday deal: $200 off 27-inch Apple iMacs
Amazon has select 27″ iMacs on sale for $200 off MSRP, each including free shipping: - 27″ 3.3GHz iMac 5K: $2099 $200 off MSRP - 27″ 3.2GHz/1TB Fusion iMac 5K: $1799 $200 off MSRP Keep an eye on our... Read more
Five To Six Million 10.5-inch iPad Pro Tablet...
Digitimes’ Siu Han and Joseph Tsai report that upstream supply chain shipments for Apple’s new 10.5-inch iPad Pro have been increasing, with monthly shipment volume expected to hit 600,000 units by... Read more
Georgia Tech Students Win Toyota and Net Impa...
Earlier this year, a team of students at Georgia Tech realized that there was a critical gap in transportation services for people who use wheelchairs, and wondered if the solution could be in the... Read more
13-inch 2.0GHz Space Gray MacBook Pro on sale...
Amazon has the 13″ 2.0GHz Space Gray non-Touch Bar MacBook Pro (MLL42LL/A) on sale for $1299.99 including free shipping. Their price is $200 off MSRP, and it’s currently the lowest price available... Read more
Roundup of 15-inch MacBook Pro sale prices, m...
B&H Photo has the new 2016 15″ Apple Touch Bar MacBook Pros in stock today and on sale for up to $200 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 15″ 2.7GHz... Read more

Jobs Board

Director *Apple* ERP Integration Lead - Ast...
…make a real difference. Come, shine with us! Astellas is announcing a Director Apple ERP Integration Lead opportunity in Northbrook, IL. Purpose & Scope: This role Read more
Director *Apple* Platform, IS Data Manageme...
…a real difference. Come, shine with us! Astellas is announcing a Director Apple Platform, IS Data Management Lead opportunity in Northbrook, IL. Purpose & Scope: Read more
Associate Director *Apple* Platform -- SAP...
…real difference. Come, shine with us! Astellas is announcing a Associate Director Apple Platform -- SAP Security, Risks and Controls Lead opportunity in Northbrook, Read more
*Apple* Media Products - Commerce Engineerin...
Apple Media Products - Commerce Engineering Manager Job Number: 57037480 Santa Clara Valley, California, United States Posted: Apr. 18, 2017 Weekly Hours: 40.00 Job Read more
*Apple* Technical Support - Atrilogy (United...
Our direct client is looking for an Apple Technical Support / Apple Help Desk Specialist for a Full Time Direct Hire role in West Los Angeles by Playa Vista, CA Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.