mac migration 03 – search for applications and plugins

This is the third article in my migration series and illustrates a scripted solution to the problem of dynamically (and more importantly, automatically) searching through the /Applications directory and separating OS installed apps that are part of your base image from any custom or departmental apps that need to be migrated, and building an array of installer files to be utilized later – all in an effort to provide zero-touch Mac migrations, or at least minimal touch.

The script relies on several static arrays that it will compare to the app list on the old Mac in order to build a list of necessary installs to migrate all departmental/custom apps to the new Mac. Currently these arrays are hard coded into the bash script, but I have plans in place to populate these at run time by reading from something like a csv file in order to protect them from accidental change/deletion by less-experienced coders. One other caveat, bash does not support multi-dimensional arrays so I am using two arrays to act as one multidimensional array. It’s kind of kludgy and not as stable as I’d like, but JAMF is very friendly toward bash, so am I. The first part of this process will be to identify the names of all departmental/custom apps that can be deployed to a Mac and the names of their installer packages. JAMF uses .pkg, .mpkg, and/or .dmg archives as installers, so we’ll start with arrays like the following. As always, use my code freely, but I take no responsibility for you doing so:

## The ordering of the following two arrays is CRITICAL! DO NOT under any circumstances change the order of these arrays!!!!!!!!!!!!!!!!!
DepartmentalApps=('Adobe Acrobat 7.0 Professional' 'Adobe After Effects CS4' 'Adobe Flash CS4' 'Art Ingest' 'BRASS' 'Capture One' 'Casper Admin' 'ColorEyes Display Pro' 'ColorNavigator' 'Corel Painter X' 'Cyberduck' 'DiskTracker' 'EskoArtwork Local License Manager' 'Fetch' 'Final Cut Pro' 'Flash 8' 'FontLab Studio' 'formZ RenderZone Plus' 'Google Earth' 'Google Earth Pro' 'iMovie' 'iShowU' 'iShowU HD' 'Keyspan Serial Assistant' 'MachineWashDelux.plugin' 'MBC4' 'NoiseNinja' 'PhotoZoom Pro 2' 'PhotoZoom Pro 4' 'Portfolio' 'Product Ingest' 'Product Tag' 'Pro Tools LE' 'QuarkXPress 7.01' 'QuarkXPress 8' 'Suitcase Fusion' 'Suitcase Fusion 2' 'Suitcase Fusion 3' 'TextWrangler' 'Tivoli Storage Manager' 'Toast Titanium' 'Toast 6 Titanium' 'VMWare Fusion')

## A capital 'X' denotes that this is unapproved software
DeptAppsInstallers=('AdobeAcrobatPro7.dmg' 'ADBEAFETCS4_LS7.dmg' 'ADBEFLPRCS4Mac_LS1(2).dmg' 'AG ArtIngest10.6_20110607v1.1.dmg' 'Brass2.pkg' 'CaptureOne.dmg' 'AGCasperSuite.dmg' 'AGDeployColorEyes.dmg' 'ColorNavigator.pkg' 'CorelPainterX.dmg' 'Cyberduck.pkg' 'AG Disk Tracker 2.4.2 v1.0.pkg' 'EskoArtwork Data Exchange.mpkg' 'AGFetch5.3.dmg' 'X' 'AG Flah 8 Pro.dmg' 'FontLab Studio.pkg' 'X' 'X' 'X' 'iMovie8.dmg' 'iShowU.dmg' 'iShowU HD.pkg' 'Mac OS X 10.2.8 to 10.6.x_v2.6.4.pkg' 'MachineWashDeluxe.pkg' 'MBC4.dmg' 'NoiseNinja.dmg' 'PhotoZoom2.dmg' 'PhotoZoom Pro 4.dmg' 'Extensis Portfolio 8.5.5.mpkg' 'AG ProductIngest10.6_20110607v1.1.dmg' 'AG ProductTag10.6_20110607v1.1.dmg' 'Install Pro Tools LE.mpkg' 'X' 'QuarkXpress8.5.dmg' 'SuitcaseFusion1.dmg' 'ExtensisSuitcaseFusion2.dmg' 'Suitcase Fusion 3.mpkg' 'TextWrangler.dmg' 'Tivoli Storage Manager.mpkg' 'X' 'X' 'X')

## Certain apps may cause problems with multiple versions, so we keep these known apps' parent folders in a separate array for comparison to existing directories so we know what version app to install
## The ordering of the following two arrays is CRITICAL! DO NOT under any circumstances change the order of these arrays
DepartmentalAppsParents=('ProfileMaker Pro 5.0.10' 'Microsoft Office 2004' 'Microsoft Office 2008' 'Microsoft Office 2011' 'ColorThink221beta2' 'QuarkXpress 6.52' 'Toast 8 Titanium' 'Toast 10 Titanium')

