Hide Text based on RegistrySearch in WiX Bundle - wix

Is it possible to use a RegistrySearch in WiX bundle to hide a Text in theme?
I don't have a clue about where to start. In the code below, the InstalledDotNet4 variable doesn't set the Text in time to disabled and I can't find a way to disable the Text (or change its text content).
Bundle.wxs:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<Bundle Name="My App" Version="1.0.0.0"
Manufacturer="ACME"
UpgradeCode="d88faa97-2197-4154-9e77-32f9ca773bd4">
<BootstrapperApplicationRef
Id="WixExtendedBootstrapperApplication.HyperlinkLicense">
<Payload SourceFile="Resources/background.png" Id="myLogo" />
</BootstrapperApplicationRef>
<WixVariable Id="WixExtbaLicenseUrl" Value="" />
<WixVariable Id="WixExtbaThemeXml" Value="Resources\MyTheme.xml" />
<WixVariable Id="WixExtbaThemeWxl" Value="Resources\MyTheme.wxl" />
<util:RegistrySearch Root="HKCU"
Key="Software\AnythingToCheck"
Value="Test" Variable="InstalledDotNet4" />
<Chain>
<MsiPackage Id="dotNETv4" DisplayName="My .NET v4 prerequisite"
SourceFile="myApp.msi"
Visible="yes"
InstallCondition="CheckboxDotNetv4" />
</Chain>
</Wix>
MyTheme.xml:
<?xml version="1.0" encoding="utf-8"?>
<Theme xmlns="http://wixtoolset.org/schemas/thmutil/2010">
<!-- Window definition -->
<!-- Font definition -->
<Page Name="Install">
<Checkbox Name="CheckboxDotNet4"
X="205" Y="126"
Width="-100" Height="17"
TabStop="yes" FontId="3"
HideWhenDisabled="yes">.NET Framework 4.0</Checkbox>
<Text Name="InstalledDotNet4"
X="-10" Y="126"
Width="80" Height="17"
TabStop="no" FontId="3"
HideWhenDisabled="yes">(Installed)</Text>
</Page>
<!-- More pages -->
</Theme>
Additionally, I tried to use the following code in Bundle.wxs, but that is not linked to the RegistrySearch:
<Variable Name="InstalledDotNet4State" Type="string" Value="disable" />

Yes, after extensive research I found it is possible to hide the Text based on a RegistrySearch. First you need to download the WiX Extended Bootstrapper Application from http://wixextba.codeplex.com/. Extract the contents and add to your project the WixBalExtensionExt.dll as shown in Bundle10.wxs example.
Then, open the project bafunctions under folder Template bafunctions. You will need to compile this C++ library and add it to your bundle as a Payload (use the Bundle10.wxs as example).
Then, to be able to read and hide the Text control, uncomment function OnDetectComplete() and add the following code, for example:
STDMETHODIMP OnDetectComplete()
{
HRESULT hr = S_OK;
LPWSTR sczValue = NULL;
#if DEBUG
// Show log info during debug.
// May not be THE way to log.
size_t i;
LPSTR sczValue2 = (char *) malloc(100);
#endif
BalGetStringVariable(L"InstalledDotNet4Reg", &sczValue);
BalExitOnFailure(hr, "Failed to get variable.");
if (sczValue == NULL)
{
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD,
"Failed to read null variable.");
}
else
{
if (_wtoi(sczValue))
{
hr = m_pEngine->SetVariableString(L"CheckboxDotNetv4State",
L"disable");
BalExitOnFailure(hr, "Failed to set control state.");
hr = m_pEngine->SetVariableNumeric(L"CheckboxDotNetv4", 0);
BalExitOnFailure(hr, "Failed to set variable.");
}
else
{
#if DEBUG
// Log information
wcstombs_s(&i, sczValue2, (size_t)100, sczValue, (size_t)100);
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, sczValue2);
#endif
hr = m_pEngine->SetVariableString(L"InstalledDotNet4State",
L"disable");
BalExitOnFailure(hr, "Failed to set control state.");
}
}
LExit:
ReleaseStr(sczValue);
return hr;
}
Finally, change (or add) your RegistrySearch, this way:
<util:RegistrySearch Root="HKLM"
Key="SOFTWARE\Classes\Installer\Products\FCDAC0A0AD874C333A05DC1548B97920"
Variable="InstalledDotNet4Reg" Result="exists" />

