Wix - How to distinguish registry key with no default value from no registry key - wix

Given the following...
<Property Id="TESTSEARCH">
<RegistrySearch Id="LookingForKeyExists"
Root="HKLM"
Key="Software\Classes\.ext"
Type="raw" />
</Property>
... I can get one of three conditions.
The key is not present
The key is present but not set
The key is present and has a value
I would like to be able to tell the difference between the following conditions.
<Condition Message="The extension .ext is missing">
???
</Condition>
<Condition Message="The extension .ext has no default value">
???
</Condition>
But all I've been able to find/figure out is the OR of the two.
<Condition Message="The extension .ext is either missing or does not have a default value">
TESTSEARCH
</Condition>
Can the two separate conditions be distinguished without writing an extension? If so, how?

I don't think it is possible and it looks like Windows Installer limitation, rather than WiX toolset. This original article on MSDN states that explicitly:
Note that it is not possible to use the RegLocator table to check only
for the presence of the key. However, you can search for the default
value of a key and retrieve its value if it is not empty.

Related

Wix Burn: Unsetting variable upon failed registry search

In my Bundle project, I'm searching registry for a version and setting a variable:
<Variable Name="Installed_Ver" bal:Overridable="yes" Type="version" Value="0.0.0.0" Persisted="yes" />
<util:RegistrySearch
Id="Self_Ver"
Root="HKLM"
Key="Software\MyCompany\MyProductName"
Value="Version"
Variable="Installed_Ver"
Format="raw"
Win64="yes"
/>
And using this in Condition as:
<util:RegistrySearchRef Id="Self_Ver" />
<bal:Condition Message="A recent or same version of this product is already installed on this machine. Please contact product support for more information." >
<![CDATA[WixBundleInstalled OR (WixBundleFileVersion > Installed_Ver)]]>
</bal:Condition>
This works fine if HKLM\Software\MyCompany\MyProductName\Version exists. But if this registry doesn't exist then registry search would fail and it is Unsetting the variable 'Installed_Ver'. This causes the condition wrongly evaluating to false.
By defining , I tried to set a default value i.e. "0.0.0.0" so it should have some value and condition would be evaluated properly i.e. to True in this case.
Here is the log, which shows that it is Unsetting the variable 'Installed_Ver'.
[5898:2AC4][2018-08-30T13:15:08]i000: Setting string variable 'WixBundleName' to value 'RegVersionCheck'
[5898:2AC4][2018-08-30T13:15:08]i000: Setting string variable 'WixBundleManufacturer' to value 'Testing'
[5898:3354][2018-08-30T13:15:08]i000: Setting numeric variable 'WixStdBALanguageId' to value 1033
[5898:3354][2018-08-30T13:15:08]i000: Setting version variable 'WixBundleFileVersion' to value '7.1.2.3'
[5898:2AC4][2018-08-30T13:15:08]i100: Detect begin, 1 packages
[5898:2AC4][2018-08-30T13:15:08]i000: Registry key not found. Key = 'Software\MyCompany\MyProductName'
[5898:2AC4][2018-08-30T13:15:08]i000: Unsetting variable 'Installed_Ver'
[5898:2AC4][2018-08-30T13:15:08]i101: Detected package: MainProduct, state: Absent, cached: None
[5898:2AC4][2018-08-30T13:15:08]i104: Detected package: MainProduct, feature: CalculatorFeature, state: Absent
[5898:2AC4][2018-08-30T13:15:08]i052: Condition 'WixBundleInstalled OR (WixBundleFileVersion > Installed_Ver)' evaluates to false.
[5898:2AC4][2018-08-30T13:15:08]e000: A recent or same version of this product is already installed on this machine. Please contact product support for more information.
[5898:2AC4][2018-08-30T13:15:08]e000: Error 0x81f40001: Bundle condition evaluated to false: WixBundleInstalled OR (WixBundleFileVersion > Installed_Ver)
[5898:2AC4][2018-08-30T13:15:08]i199: Detect complete, result: 0x0
I tried by hard-coding "0.0.0.0" in the condition in place of Installed_Ver and it works fine.
How should I get a default value for Installed_Ver if registry search fails?
Thanks
This is an open bug. You should be able to work around this by updating your condition to handle when the Variable is not defined.
WixBundleInstalled OR ((WixBundleFileVersion > Installed_Ver) AND Installed_Ver)
After some more searching, I've found a little workaround so that I can use the default value (0.0.0.0) being setup in the Variable definition. I defined another search for same registry key and getting the Boolean (exist) variable and depending upon this variable deciding to trigger the original search and get the version value. Here is my solution:
<Fragment Id="Self_Install_Check">
<?define ProdRegKey=Software\MyCompany\MyProductName?>
<Variable Name="Installed_Ver" bal:Overridable="yes" Type="version" Value="0.0.0.0" Persisted="yes" />
<util:RegistrySearch
Id="Self_Ver"
After="ProdRegExist"
Condition="ProdRegKeyExist"
Root="HKLM"
Key="$(var.ProdRegKey)"
Value="Version"
Variable="Installed_Ver"
Format="raw"
Win64="yes"
/>
<util:RegistrySearch
Id="ProdRegExist"
Root="HKLM"
Key="$(var.ProdRegKey)"
Value="Version"
Variable="ProdRegKeyExist"
Result="exists"
Format="raw"
Win64="yes"
/>
</Fragment>
So now the registry search for reading version would only be triggered if ProdRegKeyExist becomes true and Installed_Ver won't be Unset while retaining value=0.0.0.0 or else would get the Version value from registry...

