I spent the better part of three days researching how to control an MSI installation’s progress bar on the fly via an installscript CustomAction. What I needed to do was update the user while the installation was processing stuff through custom actions. So for example lets say i needed to copy an index.html file to another location but i want to tell the user the installation is doing so. I would need to have a progress bar and status text. This post descibes how to do it.
First I created a new ‘Functions.rul’ file to store my two external installscript functions (I did this just to keep things organized). One of these functions will be a ‘deferred custom action’, and the other will be the function I call from inside my CustomActions which will actually update the status text and progress bars. Basically from my understanding the deffered custom action will run silently in the background and be called anytime you need to update the progress bar. I then have my regular installscript custom actions which I have a function call in each to update the progress bar. I’ll try to describe it a bit better below:
To begin let’s start by adding our deferred custom action. I do this by creating a function in installscript called ‘AddTotalTicks’ this function will be our background task. The background task will run quietly in the background while the immediate CustomActions are fired. Below is the code i used:
////////////////////////////////////////////////////////
// Function: AddTotalTicks(hMSI)
//
// Purpose: Resets and adds the number of
// “ticks” to the progress bar.
////////////////////////////////////////////////////////
function AddTotalTicks(hMSI)
OBJECT rec;
NUMBER iResult;
HWND hRec;
begin
set rec = MsiCreateRecord(3);
MsiRecordSetInteger(rec,1,3);
MsiRecordSetInteger(rec,2,5000);
MsiRecordSetInteger(rec,3,0);
MsiProcessMessage(hMSI , INSTALLMESSAGE_PROGRESS, rec);
set rec = NOTHING;
//reset the progress bar
hRec = MsiCreateRecord(3);
MsiRecordSetInteger(hRec, 1, 0);
MsiRecordSetInteger(hRec, 2, 100);
MsiRecordSetInteger(hRec, 3, 0);
iResult = MsiProcessMessage(hMSI, INSTALLMESSAGE_PROGRESS, hRec);
end;
To add the deferred custom action to our project, go to installshield’s custom action editor add a new Installscript Custom Action and set it as deferred. Then set it to fire in the Install Exec Sequence after install files. Again this little CA will run quietly in the background doing nothing until needed.
So now that we have our nifty section that runs in the background we need to have something that can be called anytime to ‘activate’ the deferred custom action. I did this as an external function in installscript. This way if i have many other custom actions that i want to update the progress bar on i can do so simply by adding the following to my CustomActions:
szMsg = “Please wait while we execute the copy command…..”;
SEPERATE_SetProgressBar(szMsg, hMSI);
The script we will use to update the Status Text, and progress bar is as follows:
////////////////////////////////////////////////////////
// Function: SEPERATE_SetProgressBar
////////////////////////////////////////////////////////
function SEPERATE_SetProgressBar(szTitle, hMSI)
STRING szClientCount;
HWND hRec, hProgressRec;
NUMBER nvBufferSize, nIncrement, i;
NUMBER nBuff, nClientCount, nCount;
NUMBER iResult;
begin
hRec = MsiCreateRecord(3);
hProgressRec = MsiCreateRecord(3);
//reset the progress bar
hRec = MsiCreateRecord(3);
MsiRecordSetInteger(hRec, 1, 0);
MsiRecordSetInteger(hRec, 2, 100);
MsiRecordSetInteger(hRec, 3, 0);
iResult = MsiProcessMessage(hMSI, INSTALLMESSAGE_PROGRESS, hRec);
MsiRecordSetString(hRec, 1, “Progress Custom Action”);
MsiRecordSetString(hRec, 2, szTitle);
MsiRecordSetString(hRec, 3, “incrementing tick [1] of [2]”);
iResult = MsiProcessMessage(hMSI, INSTALLMESSAGE_ACTIONSTART, hRec);
// Tell the installer to use explicit progress messages.
MsiRecordSetInteger(hRec, 1, 1);
MsiRecordSetInteger(hRec, 2, 1);
MsiRecordSetInteger(hRec, 3, 0);
iResult = MsiProcessMessage(hMSI, INSTALLMESSAGE_PROGRESS, hRec);
//Specify that an update of the progress bar’s position in
//this case means to move it forward by one increment.
nIncrement = 1;
MsiRecordSetInteger(hProgressRec, 1, 2);
MsiRecordSetInteger(hProgressRec, 2, nIncrement);
MsiRecordSetInteger(hProgressRec, 3, 0);
MsiRecordSetInteger(hRec, 2, 1);
for i = 0 to 100 step 1
//This makes the progress bar glide from left to right.
MsiRecordSetInteger(hRec, 1, i);
MsiProcessMessage(hMSI, INSTALLMESSAGE_PROGRESS, hProgressRec);
endfor;
MsiCloseHandle(hRec);
MsiCloseHandle(hProgressRec);
end;
The function above takes in a string “szMsg” which basically says what to set the status text as. This above actually isn’t a custom action but rather a function that can be called via a custom action. The reason I extracted it out of the custom actions is because it’s like 30 lines of code and I didn’t want to have to cut and paste 30 lines of code every time I wanted a progress bar to change.
The final step is to subscribe the custom actions you want to edit the progress bar to the progress bar itself. This was something that tripped me up because the way to do it is actually in the dialog editor inside Installshield. You go to the ‘Behavior’ area for the ‘SetupProgress’ dialog, and then select the progress bar of ‘ActionProgress95′ and on the right hand pane you will see a little button called ‘Subscriptions’ at the bottom. For each custom action you want to update this bar on you need to subscribe it to the bar itself.
I hope this is some help. Drop me a line if you have any questions.