WiX RegistrySearch is failing for MultiString - wix

I am facing issue for RegistrySearch for multistring, where as string search working fine.
Checked in installation logs
Action start 13:40:07: AppSearch. MSI (s) (40:E0) [13:40:07:381]:
PROPERTY CHANGE: Adding MYKEY property. Its value is ''. MSI (s)
(40:E0) [13:40:07:381]: PROPERTY CHANGE: Adding MYSERVICE property.
Its value is 'myvalue2'.
I have trimmed some logs here
Action ended 13:40:51:
ScheduleReboot. Return value 1. Action ended 13:40:51: INSTALL. Return
value 1. You must restart your system for the configuration changes
made to XXXXX to take effect. Click Yes to restart now or No if you
plan to manually restart later. Property(S): UpgradeCode =
{XXXXXX-XXXX-XXX-XXXX-XXXXXXXX}
Property(S): MYKEY = [~]myvalue1[~] Property(S): MYSERVICE = myvalue2
At end of installation it seems it has correctly evaluated the MYKEY but not during AppSearch, resulting in failing my Condition evaluation
<Feature Id="MyFeature" Level="" Display="" Title="" Description="" AllowAdvertise="no" ConfigurableDirectory="INSTALLDIR">
<MergeRef Id="MyFeature" Primary="yes"/>
<Condition Level="0">((MsiNTProductType=1) OR
(MYKEY="[~]MyValue[~]") OR
(MYSERVICE="MyService" AND MYKEY=""))</Condition>
</Condition>
</Feature>
<Property Id="MYKEY" Secure="yes">
<RegistrySearch Id="MyKey"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Services\MyService"
Name="mykey"
Type="raw" />
</Property>
<Property Id="MYSERVICE" Secure="yes">
<RegistrySearch Id="MYSERVICE"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Services\MyService"
Name="DisplayName"
Type="raw" />
</Property>

UPDATE: I might have missed you stating it already, but when checking whether the property set by the AppSearch search has any assigned value at all using simply PROPERTYNAME as condition, the condition shows up as true - meaning that "something" exists in the property in question, the text is just not displayed.
Is it sufficient to test just for the presence of a value, or do you need to check the specific value of MYKEY? If the mere presence of a value is enough, then you may be able to use this condition:
((MsiNTProductType=1) OR (MYKEY) OR (MYSERVICE="MyService" AND MYKEY=""))
I guess this answer from Rob Mensching from the WiX-users mailing list answers the question with certainty. Multi-string is simply not supported for AppSearch.
There is no need to doubt the accuracy of this since Rob was on the original MSI team. You need to abandon this approach. Sorry to say. Unless the above workaround that I just added could work (check not the value, but if there is a value at all being retrieved from the registry).
A couple of other, potential workarounds:
You could read the multi-string from a custom action. I just verified that it works with a test VBScript - the forbidden MSI tool :-).
Could you search for a file or directory on disk that would signify the same thing you retrieve from the registry with this multi string?
As my motto goes every now and then: let's obsess over this (as opposed to: "careful, we don't want to learn from this" - which is another motto of mine - which tends to be the better option).
It is truly odd, that I can replicate what you state about your log file. I see a CommandLine entry which shows the multi-sting correctly, albeit with several extra null characters (slightly shortened log entry):
CommandLine: NORMALSTRING="sample regular string" MULTISTRING="[~~~]String 1[~~~]String 2[~~~]String 3[~~~]" INSTALLFOLDER="C:\Program Files (x86)\WiX3_GenericTestProject\" TARGETDIR="C:\" ACTION="INSTALL" EXECUTEACTION="INSTALL" ROOTDRIVE="C:\" INSTALLLEVEL="1" SECONDSEQUENCE="1" ADDLOCAL=Empty,Modules,ProductFeature
and also, later in the log file, after InstallFinalize:
Property(S): MULTISTRING = [~]String 1[~]String 2[~]String 3[~]
I really don't understand how that comes about. Somehow the AppSearch must have really set the property in question even if it didn't look like it did - the property just can't be retrieved correctly (or formatted correctly), and hence doesn't work in (feature) conditions either?
Maybe the underlying data model in Windows Installer has stored the retrieved registry multi string value as a BSTR (the abomination of a COM string format which allows embedded nulls and can be compiled and linked without being properly allocated / constructed via SysAllocateString - "burnt child, smells burnt - and all that...").
Anyway, I suppose AppSearch expectes a regular, null-terminated string buffer and interprets the BSTR as such? Hence stumbling on the first null value which is the first character of the data string section of the BSTR (not the length prefix section - the BSTR pointer points 4 bytes into the allocated BSTR memory) and reports an empty string overall? The property values that show up in the log file must have been read directly from the underlying data model by other means? I would assume the MSI Win32 C++ functions? But wouldn't that also be the case for AppSearch? Something is wrong with how this property string - with embedded nulls - is being displayed and used in conditions.
So in summary: maybe the retrieval of the multi string actually works, but the exposed value via Session.Property("PROP") erroneously reads the potential, native BSTR as a null-terminated string buffer and interprets the leading null as the end of the string buffer? Sort of doesn't make sense considering Session.Property is a COM call and should definitely understand a BSTR? Theories like these are never correct, but maybe they can help create some new ideas at least. What seemed like a missing Windows Installer feature, sort of smells like a bug I think. Or as it is in the real world: a technical problem, not easily fixed and hence seen and accepted as a missing feature.
Let me link together your questions on this issue for reference (and a couple of other answers):
RegistryValue Element of type multiString.
Failing condition wix.
Passing multiString values to installer through command-line.

