cancel
Showing results for 
Search instead for 
Did you mean: 
redangel
Pilgrim

Hide Feature in Basic MSI during runtime

Hi.
I have to ask the user for a setup type, "Server" or "Client". If client is selected, I want a feature to be hidden. I was able to deactivate it, but not hide it...Smiley Sad
After a long search on the forum, I found Robert's trick, with a SQL request on the MSI database, and a modify, and then replace temp.
I tried in an empty project (Basic) it worked.

Now I tried to implement it in my big project, and it doesn't work... I'm getting crazy, the code is the same, and it doesn't work!!!! Smiley Sad
Here's my code (Installscript):

function HideOrShowToolsFeature(hMSI)
HWND hDB, hView, hRecord;
STRING szDisplay;
NUMBER nResult, nDisplay;
begin
hDB = MsiGetActiveDatabase(hMSI); // Get a handle on the msidb.exe
nResult=MsiDatabaseOpenView(hDB,
"SELECT * FROM `Feature` WHERE `Feature`='Tools'", hView); // Retrieves the row 'Tools' from table 'Feature'
nResult=MsiViewExecute(hView, 0);
nResult=MsiViewFetch(hView, hRecord); // backs up the View in Record

nDisplay=MsiRecordGetInteger(hRecord, 5); // backs up the current 'Display' state : hide(0) or show(>0)

nResult=MsiViewModify(hView, MSIMODIFY_DELETE, hRecord); // substracts Record to View
nResult=MsiRecordSetInteger(hRecord, 5, 0); // Switches the fifth field of Feature Record, 'Display', to 0 (hide)

nResult=MsiViewModify(hView, MSIMODIFY_INSERT_TEMPORARY, hRecord); // now copy back the Record in View

MsiViewClose(hView);
MsiCloseHandle(hRecord);
MsiCloseHandle(hView);
MsiCloseHandle(hDB);
end;

My Feature is called "Tools".
When I debug, the nResult returns "1627" (ERROR_IN_FUNCTION) on the line "nResult=MsiViewModify(hView, MSIMODIFY_DELETE, hRecord)" and I don't understand why... Can you see an error in my code?
Maybe it doesn't work with Windows Installer 3.1?? Like I'm not allowed to modify the database or I don't know...

Could someone please help me? I lost a big time on this! (if you ever have another idea on how to hide a feature in Basic MSI during Runtime, let me know!)
Labels (1)
0 Kudos
9 Replies
redangel
Pilgrim

Re: Hide Feature in Basic MSI during runtime

Little up, if you see this one Robert (and if you have enough time...), that'd be of great help!Smiley Surprised
I'm stuck!
0 Kudos
RobertDickau
Pilgrim

Re: Hide Feature in Basic MSI during runtime

Before taking the in-memory-MSI-change technique too far, if you want to hide and deactivate the feature, you might try a condition on the feature that sets its Install Level to zero... It might be too late, if you're asking in a dialog box, but that's the standard approach.

It's been a few years since I've thought about this, but I remember some people having trouble with the technique when working with a subfeature as opposed to a top-level feature. Is that the case with your project?
0 Kudos
redangel
Pilgrim

Re: Hide Feature in Basic MSI during runtime

Thanks for getting involved Smiley Happy I'm sorry it's been a long time ago you treated this...
You're right when you say it's too late to use conditions, because they're evaluated before the UIs. (I've thought of cutting the sequences, but then there's no way to go back...)

Maybe there's a way to force conditions to be re-evaluated? But I guess no, the Custom Actions can be called only once, therefore I can't CostInitialize and CostFinalize again during the UI sequence... am I wrong?

The feature I'm trying to hide is not a subfeature, but a top one... but it has subfeatures, so maybe that's the problem?? I'm looking into it at once!Smiley Wink
0 Kudos
redangel
Pilgrim

Re: Hide Feature in Basic MSI during runtime

Ok you're right (well, almost), the difference between my empty project and my actual project was this one: the fact that when the feature you wanna hide has sub-features, the sql request returns 1627...

So, I hope...:
-maybe I need to hide every subfeatures (it works) before hiding the top one? (I doubt it...)
-maybe there's another syntax for top-level features, like '\NewFeature' or something??