Related

Wix Toolset - upgrade and do not overwrite config file

I have an WPF app that connects to some web service on some URL. I have made an installation and it works like a charm, it even asks about URL and changes .config file as instructed. Now, I want to upgrade the app, but leave the .config file intact.
I have tried some solutions on web, it doesn't want to upgrade, it seems that only does a clean install every time.
<Package InstallerVersion="400" Compressed="yes" InstallScope="perMachine" Platform="x64" />
<Icon Id="icon.ico" SourceFile="$(var.ProjectDir)Icon.ico" />
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
<WixVariable Id="WixUIBannerBmp" Value="Images\installer_top-banner.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="Images\installer_background.bmp" />
<WixVariable Id="WixUILicenseRtf" Value="$(var.ProjectDir)\license.rtf" />
<Property Id="ARPURLINFOABOUT" Value="http://www.Company.com" />
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<Property Id="FULLURL" Value="http://demo.Company.com/WcfFullPortal.svc" Secure="yes" />
<Property Id="AUTH" Value="0" Secure="yes" />
<Property Id="AUTHVALUE" Value="$(var.httpValue)" Secure="yes" />
<Property Id="PREVIOUSVERSIONSINSTALLED" Secure="yes" />
<Upgrade Id="1d96517a-8fc5-4150-b1cb-c3adf479a57d">
<UpgradeVersion Minimum="1.0.0.0" Maximum="99.0.0.0" Property="PREVIOUSVERSIONSINSTALLED" IncludeMinimum="yes" IncludeMaximum="no" />
</Upgrade>
<UIRef Id="SetupDialogUI" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch MyApp" />
<Property Id="WixShellExecTarget" Value="[#MyApp.UI.WPF.exe]" />
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." AllowSameVersionUpgrades="yes" Schedule="afterInstallExecute" />
<MediaTemplate EmbedCab="yes" />
<!-- 32-bit / 64-bit variables -->
<?if $(var.Platform) = x64 ?>
<?define bitness = "(64 bit)" ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else?>
<?define bitness = "(32 bit)" ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif?>
<Condition Message="Minimum supported OS is Windows 7."><![CDATA[Installed OR (VersionNT >= 600)]]></Condition>
<!--
<?if $(var.Platform) = x64 ?>
<Condition Message="You need to install the 32-bit version of this product on 32-bit Windows.">
<![CDATA[VersionNT64]]>
</Condition>
<?endif?>
<?if $(var.Platform) = x86 ?>
<Condition Message="You need to install the 64-bit version of this product on 64-bit Windows.">
<![CDATA[NOT VersionNT64]]>
</Condition>
<?endif?> -->
</Product>
...
Two approaches:
Finish the work in the installer. MSI doesn't persist (remember) properties on subsequent insallations. You have to harvest the value from the config file back into the property so it can be shown in the UI again and reapplied during the installation.
http://robmensching.com/blog/posts/2010/5/2/the-wix-toolsets-remember-property-pattern/
Move the complexity out of the installer and into the application. Design the application so it asks for the setting on first run. Use the app.config appSettings element file attribute to specify a second file to contain the override setting. Have the installer create the app.config but not the second file. Have the application save the first run obtained setting to the second file. The installer doesn't know about this so it'll never touch the file during subsequent installs.
https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/appsettings/appsettings-element-for-configuration
I'm a WiX expert and can handle approach 1 easily. For people new to WiX it can be a challenge. I generally go with approach 1 when it's just a handful of settings and the program is non interactive like a windows service or say a web application where the inputs are pool identity. Otherwise I find it's easier for my customers to support if I move the complexity to code they better understand.