Related

Wix Installer - Getting data from a Property

I am using Wix to make an installer and I was wondering how I indirectly get a property's value.
Like if I have a Property WIXUI_INSTALLDIR that is preset with the INSTALLFOLDER value
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
and this Property then is changed through a a Dialog from user input and I after that would want to get what the user changed the path to, how do I get the "value" out of WIXUI_INSTALLDIR ?
I would like to send this value (a string I suppose) to either the registry or a custom action.
If I try [WIXUI_INSTALLDIR] I get a warning for illegal indirection if used with a custom action and if I try to send it to registry it just becomes "[WIXUI_INSTALLDIR]" as a string.
EDIT:
My problem is when also using my own made up properties like this:
Property Id="MyOwnProperty" Value="Hello There"
and let's say a Dialogue changes the value of MyOwnProperty, how do I the access the new Value ? Like sending [MyOwnProperty] to a CustomAction as parameter will only get "[MyOwnProperty]" as a literal string.
Try getting the value of the Property (or Directory) that WIXUI_INSTALLDIR references. In this case, use [INSTALLFOLDER] in the formatted field where you'd like the value.

WiX: Feature/Condition behaving unexpectedly

I'm a novice WiX user, converting over several existing installations from a no longer maintained (.MSI-producing) commercial product.
As part of that I'm restructuring the original feature/component arrangements to take advantage of some of the new options WiX has opened up. One of these is adding Conditions at Feature rather than Component level, and as part of that I've tried specifying a conditional Feature like this (demo/test items throughout):
<Feature Id="Feature1" Title="Test Only" Level="0">
<Component Id="C1" Guid="*" Directory="SomeDir">
<File Id="File1" KeyPath="yes" Checksum="yes" Name="Test file.txt" Source="D:\xxx.dat" />
</Component>
<Condition Level="1"><![CDATA[INSTALLTYPE <> 1]]></Condition>
</Feature>
Where INSTALLTYPE is a Property defined at <Product> level and subsequently set to 0, 1 or 2 by the user via a RadioButtonGroup in the UI sequence.
Summarising what I've found after a day or so of trying different options and head-scratching:
With an initial Level=0, set to 1 by the condition (as in the sample above), the Feature is never installed.
With an initial Level=1, set to 0 by the condition, the Feature is installed when the condition is satisfied as long as an = test is used in the condition. If a INSTALLTYPE <> 1 is used, or Not (INSTALLTYPE = 1), the feature is never installed.
If a pre-defined Property (such as INSTALLLEVEL) is used in the test rather than my INSTALLTYPE, everything works correctly/as expected.
If equivalent tests are applied at Component, rather than Feature, level, everything works as expected for all Propertys and test operators.
Something seems to be going wrong with INSTALLTYPE but I can't for the life of me work out what. I've tried firing a VBScript custom action (MsgBox("INSTALLTYPE=" & Session.Property("INSTALLTYPE"))) in the Execute sequence to check the value of the Property, and as far as I can see this does display the value I'd expect it to have from the UI setting.
In principle I could get Features installing the way I want using the subset of settings I've found to work (e.g. INSTALLTYPE = 0 OR INSTALLTYPE = 2 rather than INSTALLTYPE <> 1), but my bafflement over apparently simple things not behaving as expected suggests I'm missing something fundamental...so I'm wary of just shrugging and applying workarounds.
If anyone has any thoughts on what I might be doing wrong with the items above, I'd be very happy to hear them!

