cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
redangel
Level 3

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...:(
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!!!! 😞
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
(12) Replies
redangel
Level 3

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

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
Level 3

Thanks for getting involved 🙂 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!;)
0 Kudos
redangel
Level 3

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 🙂
0 Kudos
RobertDickau
Flexera Alumni

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
Level 3

Perfect, it works (heavily) like a charm! 🙂

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
Level 3

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
Flexera Alumni

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
chrismi
Level 3

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
kiran4545
Level 3

@RobertDickau 

 

I have got a case where i need to remove my combobox item during runtime. Is there a way where i can do it? I have been struggling to get this work from many days

Use case:

I have got a combobox and i have selected one item from it to do some customaction. In the next dialog i want the combobox item to get removed because i have already used it. So the remaining items needs to show apart from the one i have selected in the previous dialog. 

Is there any piece of code which can make this work ?

Any help is highly appreciated .

0 Kudos
shunt
Revenera Moderator Revenera Moderator
Revenera Moderator

Rather than attempting to dynamically alter the combo box, could you create duplicate dialogs which are displayed depending on what the user selects?

0 Kudos

In our case we dont have too many dialogs where we can alter this . We have single dialog which has combobox where we need to delete the items once done and we will get back to the same dialog and we will select the different item where we should not see the item which is already done

Is there a possibility where we can do this without creating duplicate dialogs ?

0 Kudos