Create DSN with Wix

Having trouble making a System DSN via Wix
here is my test code
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="TestProduct" Language="1033" Version="0.0.0.1" Manufacturer="WixEdit" UpgradeCode="*">
<Package Description="Test file in a Product" Comments="Simple test" InstallerVersion="200" Compressed="yes" />
<Media Id="1" Cabinet="simple.cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Component Id="C_PROD_DSN" Guid="*">
<ODBCDataSource Id="Prod_Dsn" Name="SomeName" Registration="machine" DriverName="ODBC Driver 11 for SQL Server">
<Property Id="Description" Value="Some Description" />
<Property Id="Database" Value="SomeDB" />
<Property Id="Language" Value="us_english" />
<Property Id="LastUser" Value="SomeUser" />
<Property Id="Driver" Value="C:\windows\system32\msodbcsql11.dll" />
</ODBCDataSource>
</Component>
</Directory>
<Feature Id="DefaultFeature" Title="Main Feature" Level="1">
<ComponentRef Id="C_PROD_DSN" />
</Feature>
<UI />
</Product>
</Wix>
Wix Edit compiles it just fine but when i run it I get
Error configuring ODBC data source: SomeName. ODBC error 6: Component not found in the registry. Verify that the file SomeName exists and that you can access it.
This is a DSN to a SQL DB not to a file.
I know I can make this work by just building up the Registry trees I need, I just like to try it using what should be the built in functionality for the task.
Can this be done using ODBCDataSource? (without Custom Actions, I just don't have time to write another one right now) or should I just cut my losses and do it with RegistryKey and RegistryValue?

WIX Combobox Bind Values

I am trying to populate values using custom actions and want to bind the values into combobox which is inside product.wxs.
Can anyone guide me how to bind values if I want to populate a list of countries inside the combobox?
I am struggling with how to pass this value so the values will show inside combox while executing my MSI setup.
Below provide the code which I am trying:
public static ActionResult FillList(Session xiSession)
{
Dictionary<string, string> _co = new Dictionary<string, string>();
_co.Add(String.Empty, String.Empty);
_co.Add("US", "United States");
_co.Add("CA", "Canada");
_co.Add("MX", "Mexico");
xiSession.Log("Return success");
return ActionResult.Success;
}
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature" Title="SetupProjectComboTest" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
<UI>
<UIRef Id="WixUI_Mondo" />
<Dialog Id="MyCustomDlg" Width="500" Height="260">
<Control Id="ComboBoxMain" Type="ComboBox" X="10" Y="60" Width="300" Height="17" Property="COUNTRIES" />
<Control Id="ButtonMain" Type="PushButton" X="320" Y="60" Width="40" Height="17" Text="Show">
<Publish Property="COMBOVALUEFORMATTED" Order="1" Value="[COUNTRIES]" />
</Control>
<Control Id="LabelMain" Type="Text" X="10" Y="80" Width="360" Height="17" Property="COMBOVALUEFORMATTED" Text="[COMBOVALUEFORMATTED]" />
</Dialog>
</UI>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="SetupProjectComboTest" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<!-- <Component Id="ProductComponent"> -->
<!-- TODO: Insert files, registry keys, and other resources here. -->
<!-- </Component> -->
</ComponentGroup>
</Fragment>
You need to insert row into ComboBox table to bind the List values. If you open the msi in ORCA Editor, you can find the msi tables and rows.
You should include EnsureTable element if you don’t use any other ComboBox element in your msi.
<EnsureTable Id="ComboBox"/>
You can insert the rows from Custom action.
static int index = 1;
public static void FillComboBox(Session session, string text, string value)
{
View view = session.Database.OpenView("SELECT * FROM ComboBox");
view.Execute();
Record record = session.Database.CreateRecord(4);
record.SetString(1, "COUNTRIES");
record.SetInteger(2, index);
record.SetString(3, value);
record.SetString(4, text);
view.Modify(ViewModifyMode.InsertTemporary, record);
view.Close();
index++;
}
Inside the Custom action call the FillComboBox method.
public static ActionResult FillList(Session xiSession)
{
FillComboBox(xiSession, "US", "United States");
FillComboBox(xiSession, "CA", "Canada");
FillComboBox(xiSession, "MX", "Mexico");
return ActionResult.Success;
}
Execute the Custom action in InstallUIsequence before run that Combo Box dialog.
<InstallUISequence>
<Custom Action="INSERT_ROWS" After="AppSearch">Not Installed</Custom>
</InstallUISequence>

WiX Extended Bootstrapper Application: How to determine which radio button is selected?

Here's my setup:
Compiling at .NET 4.0 (this cannot change)
Using WiX 3.7
Using the WiX Extended Bootstrapper Application extension
So I'm creating a bootstrapper installation project and though I've got it working, I feel like there is a much more clean and easy way to do it.
Basically, I need to have the user select which environment they wish to use when they install the product. To do this, I use the WiX Extended Bootstrapper Application extension to allow me to put some radio buttons on the first install screen.
Getting that all setup was fine, but then I realized that I don't know how to easily determine which radio button has been selected. So as I work around I just listed the MSI three times in the bundle and put install conditions on each one.
Is there a way to determine which radio button has been selected with just one property? (For example, when I pass the InstallFolder property to my MSI as seen in the code below.) And if there isn't a way to do this currently, is there a way for me to do this without adding my MSI three times to the bundle?
Here's my code (Notice how I list essentially the same MsiPackage three times):
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?include Properties.wxi ?>
<Bundle Name="!(loc.Product.Name)" Version="$(var.Version)" Manufacturer="!(loc.Product.Manufacturer)" UpgradeCode="$(var.UpgradeCode)" Condition="VersionNT >= v5.1">
<BootstrapperApplicationRef Id="WixExtendedBootstrapperApplication.Hyperlink2License">
<bal:WixExtendedBootstrapperApplication SuppressRepair="yes" LicenseUrl="" ThemeFile="$(var.Bundle.ExtTheme.RadioBtns.Path)" LocalizationFile="$(var.Bundle.ExtTheme.RadioBtns.l10n.Path)" />
</BootstrapperApplicationRef>
<WixVariable Id="WixMbaPrereqPackageId" Value="Netfx4Full" />
<Variable Name="InstallFolder" Type="string" Value="$(var.InstallFolder.Value)" />
<Variable Name="RadioButton1" Type="numeric" Value="0" />
<Variable Name="RadioButton2" Type="numeric" Value="0" />
<Variable Name="RadioButton3" Type="numeric" Value="1" />
<Chain>
<!-- Other Package References Here -->
<MsiPackage Id="DBConnections_Dev"
SourceFile="$(var.MSI.Path)"
Visible="no"
Vital="yes"
InstallCondition="RadioButton1 = 1">
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
<MsiProperty Name="SELECTED_ENV" Value="1" />
</MsiPackage>
<MsiPackage Id="DBConnections_Stage"
SourceFile="$(var.MSI.Path)"
Visible="no"
Vital="yes"
InstallCondition="RadioButton2 = 1">
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
<MsiProperty Name="SELECTED_ENV" Value="2" />
</MsiPackage>
<MsiPackage Id="DBConnections_Prod"
SourceFile="$(var.MSI.Path)"
Visible="no"
Vital="yes"
InstallCondition="RadioButton3 = 1">
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
<MsiProperty Name="SELECTED_ENV" Value="3" />
</MsiPackage>
</Chain>
</Bundle>
</Wix>
I also asked this question on the project's CodePlex site and the developer responded as follows (here is a link to the full discussion: http://wixextba.codeplex.com/discussions/432341):
This can now be done using the custom actions feature.
I've tested his response and found it to be correct! This functionality is available as of version 3.7.4791.32058. There is also an example included in the source code demonstrating how this is done. I've posted the pertinent code below:
Needed in Custom Action c++ code:
STDMETHODIMP OnPlanCustomAction()
{
...
if (SUCCEEDED(BalGetNumericVariable(L"RadioButton1", &llValue)) && llValue)
{
m_pEngine->SetVariableNumeric(L"RadioButton", 1);
BalExitOnFailure(hr, "Failed to set variable.");
}
else if (SUCCEEDED(BalGetNumericVariable(L"RadioButton2", &llValue)) && llValue)
{
m_pEngine->SetVariableNumeric(L"RadioButton", 2);
BalExitOnFailure(hr, "Failed to set variable.");
}
else if (SUCCEEDED(BalGetNumericVariable(L"RadioButton3", &llValue)) && llValue)
{
m_pEngine->SetVariableNumeric(L"RadioButton", 3);
BalExitOnFailure(hr, "Failed to set variable.");
}
else if (SUCCEEDED(BalGetNumericVariable(L"RadioButton4", &llValue)) && llValue)
{
m_pEngine->SetVariableNumeric(L"RadioButton", 4);
BalExitOnFailure(hr, "Failed to set variable.");
}
else
{
m_pEngine->SetVariableNumeric(L"RadioButton", 0);
BalExitOnFailure(hr, "Failed to set variable.");
}
...
Needed in WiX XML (from examples that come with downloaded DLL):
<Variable Name="RadioButton1" Type="numeric" Value="0" />
<Variable Name="RadioButton2" Type="numeric" Value="1" />
<Variable Name="RadioButton3" Type="numeric" Value="0" />
<Variable Name="RadioButton4" Type="numeric" Value="0" />
<Chain DisableSystemRestore="yes">
<PackageGroupRef Id="NetFx40Redist" />
<MsiPackage
Id="Setup"
Compressed="yes"
SourceFile="Setup.msi"
Vital="yes">
<MsiProperty Name="APPLICATIONFOLDER" Value="[InstallFolder]" />
<MsiProperty Name="RadioButton" Value="[RadioButton]" />
</MsiPackage>
</Chain>
Check out the links in the text for the original examples from which these code samples were taken. Hope this helps others in the future.

Can WixUiBannerBmp be set in a wixlib?

Currently I'm trying to move the WixUIBannerBmp, WixUIDialogBmp and WixUILicenseRtf WixVariables and their corresponding binary files to a wixlib. Unfortunately when building it ignores these and uses the defaults.
My Library.wxs:
<Fragment>
<WixVariable Id="WixUILicenseRtf" Value="licence.rtf" />
<WixVariable Id="WixUIBannerBmp" Value="binaries/bannrbmp.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="binaries/dlgbmp.bmp" />
</Fragment>
where the rtf and bmp files are included in the wixlib project and the paths are relative to the Library.wxs file.
Anyone have any ideas why this isn't working?
Thanks
Managed to work this out myself! :)
Firstly the fragment is not automatically included into the main Product.wxs unless something is explicitly referenced. In this case I'm using the ARPPRODUCTICON property. If you don't have anything that you can use you can just add a dummy property that will never be used.
Also the paths to the binaries will then be incorrect as the path will be relative to the Product.wxs file. Therefore you need to use the Preprocessor variable to the current project path.
Product.wxs
<Wix>
<PropertyRef Id="ARPPRODUCTICON" />
</Wix>
Library.wxs
<Fragment>
<WixVariable Id="WixUILicenseRtf" Value="$(var.ProjectDir)\adastra-licence.rtf" />
<WixVariable Id="WixUIBannerBmp" Value="$(var.ProjectDir)\Bitmaps\bannrbmp.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="$(var.ProjectDir)\Bitmaps\dlgbmp.bmp" />
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
<Icon Id="icon.ico" SourceFile="$(var.ProjectDir)/App.ico"/>
<UIRef Id="WixUI_Common" />
</Fragment>