Wix Toolset: Successively check conditions

I am trying to check two conditions during installation, but need to check it in series: if condition A is false - show error A, do not check condition B.
In details: I have to conditions checks
My app is installed
Version of database schema
If my app is not installed - I need to show an error message and do not check database schema, which leads to unknown error during install.
<Property Id="MYAPPINSTALLED">
<RegistrySearch Id="MyAppInstalledSearch"
Name="MyAppInstalled"
Root="HKLM"
Key="Software\MyApp\Installed"
Type="raw"
Win64="no" />
</Property>
<Condition Message="!(loc.RequireMyApp)">
<![CDATA[(MYAPPINSTALLED="1")]]>
</Condition>
<PropertyRef Id="GETSCHEMAVERSION"/>
GETSCHEMAVERSION is a Custom Action used in other components, which tries to connect to the Database and fails if MyApp is not present on the machine.
How can I check GETSCHEMAVERSION property only in case MYAPPINSTALLED condition pass?
Updated:
GETSCHEMAVERSION Custom Action is used in some other helper applications.
The second custom action, which checks for DB schema, should be conditioned to run only if the app is installed:
<Custom Action="CheckDbSchema">MYAPPINSTALLED="1"</Custom>
That custom action should set another property, as far as I understand, e.g. DBSCHEMAISOK to 1.
Then the components that depends on the schema to be available can be conditioned with MYAPPINSTALLED="1" And DBSCHEMAISOK="1".
I might be missing the syntax details, but you should get the idea.

Burn: Pass RegistrySearch results value to Custom Bootstrapper

In my Bundle code, I'm trying to use the result of a registry search to set Variable to be used in my Custom Boostrapper:
<util:RegistrySearch
Id="ThirdPartyInstallDirSearch"
Variable="THIRDPARTY_INSTALL_DIR"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
Value="OceanSoftDir"
Result="exists"
/>
<Variable Name="THIRDPARTY_INSTALL_DIR" Type="string" Value="$(var.THIRDPARTY_INSTALL_DIR)"/>
But this would give an error:
Undefined preprocessor variable '$(var.THIRDPARTY_INSTALL_DIR)'
Basically, I want to pass the result of registry search to my custom bootstrapper application.
Thanks
As the error says, $(var.<NAME>) is a preprocessor define from either a wxi file or from the project file <DefineConstants>name=value</DefineConstants> or from a <?define?>. You can read more about the preprocessor here.
For your issue, the registry search itself should be defining the variable. I do something similar to what you want in a bootstrapper.
<Fragment>
<util:RegistrySearch
Id="ClientInstalledCheck"
Root="HKLM"
Key="SOFTWARE\Client"
Value="ClientPath"
Result="value"
Variable="ClientInstalled"/>
<util:DirectorySearch
Path='[ClientInstalled]'
Variable='InstallFolder'
After='ClientInstalledCheck'
Condition='ClientInstalled' />
</Fragment>
And then in the products installed by the bootstrapper I will pass in the "InstallFolder" value to these installs.
<MsiProperty Name="INSTALLDIR" Value="[InstallFolder]"/>
This way if the user has installed in a non-default install location, we pick up the custom location they chose and use that instead. If the registry key did not exist, we use the default location.
I also have the InstallFolder variable defined with a default location (since my use case is slightly different than yours) as
<Variable Name="InstallFolder" Type="string" Value="[ProgramFilesFolder]$(var.CompanyInstallDir)\" bal:Overridable="yes" Persisted="yes"/>
Where CompanyInstallDir is defined as a preprocessor variable through <DefineConstants> which is originally defined somewhere in an MSBuild properties file.
So to explain your issue, you are mixing preprocessor directives and Variables. In your registry search, you're using Result="exists" which will set the variable THIRDPARTY_INSTALL_DIR to '0' or '1'. You want to use Result="value". This will put the registry location's value in the variable you define in the Variable="" attribute.
If you do the registry search + directory search with condition, you can properly set a variable if and only if the registry exists AND the directory is actually present on the machine and properly handle cases where it isn't but the registry still exists.
Some things you may do differently since the use case is slightly different but hopefully this sets you on the right path for doing what you need to do.

WiX File Search Conditional

