InstallShield Tips and Techniques

January 31, 2009

Reading a Silent Install Parameter File

Filed under: Reference Materials — shieldmaster @ 1:29 am

 

I have been focusing quite a bit on silent installs lately.  In fact I have recently rewritten two installs that were giving problems to the customer base simply because they could not be installed silently – or unattended.

 

In my experience, quite a few corporations require that software be able to be installed in an unattended/silent method.  For example, when a large corporation with 20,000 customer support personnel wants to distribute new software to their desktops, they use a high-end software distribution program (SMS, Trivoli) that will push the application install to the desktops during the night, launch the install package and reboot the machine.  No user intervention is required, and typically the user may not have administrative privileges so the application install package needs to be able to run both unattended and with elevated administrative privileges.
 
Smaller organizations may simply stage the application install program on a network share and provide every customer support personnel a link to a batch file (.BAT) that will run the application, supplying all of the options and data input without requiring user intervention.  All the user would do would be to click on the link before departing the office. 

 

I know the vast majority of installs are very simple and when you create a batch file, about the only item you will need is to pass the INSTALLDIR parameter.  But what happens when you have 6 to 8 dialogs, each with multiple data fields.  Here is an example of the MSI Properties I needed to pass to an application install to run silently with the correct configuration:

 

INSTALLDIR=\”C:\Program Files\ISI Systems\”

ISI_PLATFORM_GROUP=1

ISI_CS_COMPONENT_AS=1

ISI_CS_COMPONENT_CM=1

ISI_CS_COMPONENT_SM=1

ISI_CS_COMPONENT_AD=1

ISI_CS_TOPOLOGY_GROUP=1

ISI_AUDITDB_SERVER=DEVSERVER

ISI_AUDITDB_NAME=MSSQLSERVER

ISI_HTTP_SERVERNAME=ISI_SERVER

ISI_HTTP_SERVERPORT=80

ISI_HTTP_SERVERPATH=haven/services/content.asp

 

 

It looks nice lined up and very easy to read, but unfortunately the MSI Command line parameters must be on one contiguous input line – you cannot break these input parameters across two or more lines.  Now image how difficult it will be to explain the information clearly to a customer who is attempting to create a silent install BATCH file.

 

I remember years back how very much in vogue the INI type files were for reading in program data.  Now everything is XML format, which is not quite as easy for the customer to manipulate.  Started me thinking…

 

I ended up playing around with a silent parameter file – using a INI file format.  I wanted to keep the MSI Command line as simple as possible.  The goal is to create a BATCH file, such as “SilentInstall.bat”, but just specify a MSI Property, such as: “SILENT_PARAMETER_FILE” which provides the path and file name of the special INI file that would contain all of the application configuration items.   


Examples of the contents of a  BATCH file “SilentInstall.BAT”, would be:

 

“c:\Install\Setup.exe” /s /v”/qb ß Line break for clarity only

SILENT_PARAMETER_FILE=C:\Install\SilentParms.INI REBOOT=ReallySuppress

 

Msiexec /I c:\Install\AppSetup.msi /qb ß Line break for clarity only

SILENT_PARAMETER_FILE=C:\Install\SilentParms.INI REBOOT=ReallySuppress

 

 

Now the contents of the SilentParms.INI file would contain the application information that would normally be filled in the dialog data element fields.  Use comments to ensure clarity, especially if you are informing the customer of the relationships between selection values.

 

Here is a sample SilentParms.INI file:

 

REM ****************************************************************

REM **   Silent Parm File for running application Packrat

REM ****************************************************************

 

[GLOBAL]

InstallDirectory=”c:\Program Files\ISI Systems”

 

[PLATFORM_GROUP]

REM ****************************************************************

REM ** Activate only one Platform – choices are:

REM **   Platform=Rotunda

REM **   Platform=Balance

REM **   Platform=Beam

REM ****************************************************************

Platform=Rotunda  

 

[ROTUNDA]

REM ****************************************************************

REM ** Choose as many Components for the Rotunda selection

REM ** required

REM ** Note: values are not used if Platform selected was not

REM **       ROTUNDA

REM ****************************************************************

AudioServer=Yes

CacheMonitor=Yes

SearchMonitor=No

Auditing=NO

 

REM ****************************************************************

REM ** Choose Web or Viewer Services for the Rotunda selection.

REM ** Choices are:

REM **    CS_Services=Web

REM **    CS_Services=Viewer

REM ** Note: values are not used if Platform selected was not

REM **       ROTUNDA

REM ****************************************************************

CS_Services=Viewer

 

REM ****************************************************************

REM ** Provide data for the Rotunda HTTP Services selection.

REM **

