Kyle Ward - Flash, Flex, AIR Developer   
  • Home
  • About
  • Contact
  • Linked In
  •  

    CIC – Communicator Installer Creator

    July 6th, 2010

    air2, flex4, robotlegs

    My employers [www.d6technology.com] provide a Desktop Application [PC/MAC] that allows companies/schools to communicate with their users. The application offers features such as News, Calendar, Gallery, Contacts, Resources etc.

    Although the same code base is used for each clients application, each application differs: Name, ID, Type, Skin etc. We also provide installers [PC/MAC] for our clients which they can either download or are provided by CD.  Like-wise each installer is different.

    Needless to say production is a tiresome process requiring above average IT knowhow, is prone to human error and is done by a non IT person with a turn around time is about 30mins.  I proposed therefore that we automate production by creating an application that would recieve input [details, images, assets], output the installers [exe/dmg] and remove all the work inbetween from our production department.

    The Process and The Solution:

    PC:

    1. Ensure the files the dtool.exe requires are named and path’d correctly.
    2. From the CMD inject dtool.exe with settings,  which will provide a customised exe that runs on the users machine.
    3. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
        private function createEXEFromDTOOL():void
        {
         var process:NativeProcess = new NativeProcess();  
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>;

         args.push("inject");
         args.push("-e");
         args.push(_detailsVO.type.pcDTOOLName);
         args.push("-d");
         args.push( new File(PathsConstants.WIN_TMP_PATH).nativePath );
         args.push("-a");
         args.push(StringEdit.removeExtension(_engineVO.customD6WinAppEXE.name));
         args.push("-f");
         args.push(_detailsVO.feedID);    
         args.push("-s");
         args.push(_engineVO.mProjector.name);    
         //args.push(_assetsVO.mProjector.name);  
         info.arguments = args;
         info.workingDirectory = returnDTOOLDirectory();
         info.executable = _dtool;
         
         process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, handleDTOOLProcessOutput,false,0,true);
         process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handleDTOOLProcessError,false,0,true);
         process.addEventListener(NativeProcessExitEvent.EXIT,   handleDTOOLProcessExit,false,0,true);
         
         _message.sendMessage(MessageConstants.DEFAULT_SUCCESS_TITLE, "Starting DTOOL.", this);
         process.start(info);  
        }
    4. Edit the ISS [Inno Setup Script] with settings.
    5. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
        private function populateISSFile():void
        {
         var shortname:String = StringEdit.removeExtension(_engineVO.customD6WinAppEXE.name);// returnShortName(_detailsVO.title);
         var mProjector:String = StringEdit.removeExtension(_engineVO.mProjector.name).substr(0,18);
         var customD6WinAppExe:String = StringEdit.removeExtension(_engineVO.customD6WinAppEXE.name).substr(0,18);
           
         _issCopy = _issCopy.replace(/%SHORTNAME%/g,   shortname);
         _issCopy = _issCopy.replace(/%MPROJECTOR_PATH%/g, _engineVO.mProjector.nativePath);
         _issCopy = _issCopy.replace(/%MPROJECTOR_NAME%/g, _engineVO.mProjector.name);
         _issCopy = _issCopy.replace(/%TITLE%/g,    _detailsVO.title);
         _issCopy = _issCopy.replace(/%COMMON%/g,   _assetsVO.commonFiles.nativePath+File.separator);
         _issCopy = _issCopy.replace(/%SPECIFIC%/g,   _assetsVO.specificFiles.nativePath+File.separator);
         _issCopy = _issCopy.replace(/%ICON%/g,    _imagesVO.icon.nativePath);
         _issCopy = _issCopy.replace(/%INSTALLER_BG%/g,  _imagesVO.installerBG.nativePath);
         _issCopy = _issCopy.replace(/%D6_EXE%/g,   _engineVO.customD6WinAppEXE.nativePath);
         _issCopy = _issCopy.replace(/%D6_EXE_NAME%/g,  _engineVO.customD6WinAppEXE.name);
         _issCopy = _issCopy.replace(/%OUTPUT_DIR%/g,  _engineVO.installer.parent.nativePath);
         _issCopy = _issCopy.replace(/%SHORT_MPROJ_TITLE%/g, mProjector);
         _issCopy = _issCopy.replace(/%SHORT_EXE_TITLE%/g, customD6WinAppExe);
         _issCopy = _issCopy.replace(/%INSTALLER%/g,   StringEdit.removeExtension(_engineVO.installer.name));

         process(++_processSEQ);
        }

        private function openISSForUPDATE():void
        {
         var fs:FileStream = new FileStream();
         
         //fs.addEventListener(Event.COMPLETE,   handleISSForUPDATEComplete,false,0,true);
         //fs.addEventListener(IOErrorEvent.IO_ERROR, handleISSForUPDATEIOError, false, 0, true);
         
         _message.sendMessage(MessageConstants.DEFAULT_SUCCESS_TITLE, "Opening ISS for UPDATE.", this);
         
         fs.open
         (
          returnISSCreateInstallerFile(),
          FileMode.WRITE
         );
         
         fs.writeMultiByte(_issCopy, CharSetConstants.WINDOWS_1252);
         fs.close();  
         process(++_processSEQ);  
        }
    6. Ensure the files the ISS requires are named and path’d correctly.
    7. Run the ISS which creates the installer.
    8. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
        private function processISCC():void
        {
         var process:NativeProcess = new NativeProcess();  
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>;

         args.push("/Q");
         args.push(returnISSCreateInstallerFile().nativePath);  
       
         info.arguments = args;
         info.workingDirectory = returnWorkingDirectory();
         info.executable = _settingsVO.iscc;
         
         process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, handleISCCProcessOutput,false,0,true);
         process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handleISCCProcessError,false,0,true);
         process.addEventListener(NativeProcessExitEvent.EXIT,   handleISCCProcessExit,false,0,true);
         
         _message.sendMessage(MessageConstants.DEFAULT_SUCCESS_TITLE, "Starting ISCC.", this);
         process.start(info);  
        }

    MAC:

    1. Ensure the files the dtool.app requires are named and path’d correctly.
    2. From the Terminal run dtool.app with settings, which will package a customised app that runs on the users machine.
    3. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
        public function process():void
        {
         _dtoolDirectory = new File(workingDirectory.nativePath+"/dtool");
         deleteDTOOLAppDirectory();
         
         _dtoolProcess = new NativeProcess();
         
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>;  
         
         args.push("package");
         args.push("-e");
         args.push(detailsVO.type.macDTOOLName);
         args.push("-r");
         args.push(assetsVO.specificFiles.nativePath);
         args.push("-s");
         args.push(engineVO.mProjector.nativePath);
         args.push("-f");
         args.push(detailsVO.feedID);
         args.push("-a");
         args.push(detailsVO.title);
         args.push("-t");
         args.push(detailsVO.title);
         args.push("-i");
         args.push(imagesVO.icon.nativePath);
         args.push("-d");
         args.push(_dtoolDirectory.nativePath);
         args.push("-c");
         args.push(_dtoolDirectory.nativePath+"/dtool.xml");
         args.push("-x");
         args.push(_dtoolDirectory.nativePath+"/d6.app/Contents/MacOS/d6");
         args.push("-d");
         args.push(_dtoolDirectory.nativePath+"/build");
         
         info.arguments = args;
         info.workingDirectory = _dtoolDirectory;
         info.executable = new File(_dtoolDirectory.nativePath+"/dtool");
         
         _dtoolProcess.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA,  handleDTOOLProcessOutput,false,0,true);
         _dtoolProcess.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handleDTOOLProcessError,false,0,true);
         _dtoolProcess.addEventListener(NativeProcessExitEvent.EXIT,    handleDTOOLProcessExit,false,0,true);
         
         _dtoolProcess.start(info);  
        }
    4. Create a Packager [http://s.sudre.free.fr/Software/Packages.html] project with settings.
    5. Packaged with Installer

    6. Create the installer from the Packager project. [Updating the POST & PRE install scripts]
    7. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
        private function parseInstallScripts():void
        {
         var pretitle:String = detailsVO.title
         
         //pretitle = pretitle.replace(/'/g,"\'")
         
         _preinstallCopy = _preinstallCopy.replace(/%TITLE%/g,pretitle);
         _postinstallCopy = _postinstallCopy.replace(/%TITLE%/g,detailsVO.title);

         processInstallScripts(2);
        }
       
        private function updateInstallScripts():void
        {
         var preinstallFS:FileStream = new FileStream();
         var postinstallFS:FileStream = new FileStream();
         var scripts:File = returnScriptsDirectory();
         var preinstallF:File = new File(scripts.nativePath+File.separator+PathsConstants.PREINSTALL_SH.toLowerCase());
         var postinstallF:File = new File(scripts.nativePath+File.separator+PathsConstants.POSTINSTALL_SH.toLowerCase());
         
         
         preinstallFS.open(preinstallF,FileMode.WRITE)
         preinstallFS.writeMultiByte(_preinstallCopy, CharSetConstants.MACINTOSH);
         
         postinstallFS.open(postinstallF,FileMode.WRITE);
         postinstallFS.writeMultiByte(_postinstallCopy, CharSetConstants.MACINTOSH);
           
         preinstallFS.close();
         postinstallFS.close();
         
         processInstallScripts(3);  
        }

        private function createPackage():void
        {
         var process:NativeProcess = new NativeProcess();
         var packageDirectory:File = returnPackageDirectory();
         var installerPKG:File = returnInstallerPKG();
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>;
         
         args.push("--doc");
         args.push(packageDirectory.nativePath+File.separator+"TEMPLATE.pmdoc");
         args.push("--version");
         args.push("1.0");
         args.push("--title");
         args.push(detailsVO.title);
         args.push("--id");
         args.push(returnIDByTitle(detailsVO.title));
         args.push("-w");
         args.push("--verbose");
         args.push("--out");
         args.push(installerPKG.nativePath);
         
         info.arguments = args;
         info.workingDirectory = packageDirectory;
         info.executable = settingsVO.packageMaker;
         
         process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, handlePackageProcessOutput,false,0,true);
         process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handlePackageProcessError,false,0,true);
         process.addEventListener(NativeProcessExitEvent.EXIT,   handlePackageProcessExit,false,0,true);
         
         process.start(info);
        }
    8. Init, Decorate, Compress DMG with background image and icon for Web/CD.[Using YourSway create-dmg]
    9. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
        private function implementYourSway(type:String):void
        {
         var process:NativeProcess = new NativeProcess();  
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>();
         
         args.push("--volname");
         args.push(detailsVO.title);
         args.push("--background");
         args.push(_backgroundTMP.nativePath);
         args.push("--icon-size");
         args.push("32");
         args.push("--window-size");
         args.push("550");
         args.push("450");
         args.push("--icon");
         args.push(INSTALLER_PKG);
         args.push("450");
         args.push("215");
           
         if(type == EngineConstants.CD)
         {
          args.push("--icon");
          args.push
          (
           (detailsVO.type.id == DetailsConstants.CLUB_ID) ? "faq.xml" : "faq_mac.xml"
          );
          args.push("450");
          args.push("355");
         
          args.push("--icon");
          args.push("About Communicator");
          args.push("450");
          args.push("285");
         
          args.push(engineVO.cdDMG.nativePath);
          args.push
          (
           (detailsVO.type.id == DetailsConstants.CLUB_ID) ?  returnCLUBTemplateDIR().nativePath : returnSCHOOLTemplateDIR().nativePath
          );
         
         }else{
          /*TODO: Fix HORRIBLE HACK*/
          args.push("--icon");
          args.push("About Communicator");
          args.push("450");
          args.push("285");
          /*TODO: HORRIBLE HACK*/
         
          args.push(engineVO.webDMG.nativePath);
          args.push(returnEMPTYTemplateDIR().nativePath);    
         }
         
         
         info.arguments = args;
         info.workingDirectory = returnYourSwayDIR()
         info.executable = returnYourSwayApp();
         
         process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, handleImplementProcessOutput,false,0,true);
         process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handleImplementProcessError,false,0,true);
         process.addEventListener(NativeProcessExitEvent.EXIT,   handleImplementProcessExit,false,0,true);
         
         process.start(info);
        }

    I mainly used AIR 2′s Native Process for the steps above [a bit of a setup involved as well as having to create platform specific installers].
    This allowed me to interact with applications on the PC/MAC, send them commands and have them quitely [mostly] perform tasks for me:

    D6 Tool [Custom exe/app]: Custom/client specific exe/app.
    YourSway: Custom DMGs.
    Connecting to and instructing these tools was simple and straight forward.

    Inno Setup Compiler / PackageMaker: Create Installers.
    The Inno Setup Compiler just required its settings file edited and then passing as an argument with the instruction.
    PackageMaker was a total nightmare and took days to get it to work via the Terminal, eventually i got it right by a combination of a “TEMPLATE.pmdoc” and arguments which overwrote some of the settings in the doc.