## A capital 'X' denotes that there is no JAMF installer or this is unapproved software
DeptAppsParentsInstallers=('ProfileMaker5010.dmg' 'Microsoft Office 2004 11.6.3 FULL.dmg' 'AG_MSOffice08_1224.dmg' 'X' 'ColorThink221.dmg' 'X' 'X' 'Toast10TitaniumLicensed.dmg')

It’s important to note that the first array “DepartmentalApps” contains the names of all non-core applications that can be installed on any mac. This is NOT how OS X sees the filenames, but rather how the following command sees the filenames (this command is run on each execution to ensure that any newly installed apps are accounted for):

find /Volumes/"$MacName"/Applications -type d -print0 2>/dev/null | xargs -0 basename -s .app > /tmp/"$ProcAppList"/AppList.txt

The above command searches the entire /Applications directory, strips the path so we get just the filename, strips the “.app” extension (so we get a uniform set of filenames), and writes every filename as a list to a file in /tmp. From this list we can get the contents of DepartmentalApps. The second array contains the name of the apps’ corresponding installer as it was added to JAMF. Since bash does not support multi-dimensional arrays we use 2 separate arrays, and the index of each item in DepartmentalApps MATCHES the index of each item in the DeptAppsInstallers array.

Some applications do not have a version number in their name or there can be multiple versions installed (as is the case at my company) so I have created a 3rd array named DepartmentalAppsParents to denote the names of the enclosing folders as OS X sees them. The 4th array is simply a list of the package names as they appear in JAMF. Again the indices of items in both of these arrays match.

The next part simply loops through our first array comparing it to the name list created with the find command and written to the AppList.txt file above, and dynamically populates a 5th array (AppsToXfer) with the installer filenames located in the 2nd array:

AppsToXfer=() ## Array to hold the values of all necessary installer packages
IFS=$'\r' ## We need to change the IFS variable to \r so text files can be read correctly
ATXCount=0 ## a variable to hold the index of the AppsToXfer array
cd /tmp/"$ProcAppList"/
## Get the size of the departmental apps array. Starting position of arrays is 0, so loop to 1 less than the array size to access each position in the array
DeptAppsSize=${#DepartmentalApps[@]}
while read line; do
     for (( i=0; i < $DeptAppsSize; i++)); do
          if [[ "$line" == "${DepartmentalApps[$i]}" ]]; then
               if [[ "$line" == "$PreApp" ]]; then ## compare the current app name to the previous app. If it's the same, skip it. Some apps are written multiple times in a row, so don't process multiples
                    break
               fi
               echo "$line" >> $LogFile
               ## Append the installer file onto the AppstoXfer array for later installation
               AppsToXfer[$ATXCount]="${DeptAppsInstallers[$i]}"
               PreApp="$line"
               ## Advance the AppsToXfer array index to the next blank slot
               ATXCount=$(( $ATXCount + 1 ))
               break ## If the app is found, there is no need to loop through the rest of the DeptApps array. Start with the next App in AppsList.txt
          fi
     done
 done < "AppList.txt"
----------
## Some apps are not obvious as to their versions and there are multiple versions of some of these at AG. Luckily these apps have parent folders that denote their version, so find these folders and check to see whether the actual program executable exists (some apps install plugins only) and if so output it as an application to migrate.
## Get the size of the departmental apps parents array. Starting position of arrays is 0, so loop to 1 less than the array size to access each position in the array
DeptAppsParentsSize=${#DepartmentalAppsParents[@]}
for (( x=0; x < $DeptAppsParentsSize; x++ )); do
     if [ -d /Volumes/"$MacName"/Applications/"${DepartmentalAppsParents[$x]}" ]; then
          case "${DepartmentalAppsParents[$x]}" in
               "Microsoft Office 2004")
                    if [ -e "/Volumes/$MacName/Applications/Microsoft Office 2004/Microsoft Excel" ]; then
                         echo "${DepartmentalAppsParents[$x]}" >> $LogFile
                         AppsToXfer[$ATXCount]="${DeptAppsParentsInstallers[$x]}"
                         ATXCount=$(( $ATXCount + 1 ))
                    fi
                    ;;
               "ProfileMaker Pro 5.0.10")
                    if [ -e "/Volumes/$MacName/Applications/ProfileMaker Pro 5.0.10/ProfileMaker" ]; then
                         echo "${DepartmentalAppsParents[$x]}" >> $LogFile
                         AppsToXfer[$ATXCount]="${DeptAppsParentsInstallers[$x]}"
                         ATXCount=$(( $ATXCount + 1 ))
                    fi
                    ;;
               "QuarkXPress 6"*)
                    if [ -e "/Volumes/$MacName/Applications/QuarkXPress 6"* ]; then
                         echo "${DepartmentalAppsParents[$x]} - Not supported. Upgrade to a newer version of Quark Xpress. Inform the department head that a license needs to be purchased." >> $LogFile
                         AppsToXfer[$ATXCount]="${DeptAppsParentsInstallers[$x]}"
                         ATXCount=$(( $ATXCount + 1 ))
                    fi
                    ;;
               "ColorThink221beta2")
                    if [ -e "/Volumes/$MacName/Applications/ColorThink221beta2/ColorThink 2.2.1b2" ]; then
                         echo "${DepartmentalAppsParents[$x]}" >> $LogFile
                         AppsToXfer[$ATXCount]="${DeptAppsParentsInstallers[$x]}"
                         ATXCount=$(( $ATXCount + 1 ))
                    fi
                    ;;
               "Toast 8 Titanium")
                    if [ -e "/Volumes/$MacName/Applications/Toast 8 Titanium/Toast Titanium.app" ]; then
                         echo "${DepartmentalAppsParents[$x]}" >> $LogFile
                         AppsToXfer[$ATXCount]="${DeptAppsParentsInstallers[$x]}"
                         ATXCount=$(( $ATXCount + 1 ))
                    fi
                    ;;
               "Toast 10 Titanium")
                    if [ -e "/Volumes/$MacName/Applications/Toast 10 Titanium/Toast Titanium.app" ]; then
                         echo "${DepartmentalAppsParents[$x]}" >> $LogFile
                         AppsToXfer[$ATXCount]="${DeptAppsParentsInstallers[$x]}"
                         ATXCount=$(( $ATXCount + 1 ))
                    fi
                    ;;
               "Microsoft Office 2011")
                    if [ -e "/Volumes/$MacName/Applications/Microsoft Office 2011/Microsoft Excel.app" ]; then
                         echo "${DepartmentalAppsParents[$x]}" >> $LogFile
                         AppsToXfer[$ATXCount]="${DeptAppsParentsInstallers[$x]}"
                         ATXCount=$(( $ATXCount + 1 ))
                    fi
                    ;;
               "Microsoft Office 2008")
                    if [ -e "/Volumes/$MacName/Applications/Microsoft Office 2008/Microsoft Excel.app" ]; then
                         echo "${DepartmentalAppsParents[$x]}" >> $LogFile
                         AppsToXfer[$ATXCount]="${DeptAppsParentsInstallers[$x]}"
                         ATXCount=$(( $ATXCount + 1 ))
                    fi
                    ;;
          esac
     fi
 done