REM ** Choices for Viewer Services:

REM **    HTTP_SERVERNAME=CHARLES1

REM **    HTTP_SERVERPORT=80

REM **    HTTP_SERVERPATH=witness/services/content.asp

REM **

REM ** Choices for WEB Services:

REM **    HTTP_SERVERNAME=CHARLES1

REM **    HTTP_SERVERPORT=8080

REM **    HTTP_SERVERPATH=adapter/locator

REM ** Note: values are not used if Platform selected was not

REM **       ROTUNDA

REM ****************************************************************

HTTP_SERVERNAME=CHARLES1

HTTP_SERVERPORT=80

HTTP_SERVERPATH=witness/services/content.asp

 

REM ****************************************************************

REM ** Provide data for the Rotunda Auditing selection.

REM ** Choices are:

REM **    AUDITDB_SERVER=CHARLES1

REM **    AUDITDB_NAME=CHARLES2

REM ** Note: values are not used if Platform selected was not

REM **       ROTUNDA

REM ****************************************************************

AUDITDB_SERVER=CHARLES1

AUDITDB_NAME=CHARLES2

 

[BALANCE_SERVICES]

REM ****************************************************************

REM ** Choose Web or COM Services for the Balance selection.

REM ** Choices are:

REM **    Balance_Services=WEB

REM **    Balance_Services=COM

REM ** Note: value is not used if Platform selected was not BALANCE

REM ****************************************************************

Bal_Services=WEB

 

Personally the INI style format is just cleaner to look at, and should be much easier to document – especially when you can put comments throughout the document.

 

InstallScript Custom Action

Now here is a sample InstallScript that will read the INI file and create the selected values to MSI Properties.  Couple of things to note:

  1. Since you typically will run silently, the Custom Action must be run both in the UI Sequence as well as the Execute Sequence.  Obviously the majority of time the appliation will be running silently and only in the Execute Sequence.  But also think of the INI file used as a method to prefill data elements if QA decides to run with dialogs for evaluation requirements.  That is why you need to enable it to run in both sequences – but only allow it to run once!
  2. The conversion of the INI values into MSI Properties should precisely deliver the same installation configuration.  This is a good method to test – run the install silently with the MSI Properties, and then run with the INI file.  The results should match up correctly.
  3. If a file and path are entered via the MSI Property “SILENT_PARAMETER_FILE”, and the file cannot be found, then trigger an abort.
  4. When the Script contains a INSTALLDIR parm, you need to create a Type 35 Custom Action and use the property value “ISI_INSTALLPATH” (Set in the script) to set the INSTALLDIR directory property.
  5. Be sure to allow for the path entered to be in UNC form – could be a remote location! 
  6. To shorten the Script – I have excised the redundant script code.

 

/////////////////////////////////////////////////////////////////////////

// Function:  ISI_ReadSilentParms

//                                                                          

//  Purpose:  This function will be called by the script engine when

//            Windows(TM) Installer executes your custom action

//            and will read and validate the Silent run parms

//

///////////////////////////////////////////////////////////////////////function ISI_ReadSilentParms(hMSI)          

      STRING   szProgramFolder, szCommandLine, szItemName, szMigration; 

      STRING   szPath, szRegKey, szRegSubKey, szSupportDir;

STRING   szProgramFiles, szBuffer, szLogMessage, szParmFile;

STRING   szINI_SectionName, szINI_ValueName; 

      STRING   szPlatform, szAudioServer, szCacheMonitor;

STRING   szSearchMonitor;

      STRING   szAuditing, szCS_Services, szBalance_Services;

STRING   szAuditDB, szAuditDB_Server;

      STRING   szAuditDB_Name, szCS_ServerName, szCS_ServerPort;

STRING   szCS_ServerPath;

      NUMBER   nBuffer, nResult, nvType, nvSize;

 

begin  

 

//WriteToLogFiles

ISI_WriteLogFile (“ISI–> Entering ISI_ReadSilentParms Function”);   

 

 

//***********************************************

//** Retrieve MSI Property that tells where

//** the silent parm file is located

//*********************************************** 

nBuffer = 256;

MsiGetProperty (ISMSI_HANDLE, “SILENT_PARAMETER_FILE”, szParmFile, nBuffer);

if (nBuffer = 0) then

            szBuffer = “ISI–> INFO: Retrieved no value from the MSI Property ‘SILENT_PARAMETER_FILE’!”;

            Sprintf (szLogMessage, szBuffer, szParmFile);

            ISI_WriteLogFile (szLogMessage);

            goto BypassSilentParmFileRead;

endif;

 

szBuffer = “ISI–> INFO: Retrieved ‘%s’ value from the MSI Property ‘SILENT_PARAMETER_FILE’ “;