Please let me know if it's the case Smiley Happy
0 Kudos
RobertDickau
Pilgrim

Re: Hide Feature in Basic MSI during runtime

It does seem that the issue is related to attempting to delete subfeatures that have the top-level feature as a parent (in the Parent_Feature column of the Feature table). I feel dirty about it, but first hiding the subfeatures and setting them (temporarily) to have no parent seems to do the trick.

No guarantees that this will do anything useful, won't crash, etc. (at the very least, it could be streamlined), but as a coffee-break test this appears to work on an example with top-level feature "HideMe" with two subfeatures "HideMeSub1" and "HideMeSub2":
#include "ifx.h"

export prototype HideFeatureWithSubfeatures(HWND);

// (return handling omitted)

function HideFeatureWithSubfeatures(hInstall)
HWND hDB, hView, hRecord, hView2, hRecord2;
NUMBER nResult, nBuffer;
STRING szName;
begin

hDB = MsiGetActiveDatabase(hInstall);

// hide subfeatures first
nResult = MsiDatabaseOpenView(hDB,
"SELECT * FROM `Feature` WHERE `Feature_Parent`='HideMe'",
hView);

nResult = MsiViewExecute(hView, NULL);

// loop over subfeatures
nResult = MsiViewFetch(hView, hRecord);

while (nResult = ERROR_SUCCESS)
// uncomment for debugging
// nBuffer = 255;
// MsiRecordGetString(hRecord, 1, szName, nBuffer);
// MessageBox("Operating on feature: " + szName, INFORMATION);

nResult = MsiViewModify(hView, MSIMODIFY_DELETE, hRecord);

// hide subfeatures and change them to top-level features
nResult = MsiRecordSetString(hRecord, 2, "");
nResult = MsiRecordSetInteger(hRecord, 5, 0);

nResult = MsiViewModify(hView, MSIMODIFY_INSERT_TEMPORARY, hRecord);
nResult = MsiViewFetch(hView, hRecord);
endwhile;

MsiViewClose(hView);
MsiCloseHandle(hRecord);
MsiCloseHandle(hView);

// ...then hide the top-level feature
nResult = MsiDatabaseOpenView(hDB,
"SELECT * FROM `Feature` WHERE `Feature`='HideMe'",
hView2);
nResult = MsiViewExecute(hView2, NULL);
nResult = MsiViewFetch(hView2, hRecord2);

// uncomment for debugging
// nBuffer = 255;
// MsiRecordGetString(hRecord2, 1, szName, nBuffer);
// MessageBox("Operating on feature: " + szName, INFORMATION);

nResult = MsiViewModify(hView2, MSIMODIFY_DELETE, hRecord2);

nResult = MsiRecordSetInteger(hRecord2, 5, 0);
nResult = MsiViewModify(hView2, MSIMODIFY_INSERT_TEMPORARY, hRecord2);

MsiViewClose(hView2);
MsiCloseHandle(hRecord2);
MsiCloseHandle(hView2);

MsiCloseHandle(hDB);
return ERROR_SUCCESS;
end;

Let us never speak of this again, ha ha...
0 Kudos
redangel
Pilgrim

Re: Hide Feature in Basic MSI during runtime

Perfect, it works (heavily) like a charm! Smiley Happy

Question: why do you use 2 hView?:confused:
As it is closed after the first use, you could use it again, couldn't you?

Oh I see that you also have 2 hRecord(s), and I guess you made that so it's just cleaner "to the eyes" (french expression) isn't it?

Anyway, thank you very much!!


Well now I have problems in order to manage Rollback... I'll write again later, when all my problems will be solved (on this...)
0 Kudos
redangel
Pilgrim

The solution

Ok everything works (guess that was hard...) so maybe my code will serve someone some day...:

function PrepareFeatureStates(hMSI)
// Variables declaration:
NUMBER nCount; // stLanguages index of initial table
STRING szCount; // string corresponding to this number

NUMBER nBuff; // buffer for MsiGetProperty function
STRING szTemp; // temporary string for MsiGetProperty function