The next piece of code loops through the dynamically created AppsToXfer array, and uses JAMF to install all items in the array. This code contains logic to skip apps that have already been installed in case the script gets interrupted and needs to be run a second time.

PreInstalledAppFlag=0 ## have apps been preinstalled? Default to zero, change the flag later
 echo "Now reinstalling departmental applications onto $NewMacBootDrive... " >> $LogFile
 ## Loop through each item in the AppsToXfer array and run jamf install to automatically reinstall these packages. Write the output to the log file and the package name to a secondary "check" file that the program uses to see if apps have already been installed on the new mac.
 for x in "${AppsToXfer[@]}"; do
      ## Has the app been previously installed? Check for the existence of .JAMFAppInstaller-DataMigration.txt
      if [ -f /.JAMFAppInstaller-DataMigration.txt ]; then
           cd /
           while read line; do
           ## Check to see if our package matches any previously installed package, and if so break the loop and set the preinstalled flag to 1
                if [ "$x" == "$line" ]; then
                     PreInstalledAppFlag=1
                     break
                fi
           done < ".JAMFAppInstaller-DataMigration.txt" ## Read from the .JAMFAppInstaller file to see whats already been installed
 # If PreInstalledAppFlag is still 0, the app has not been previously installed so go ahead and install it AND write the package name for future reference
           if [ $PreInstalledAppFlag -ne 1 ]; then
                if [ "$x" != "X" ]; then
                     jamf install -package "$x" -path "/Volumes/$CasperDP/Packages" -target / >> $LogFile
                     echo "$x" >> /.JAMFAppInstaller-DataMigration.txt
                else
                     echo "JAMF installer not found. Is the software approved?" >> $LogFile
                fi
           else
           ## If PreInstalledAppFlag is 1, then log that this app has been installed already, moving to the next app
                echo "$x - Previously installed or no installer available. Checking next app." >> $LogFile
                PreInstalledAppFlag=0
                continue
           fi
      else
      ## If no .JAMFAppInstaller-DataMigration.txt, install the package, create .JAMFAppInstaller-DataMigration.txt, and write the name to .JAMFAppInstaller-DataMigration.txt for future processing. If the installer is 'X,' skip it
           if [ "$x" != "X" ]; then
                jamf install -package "$x" -path "/Volumes/$CasperDP/Packages" -target / >> $LogFile
                echo "$x" >> /.JAMFAppInstaller-DataMigration.txt
           else
                echo "JAMF installer not found. Is the software approved?" >> $LogFile
           fi
      fi
 done
 echo "#########################" >> $LogFile
 echo "Finished installing packages." >> $LogFile
 ## Reset AppsToXfer Array and ATXCount for correct processing on future runs
 AppsToXfer=()
 ATXCount=0

In case anyone is interested in downloading these modules: http://dl.dropbox.com/u/5413877/TransferApps.sh


About this entry