Sprintf (szLogMessage, szBuffer, szParmFile);

ISI_WriteLogFile (szLogMessage);

 

LongPathToShortPath (szParmFile);                                

LongPathToQuote ( szParmFile, TRUE );

 

if ( Is (FILE_EXISTS, szParmFile) = FALSE) then  

      szBuffer = “ISI–> ERROR: File ‘%s’ was not found –

cancelling install! “;

      Sprintf (szLogMessage, szBuffer, szParmFile);

      ISI_WriteLogFile (szLogMessage);  

      return ERROR_INSTALL_FAILURE;   //Breaks out of install and aborts

endif;

     

      szINI_SectionName =    “GLOBAL”;

      szINI_ValueName   =    “InstallDirectory”;

      nResult = GetProfString ( szParmFile, szINI_SectionName, szINI_ValueName, g_szInstallDir );

      if (nResult = 0) then

            szBuffer = “ISI–> INFO: Value retrieved from Section ‘%s’ with name ‘%s’! was ‘%s’ “;

            Sprintf (szLogMessage, szBuffer, szINI_SectionName, szINI_ValueName, g_szInstallDir);

            ISI_WriteLogFile (szLogMessage);  

      else

            szBuffer = “ISI–> ERROR: Value could not be retrieved from Section ‘%s’ with name ‘%s’! “;

            Sprintf (szLogMessage, szBuffer, szINI_SectionName, szINI_ValueName);

            ISI_WriteLogFile (szLogMessage);  

      endif;

 

      // Set MSI Property which will reset INSTALLDIR Property for new files being installed

      MsiSetProperty (ISMSI_HANDLE, “ISI_INSTALLPATH”, g_szInstallDir);

            szBuffer = “ISI–> INFO: Set ISI_INSTALLPATH Directory to be: ‘%s’ “;

            Sprintf (szLogMessage, szBuffer, g_szInstallDir);   

            ISI_WriteLogFile (szLogMessage);

 

     

// ********************************************************

// ** Retrieve the Platform value

// ********************************************************

szINI_SectionName =    “PLATFORM_GROUP”;

szINI_ValueName   =    “Platform”;

nResult = GetProfString ( szParmFile, szINI_SectionName, szINI_ValueName, szPlatform );

if (nResult = 0) then

      szBuffer = “ISI–> INFO: Value retrieved from Section ‘%s’ with name ‘%s’! was ‘%s’ “;

      Sprintf (szLogMessage, szBuffer, szINI_SectionName, szINI_ValueName, szPlatform);

      ISI_WriteLogFile (szLogMessage);  

      nResult = StrCompare ( “Rotunda”, szPlatform );  //Case insensitive comparison

      if (nResult = 0) then

            MsiSetProperty (ISMSI_HANDLE, “ISI_PLATFORM_GROUP”, “1”); 

            szBuffer = “ISI–> INFO: Set MSI Property ‘%s’ with value of 1 – set as Rotunda”;

            Sprintf (szLogMessage, szBuffer, “ISI_PLATFORM_GROUP”);

            ISI_WriteLogFile (szLogMessage);    

            goto BypassPlatformRead;

      endif;

      nResult = StrCompare ( “Balance”, szPlatform ); 

      if (nResult = 0) then

            MsiSetProperty (ISMSI_HANDLE, “ISI_PLATFORM_GROUP”, “2”); 

            szBuffer = “ISI–> INFO: Set MSI Property ‘%s’ with value of 2 – set as Balance”;

            Sprintf (szLogMessage, szBuffer, “ISI_PLATFORM_GROUP”);

            ISI_WriteLogFile (szLogMessage);    

            goto BypassPlatformRead;

      endif;

      nResult = StrCompare ( “Ultra”, szPlatform ); 

      if (nResult = 0) then

            MsiSetProperty (ISMSI_HANDLE, “ISI_PLATFORM_GROUP”, “3”); 

            szBuffer = “ISI–> INFO: Set MSI Property ‘%s’ with value of 3 – set as Ultra”;

            Sprintf (szLogMessage, szBuffer, “ISI_PLATFORM_GROUP”);

            ISI_WriteLogFile (szLogMessage);    

            goto BypassPlatformRead;

      endif;

            //PROBLEM DID NOT FIND MATCHING VALUE!!!

            szBuffer = “ISI–> ERROR: Value could not be retrieved from Section ‘%s’ with name ‘%s’! “;

            Sprintf (szLogMessage, szBuffer, szINI_SectionName, szINI_ValueName);

            ISI_WriteLogFile (szLogMessage);        

            MsiSetProperty (ISMSI_HANDLE, “ISI_PLATFORM_GROUP”, “1”);  //Setting Default!

 