I have a WiX installer project that I'm creating where I'd like the installer to check to see if another application is already installed on the user's machine. If it is, then I'd like to set the install level of one of the features to "1", otherwise it should remain hidden (i.e. install level = 0). To find out where the application is installed, I first do a registry search:
<Property Id="MYAPPINSTALLFOLDER">
<RegistrySearch Id='InstallPathRegistry'
Type='raw'
Root='HKLM'
Key='SOFTWARE\SomeLongAppPath' Name='FileName'
Win64='yes'/>
</Property>
You'll notice that the registry value that I end up getting is actually the directory of the installed application including the actual program name with extension (let's say myapp.exe). So, once I get the full path of the installed application, I check to see if the file exists:
<Property Id="MYAPPINSTALLED">
<DirectorySearch Id="CheckFileDir" Path="[MYAPPINSTALLFOLDER]" AssignToProperty="yes">
<FileSearch Id="CheckFile" Name="myapp.exe" />
</DirectorySearch>
</Property>
Now, what I would expect to see is that if the file actually exists in that location, then the Property called "MYAPPINSTALLED" would be set to 1, otherwise it would be 0. Then, when I setup my features I use something like this:
<Feature Id="ThirdPartyPlugins" Title="Third Party Plugins" Level="0">
<Condition Level="1">MYAPPINSTALLED</Condition>
<ComponentGroupRef Id="MyAppPlugin" />
</Feature>
However, when I run my installer the third party plugin feature is always hidden. I've enabled msi datalogging by setting the property like this:
<Property Id="MsiLogging" Value="voicewarmupx"/>
And when I check the log file I can definitely see that the MYAPPINSTALLFOLDER property gets changed to the correct file path when it does the registry search. However, if I search the log for the property MYAPPINSTALLED, then I can see the following:
AppSearch: Property: MYAPPINSTALLED, Signature: CheckFileDir
Action ended 15:55:06: AppSearch. Return value 1.
So, it looks like it worked, however it doesn't seem to ever set the Property to equal the search value. Am I doing something wrong? Can someone explain why my feature install level never gets set to 1 even though the application file exists?
Edit
Ok, after more debugging... I think the issue is that the directory search is trying to use a path that includes the file name and extension (i.e. C:/Program Files/MyApp/myapp.exe") instead of just the directory where the file comes from. This is because the registry search has the full path including the file name stored (but not just the install directory). If I do a directory search just using the correct absolute directory (not using the registry search) then the process works. So, my follow up question is... my Property MYAPPINSTALLFOLDER contains the full path with file name and extension. Is there a way to strip the file name and extension from this property so that I just have the proper directory name to search for?
You're checking to see if another application is installed but that's rather a long way around. Also, the file search returns a path, not zero or 1, but either way a full verbose log should tell you if the properties are being set. It might help if you could post the entire log somewhere rather than the parts you think are the only relevant ones. e.g. There's probably an AppSearch in the execute sequence for silent installs.
It's requently easier to do a single search for other applications that were installed with MSI packages in these ways:
If you know the other product's UpgradeCode (and version ranges if applicable) then add Upgrade/UpgradeVersion elements with onlydetect set to yes, and that search will set a property if the product is detected.
If you know (or can find out) the Component id of any of the relevant components from that other product, then you can use them in a WiX ComponentSearch. If you get the target property set then that component is installed. This post contains a couple of ways to find out component guids:
How to find out which application requires a certain assembly from GAC?
It's also puzzling that the AppSearch log extract you posted only refers to one property. The Directory/FileSearch is also an AppSearch, so if the MSI actually contains two searches in AppSearch there should be references to all the properties being set. Again, that's a reason to post the entire log and look in the MSI file for those searches. The RegLocator search is documnented to occur before the DRLocator, so why is there no MYAPPINSTALLFOLDER property in the AppSearch log entry? You're not on a 32-bit system are you? (noticing the win64 search).
Per the WiX documentation:
Use the AssignToProperty attribute to search for a file but set the outer property to the directory containing the file. When this attribute is set to yes, you may only nest a FileSearch element with a unique Id or define no child element [of the DirectorySearch].
I added the text in the brackets to make it more clear.
So, after reading this sentence a few times and cross referencing your WiX XML, I think I see what the problem is with your current WiX XML. You perform a separate registry search from the directory search. Instead, you should nest these. There are two ways to perform the search, depending on what you want to do. One way is to simply retrieve the registry value from the registry, and if the value exists, then you make the assumption that the feature's required application is installed, at which point you appropriately set a property that would enable hiding/showing the feature within your installer's feature selection tree. The other way is to actually find the file you're interested in, using the results of the registry search as the basis for the file search.
Below is the XML for just a registry search, which doesn't check that the file actually exists on disk. You're making the assumption that if this registry value exists, the file is installed and available.
<Property Id="MYAPPINSTALLFOLDER">
<RegistrySearch Id='InstallPathRegistry'
Type='raw'
Root='HKLM'
Key='SOFTWARE\SomeLongAppPath' Name='FileName'
Win64='yes'/>
</Property>
<Property Id="SHOW_APP_FEATURE" Value="hidden" />
<SetProperty Id="SHOW_APP_FEATURE" Value="collapse" Sequence="both" After="CostFinalize">
<!-- If MYAPPINSTALLFOLDER is defined and contains any non-empty value, this
evaluates to TRUE; otherwise, it evaluates to FALSE.
-->
MYAPPINSTALLFOLDER
</SetProperty>
<!-- You could also be more explicit:
<SetProperty Id="SHOW_APP_FEATURE" Value="collapse" Sequence="both" After="CostFinalize">
<![CDATA[MYAPPINSTALLFOLDER <> ""]]>
</SetProperty>
-->
<Feature Id="MyAwesomeFeature" Title="My Awesome App Feature"
Display="[SHOW_APP_FEATURE]">
... <!-- Component/ComponentRefs go here -->
</Feature>
If you want to ensure that, even if the registry value exists in the registry, that the file it points to is 1) actually a file path; and 2) that the file actually exists on disk, then you need to perform a nested file search within a directory search, which itself is nested within a registry search. You would again need to use a SetProperty custom action to set a property that would enable the hiding/showing of the feature within your installer's feature selection tree. Here's the XML for this search:
<!-- Performing a FileSearch nested within a DirectorySearch,
which is itself nested within a RegistrySearch
This search twill ensure that the file exists on disk, and
if so, assign the full filename and path to the
MYAPPINSTALLFOLDER property.
-->
<Property Id="MYAPPINSTALLFOLDER">
<RegistrySearch Id='InstallPathRegistry'
Type='raw'
Root='HKLM'
Key='SOFTWARE\SomeLongAppPath' Name='FileName'
Win64='yes'>
<DirectorySearch Id='InstallPathDirectory' AssignToProperty='yes'>
<FileSearch Id='InstallPathFile' Name='myapp.exe' />
</DirectorySearch>
</RegistrySearch>
</Property>
<Property Id="SHOW_APP_FEATURE" Value="hidden" />
<SetProperty Id="SHOW_APP_FEATURE" Value="collapse" Sequence="both" After="CostFinalize">
<!-- If MYAPPINSTALLFOLDER is defined and contains any non-empty value, this
evaluates to TRUE; otherwise, it evaluates to FALSE.
-->
MYAPPINSTALLFOLDER
</SetProperty>
<Feature Id="MyAwesomeFeature" Title="My Awesome App Feature"
Display="[SHOW_APP_FEATURE]">
... <!-- Component/ComponentRefs go here -->
</Feature>
This should allow you to accomplish what you're trying to achieve.