/*----------Hide-And-Show Server features variables-------*/
HWND hDB, hView, hRecord;
STRING szDisplay, szFeaturedisplay, szFeature;
NUMBER nResult, nDisplay;
/*--------------------------------------------------------*/

begin
MsiGetProperty(hMSI, "SETUPTYPE", szTemp, nBuff); // retrieves the Client or Server install type


/*---------Hide-And-Show Server features code--------------*/
hDB = MsiGetActiveDatabase(hMSI); // we have to deal directly with the Msi Database in order to HIDE (not for selection)

// If we're in Server type, we have to re-enable the top-feature "Tools"' visibility (BEFORE the subfeatures' visibility)
MsiGetProperty(hMSI,"FEATURE_DISPLAYSTATE0", szDisplay, nBuff); //retrieves the previously recorded diplaystate
if(szTemp="Server" && szDisplay!="") then // Server mode AND something to back up
MsiSetFeatureState(hMSI, "Tools", INSTALLSTATE_LOCAL); // enables the Server feature: Tools

MsiDatabaseOpenView(hDB,
"SELECT * FROM `Feature` WHERE `Feature`='Tools'", hView); // Retrieves the subfeatures of "Tools" top-feature

MsiViewExecute(hView, NULL);
MsiViewFetch(hView, hRecord);
MsiViewModify(hView, MSIMODIFY_DELETE, hRecord); // substracts Record to View

StrToNum(nDisplay,szDisplay); //casts xDisplay to NUMBER
MsiRecordSetInteger(hRecord, 5, nDisplay); // Switches the fifth field of Feature Record, 'Display', to non-0 (show)
MsiViewModify(hView, MSIMODIFY_INSERT_TEMPORARY, hRecord); // now copy back the Record in View

MsiViewClose(hView);
MsiCloseHandle(hRecord);
MsiCloseHandle(hView);
endif;
// End of Server specific code

// Common code for both Server and Client Setup type
MsiDatabaseOpenView(hDB,
"SELECT * FROM `Feature` WHERE `Feature_Parent`='Tools' OR `Feature_Parent`='_Tools'", hView);
// Retrieves the subfeatures from "Tools" top-feature (when switching to Client)
// or the fake "_Tools" top-feature, wich contains nothing and is not to be installed (and therefore when switching to Server)

MsiViewExecute(hView, NULL);
MsiViewFetch(hView, hRecord); // proceeds with first subfeature

nCount=0; // flag

while(nResult = ERROR_SUCCESS) // while loop is left when MsiViewFetch returns "ERROR_MORE_DATA"
nCount=nCount+1;
NumToStr(szCount,nCount); // casts the flag

nDisplay = MsiRecordGetInteger(hRecord, 5); // backs up the current 'Display' state : hide(0) or show(>0)

/****deselection of the subfeature****/
MsiRecordGetString(hRecord, 1, szFeature, nBuff); // retrieves the subfeature's name
if(szTemp="Client") then
MsiSetFeatureState(hMSI, szFeature, INSTALLSTATE_ABSENT); // disables the subfeature
endif;
/*************************************/

MsiViewModify(hView, MSIMODIFY_DELETE, hRecord); // substracts Record to View

// We have to take care of the Rollback possibility!!
// That's why we'll backup the 'Display' states of the Server-specific feature ("Tools") in an MSI property
if(szTemp="Client") then
if(nDisplay!=0) then
NumToStr(szDisplay,nDisplay); //casts xDisplay to STRING
MsiSetProperty(hMSI,"FEATURE_DISPLAYSTATE"+szCount, szDisplay); //backs up Display state in DISPLAYSTATEx MSI property
MsiRecordSetInteger(hRecord, 5, 0); // Switches the fifth field of Feature Record, 'Display', to 0 (hide)
MsiRecordSetString(hRecord, 2, "_Tools"); //changes subfeature's Feature_parent to a fake one (so that "Tools" can be hidden too...)
endif;
else // "Server" install
if(nDisplay=0) then
MsiGetProperty(hMSI,"FEATURE_DISPLAYSTATE"+szCount, szDisplay, nBuff); //retrieves the previously recorded diplaystates
StrToNum(nDisplay,szDisplay); //casts xDisplay to NUMBER
MsiRecordSetInteger(hRecord, 5, nDisplay); // Switches the fifth field of Feature Record, 'Display', to non-0 (show)
MsiRecordSetString(hRecord, 2, "Tools"); //backs up the real Feature_parent (Tools) of current subfeature
endif;
endif;

MsiViewModify(hView, MSIMODIFY_INSERT_TEMPORARY, hRecord); // now copy back the Record in View

/****reselection of the subfeature****/
if(szTemp="Server") then
MsiSetFeatureState(hMSI, szFeature, INSTALLSTATE_LOCAL); // enables the subfeature
endif;
/*************************************/

nResult = MsiViewFetch(hView, hRecord); //proceeds next subfeature
endwhile;

MsiViewClose(hView);
MsiCloseHandle(hRecord);
MsiCloseHandle(hView);

// If we're in Client type, we have to disable the top-feature "Tools"' visibility (AFTER the subfeatures' visibility)
if(szTemp="Client" && nDisplay!=0) then
MsiDatabaseOpenView(hDB,
"SELECT * FROM `Feature` WHERE `Feature`='Tools'", hView); // Retrieves the subfeatures of "Tools" top-feature

MsiViewExecute(hView, NULL);
MsiViewFetch(hView, hRecord);

nDisplay = MsiRecordGetInteger(hRecord, 5); // backs up the current 'Display' state : hide(0) or show(>0)
NumToStr(szDisplay,nDisplay); //casts xDisplay to STRING
MsiSetProperty(hMSI,"FEATURE_DISPLAYSTATE0", szDisplay); //backs up Display state in DISPLAYSTATE0 MSI property.

MsiViewModify(hView, MSIMODIFY_DELETE, hRecord); // substracts Record to View

MsiRecordSetInteger(hRecord, 5, 0); // Switches the fifth field of Feature Record, 'Display', to 0 (hide)
MsiViewModify(hView, MSIMODIFY_INSERT_TEMPORARY, hRecord); // now copy back the Record in View

MsiViewClose(hView);
MsiCloseHandle(hRecord);
MsiCloseHandle(hView);

MsiSetFeatureState(hMSI, "Tools", INSTALLSTATE_ABSENT); // deactivate Server's feature "Tools" installation
endif;
// End of Client specific code

MsiCloseHandle(hDB);
/*--------------------------------------*/

end;


That was something!:eek: Thanks Robert Dickau
0 Kudos
RobertDickau
Pilgrim

Re: Hide Feature in Basic MSI during runtime

Merci for posting your solution. One small addition I might suggest is resetting the buffer-size variable nBuff to a "large" value before each call to MsiGetProperty, to avoid the property values being truncated.

(I haven't read through all the code, but in general you shouldn't need to "roll back" anything, since changes to the in-memory MSI database aren't reflected in the cached MSI file.)

Thanks for posting,

Robert
0 Kudos
Highlighted
chrismi
Pilgrim

Hide Feature in Basic MSI during runtime

Wow that's great that you two got it to work. That code will serve me well in the future. Thank you.

Here's one alternate way that avoids the need for a custom action:
- For each Feature whose state needs to change conditionally, create 2 rows in the Condition table, one that sets that Feature to InstallLevel 1 and the other that sets it to InstallLevel 0. Set the condition to be the user's radio button selection (e.g. SELECTED_STATE="Server"), as appropriate for that Feature.
- Set the Feature to be "Visible and Expanded" and Required to "No". The InstallLevel can be set to either 0 or 1.
- Trigger a control event from the "Next" button of the dialog where the user chooses between "server" and "client", with the following values: DoAction | CostFinalize | 1

The only thing this doesn't do is maintain the user's selection state of the Feature if he goes back and forth, i.e. if he clicks as follows:

Server -> de-select the enabled server Feature (i.e. to not install) -> Back -> Client -> Back -> Server

, then the enabled server Feature is selected, though he had previously specified that it be de-selected. But this is a minor issue, if any. One could even argue it's better this way.

Robert: I'd love to hear from you as to whether you think running CostFinalize several times has any drawbacks. It didn't appear to, but I seem to recall learning to avoid this several years ago.

Thanks!

Christopher
0 Kudos