else

      szBuffer = “ISI–> ERROR: Value could not be retrieved from Section ‘%s’ with name ‘%s’! “;

      Sprintf (szLogMessage, szBuffer, szINI_SectionName, szINI_ValueName);

      ISI_WriteLogFile (szLogMessage);  

      MsiSetProperty (ISMSI_HANDLE, “ISI_PLATFORM_GROUP”, “1”);  //Setting Default!

endif;

 

BypassPlatformRead:

// ********************************************************

// ** Retrieve the ROTUNDA Components value

// ********************************************************    

szINI_SectionName =    “ROTUNDA”;

      szINI_ValueName   =    “AudioServer”;

      nResult = GetProfString ( szParmFile, szINI_SectionName, szINI_ValueName, szAudioServer );

      if (nResult = 0) then

            szBuffer = “ISI–> INFO: Value retrieved from Section ‘%s’ with name ‘%s’! was ‘%s’ “;

            Sprintf (szLogMessage, szBuffer, szINI_SectionName, szINI_ValueName, szAudioServer);

            ISI_WriteLogFile (szLogMessage);  

      else

            szBuffer = “ISI–> ERROR: Value could not be retrieved from Section ‘%s’ with name ‘%s’! “;

            Sprintf (szLogMessage, szBuffer, szINI_SectionName, szINI_ValueName);

            ISI_WriteLogFile (szLogMessage);  

      endif;

            nResult = StrCompare ( “Yes”, szAudioServer );  //Case insensitive comparison

            if (nResult = 0) then

                  MsiSetProperty (ISMSI_HANDLE, “ISI_CS_COMPONENT_AS”, “1”); 

                  szBuffer = “ISI–> INFO: Set MSI Property ‘ISI_CS_COMPONENT_AS’ with value of 1”;

                  Sprintf (szLogMessage, szBuffer, “ISI_CS_COMPONENT_AS”);

                  ISI_WriteLogFile (szLogMessage);    

            else

                  MsiSetProperty (ISMSI_HANDLE, “ISI_CS_COMPONENT_AS”, “”); 

                  szBuffer = “ISI–> INFO: Set MSI Property ‘ISI_CS_COMPONENT_AS’ with value of 0”;

                  Sprintf (szLogMessage, szBuffer, “ISI_CS_COMPONENT_AS”);

                  ISI_WriteLogFile (szLogMessage);    

            endif;

 

 

// ********************************************************

// ** Retrieve the ROTUNDA Services values

// ********************************************************

szINI_ValueName   =    “CS_Services”;

nResult = GetProfString ( szParmFile, szINI_SectionName, szINI_ValueName, szCS_Services );

if (nResult = 0) then

      szBuffer = “ISI–> INFO: Value retrieved from Section ‘%s’ with name ‘%s’! was ‘%s’ “;

      Sprintf (szLogMessage, szBuffer, szINI_SectionName, szINI_ValueName, szCS_Services);

      ISI_WriteLogFile (szLogMessage);

            nResult = StrCompare ( “Viewer”, szCS_Services );  //Case insensitive comparison

            if (nResult = 0) then

                  MsiSetProperty (ISMSI_HANDLE, “ISI_CS_TOPOLOGY_GROUP”, “1”); 

                  szBuffer = “ISI–> INFO: Set MSI Property ‘ISI_CS_TOPOLOGY_GROUP’ with value of 1 – Viewer”;

                  Sprintf (szLogMessage, szBuffer, “ISI_CS_TOPOLOGY_GROUP”);

                  ISI_WriteLogFile (szLogMessage);    

            else

                  MsiSetProperty (ISMSI_HANDLE, “ISI_CS_TOPOLOGY_GROUP”, “2”); 

                  szBuffer = “ISI–> INFO: Set MSI Property ‘ISI_CS_TOPOLOGY_GROUP’ with value of 2 – WEB”;

                  Sprintf (szLogMessage, szBuffer, “ISI_CS_TOPOLOGY_GROUP”);

                  ISI_WriteLogFile (szLogMessage);    

            endif;

else

      szBuffer = “ISI–> ERROR: Value could not be retrieved from Section ‘%s’ with name ‘%s’! “;

      Sprintf (szLogMessage, szBuffer, szINI_SectionName, szINI_ValueName);

      ISI_WriteLogFile (szLogMessage);  

endif;

 

 

//WriteToLogFiles  

 BypassSilentParmFileRead:

ISI_WriteLogFile (“ISI–> Exiting ISI_ReadSilentParms Function”);   

 

 

end;

 

Hope this helps!  Happy Coding…

Charles

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: