- Revenera Community
- :
- InstallShield
- :
- InstallShield Forum
- :
- Re: Hide Feature in Basic MSI during runtime
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Subscribe
- Mute
- Printer Friendly Page
Hide Feature in Basic MSI during runtime
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!)
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?
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!;)
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 🙂
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...
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...)
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
(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
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
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 .
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 ?