wix - fill edit-control with value from registry

Installer is writting SQL Server's name to registry during installing my service.
And I created a dialog window with edit control where user will type the servername . Now I want to fill this control with value from registry in ChangeMode. And if this registry key is empty to fill with some default name.
How is possible to resolve it?
I tried to put RegistrySearch node into Control node. But it seems to me it's not working..
Any help is appreciated
P.S. Looked some information here: link1. And tried code like:
<Property Id="SERVCONNSTR" Value=".\SQLEXPRESS">
</Property>
<Property Id="CONNSEARCH">
<RegistrySearch Id="servconstr" Root="HKLM"
Key="Software\$(var.Manufacturer)\SERVICE" Name="SQL Server" Type="raw"></RegistrySearch>
</Property>
<SetProperty Id="SERVCONNSTR" Value="CONNSEARCH"
After="AppSearch"><![CDATA[CONNSEARCH AND (!FEATURE1=3 OR !FEATURE2=3 OR !FEATURE3=3)]]></SetProperty>
But I'm still getting .\SQLEXPRESS in edit-control during modifying.
What i'm doing wrong?
Put RegistrySearch into a Property, and tie that property to the Control.
<Property Id="SERVCONNSTR" Value=".\SQLEXPRESS">
</Property>
<Property Id="CONNSEARCH">
<RegistrySearch Id="servconstr" Root="HKLM"
Key="Software\$(var.Manufacturer)\SERVICE" Name="SQL Server" Type="raw"></RegistrySearch>
</Property>
<SetProperty Id="SERVCONNSTR" Value="[CONNSEARCH]"
After="AppSearch">CONNSEARCH</SetProperty>
I don't know why you are using two properties, If you set your property CONNSEARCH with the registrySearch, it'll check the registry and assign it the value found there, if one is present. This happens in the AppSearch phase well before most UI's take place, where your edit box would be. So you shouldn't need to use SetProperty either.
Did you want to fill the registry key with a default value, or your property with a default value?
For the property just fill the Value attribute of the property element that is using a registry search. It'll default to that if nothing is found in registry, as I said above.
It would look like this;
<
<Property Id="CONNSEARCH" Value="DefaultValue">
<RegistrySearch Id="servconstr" Root="HKLM"
Key="Software\$(var.Manufacturer)\SERVICE" Name="SQL Server" Type="raw"></RegistrySearch>
</Property>
After Appsearch your property would be set, and if your control is tied to that property it'll be filled with that value. This will also set your default property value to "DefaultValue" If nothing is in the registry.
To create a default value for the key in the registry, I guess just put a registryKey/RegistryValue element in a component that will always be installed in this scenario, and set that key to your default value if you don't set it anywhere else.
Best