Custom action to set a property after a failed RegistrySearch is not running

The aim is that if the value isn't found in the registry then I want to assign a default value and then have that value display as the default value in a field in the installer UI. The default value I actually want to use is [ComputerName] but obviously I can't use [ComputerName] directly in the property value attribute because it will give me errors on compiling, specifically:
warning CNDL1077: The 'MYPROPERTY' Property contains '[ComputerName]'
in its value which is an illegal reference to another property. If
this value is a string literal, not a property reference, please
ignore this warning. To set a property with the value of another
property, use a CustomAction with Property and Value attributes.
I want to get it working with plain text before I even try [ComputerName] but so far I can't even get that working.
In my project I have a Product.wxs file which contains the Product element, lots of custom actions (most of which are running fine but they're all running significantly later) and the following elements which are not cooperating and which are all siblings under the Product element.
Property definition and registry search:
<Property Id="MYPROPERTY" Value="ADefaultValue">
<RegistrySearch Id="MyProperty" Type="raw" Root="HKLM" Win64="$(var.Win64)"
Key="Software\MyCompany\MyApplication" Name="MyProperty" />
</Property>
Custom action definition:
<CustomAction Id="SetMyPropertyDefault" Property="MYPROPERTY" Value="MyCustomValue" Execute="immediate"/>
Custom action execution:
<InstallExecuteSequence>
<Custom Action="SetMyPropertyDefault" After="AppSearch"><![CDATA[MYPROPERTY="ADefaultValue"]]></Custom>
</InstallExecuteSequence>
It just will not work for me at all.
For the custom element content I have tried:
<Custom Action="SetMyPropertyDefault" After="AppSearch"><![CDATA[MYPROPERTY="ADefaultValue"]]></Custom>
<Custom Action="SetMyPropertyDefault" After="AppSearch">1</Custom> // I thought this would always run the custom action.
<Custom Action="SetMyPropertyDefault" After="AppSearch">NOT MYPROPERTY</Custom> // Back when I wasn't using the default value on the property at all.
The result is always the same, I'm still getting "ADefaultValue" showing up in the UI, never the alternate "MyCustomValue".
According to every blog and SO post I've seen I'm doing exactly what I should be doing except clearly I'm missing something.
Any ideas?
UPDATE/Answer:
The piece of information that I was missing which was provided by #sutarmin-anton was that InstallUISequence runs before InstallExecuteSequence (seems counter-intuitive to me but there you go).
But as it happened I didn't need to explicitly duplicate the custom action call in each of the install sequence elements, instead I used the SetProperty element.
So now I've got the following in my Product.wxs as children of the Product element:
<Property Id="MYPROPERTY">
<RegistrySearch Id="MyProperty" Type="raw" Root="HKLM" Win64="$(var.Win64)" Key="Software\MyCompany\MyApplication" Name="MyProperty" />
</Property>
<SetProperty Id="MYPROPERTY" After="AppSearch" Value="[ComputerName]">NOT MYPROPERTY</SetProperty>
It now runs the SetProperty after AppSearch in both InstallUISequence and InstallExecuteSequence, but the second time it runs the NOT MYPROPERTY will come out false so it doesn't get reset, and of course if it's run in quiet mode it'll still work correctly.
When you going through installer UI, installation is in InstallUISequence. InstallExecuteSequence runs after all UI events. This is cause of you have not seen "MyCustomValue". To change your property before UI sequence you should place your custom action in "InstallUISequence".
By the way, why don't you set default value of your property to "MyCustomValue"? Then, if AppSearch wont find value in regisrty, it just leave default value that you are trying to set manually.
You may be overcomplicating things. The MYPROPERTY value will not be set at all if you don't set a default. So then you call your CA to set it if 'NOT MYPROPERTY'
I see that you've tried this, and I'd say it's the correct approach that I'd try to diagnose rather than try something else. A verbose log would be invaluable. Do a:
msiexec /i [path to msi] /l*vx [path to a text log file]
and see what CA is called, what AppSearch does, property values etc.
Your original comment of "I can't use [ComputerName] - if that's the problem why not tell us what happened and maybe there is a solution that doesn't require all this. What's the compile error, for example?

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 RegistryValue setting to null (Value="")

I've been working on a WiX project for some time, and consuming the registry values that my setup initializes has proven to be "interesting". I am looking for a sanity check here, please.
I have the following line in my WiX script that creates a registry value:
<RegistryValue Type="multiString" Name="polling_manifest" Value="" />
This creates a registry value with the data that looks like this (from regedit/export):
"polling_manifest"=hex(7):00,00,00,00
which, to me, looks like a string[2] with two null strings in it. In actuality, if you open this registry value with something like:
public static string[] pollingManifestValue
{
get
{
return (string[])RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)
.OpenSubKey(AppSettingsGet("RegPathKeyConary"))
.GetValue(AppSettingsGet("rTISRegKeyPollingManifest"));
}
set
{
RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)
.OpenSubKey(AppSettingsGet("RegPathKeyConary"), true)
.SetValue(AppSettingsGet("rTISRegKeyPollingManifest"), value);
}
}
pollingManifestValue comes back as null. Consequently, all my code that accesses this construct has to have the "check for null first, then access properties"-type code.
I understand that those checks are the right thing to do, and I am not looking to be a more lazy programmer here. I am looking for thoughts and suggestions of those more experienced than I with the WiX deep magic, because it feels to me like I am not doing it optimally. Any code review / usage guidance is most welcome, and I thank you in advance.
Internally, REG_MULTI_SZ (Type="multiString") values are represented by a sequence of null-terminated strings, terminated by an empty string.
While this MSDN page clearly states that it is not possible to include a zero-length string in the sequence, tools like WiX do allow for writing empty strings to the registry. Registry Editor, while displaying such values properly in some places, will remove empty strings before saving.
So your example
<RegistryValue Type="multiString" Name="polling_manifest" Value="" />
which can also be written as follows to make its intention more clear
<RegistryValue Type="multiString" Name="polling_manifest">
<MultiStringValue></MultiStringValue>
</RegistryValue>
will be written to the registry as an empty string, followed by a \0 character, followed by the sequence-terminating \0 character. With each character represented by two bytes, the entire sequence is exported as "polling_manifest"=hex(7):00,00,00,00.
While testing this, I found that .NET, at least in version 4, doesn't use c-style string processing for reading REG_MULTI_SZ values, and can clearly process empty strings. Using code like yours clearly returned a string array containing one empty string element.
I noticed you're using the 64-bit registry view in your code, however it is unclear whether your value will be written to this registry view. Furthermore key path and value name are read from configuration; are you sure they are correct?
Create registry entry using this code
<RegistryKey Id="ServiceName" Action="createAndRemoveOnUninstall" Key="SOFTWARE\ProductName\[ProductCode]\FeatureName" Root="HKLM">
<RegistryValue Id="ServiceName" Type="string" Name="ABC" Value="[SERVICENAME]"></RegistryValue>
<RegistryValue Id="ServiceInstallLocation" Type="string" Name="ServiceInstallLocation" Value="[INSTALLLOCATION2]"></RegistryValue>
</RegistryKey>
For reading registry you can use the straight forward code like below
string processor = System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
processor = processor.Substring(processor.Length - 2, 2);
if (processor == "86")
{
regkey = Registry.LocalMachine.OpenSubKey("Software\\ABC company");
}
else
{
regkey = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\ABC company");
}
Then fetch the values
Registry.GetValue(regkey + "\\Services", "ServiceName", "0")
If it returns "0" then value does not exists else it will give you the service name.