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.
Related
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!
I'm developing a custom filter to my file format. Everything works fine when I use any of system keys from <propkey.h> like PKEY_Search_Contents.
But now I need to have my custom properties to register and use with Windows Search. Imagine:
"SELECT * FROM SystemIndex WHERE scope ='file:C:/' AND Publisher.Item.CustomProperty LIKE '%Test%'"
I saw about Property Handlers and already tested. I used the RecipePropertyHandler project from Windows 7 SDK Samples. And when I searched with this code:
// get a property store for the mp3 file
IPropertyStore* store = NULL;
HRESULT hr = SHGetPropertyStoreFromParsingName(L"SomePath",
NULL, GPS_DEFAULT, __uuidof(IPropertyStore), (void**)&store);
hr = HRESULT_FROM_WIN32(GetLastError());
PROPVARIANT variant;
store->GetValue(PKEY_Microsoft_SampleRecipe_Difficulty, &variant);
//// very important undocumented method
store->Release();
CoUninitialize();
I receive the correct answer.
But I don't understand how to union IFilter with IPropertyStore to create my custom properties. To start my IFilter, I used the example from Windows 7 SDK Samples. I'm doing something like:
chunkValue.SetTextValue(PKEY_SearchContents, filtered.c_str(),CHUNK_TEXT, 1046, 0, 0, CHUNK_EOS);
I didn't found the link anymore anymore but I read a quote of msdn about you can't implement IFilter AND IPropertyStore together. It's that true? Talking in other words, I can't create a custom property?
I saw all links from msdn, like link, link2 or any other.
I can do separate things but I don't know how to union both.
Any ideia how to implement?
EDIT
Im trying yet implement the PropertyHandler. My .propdesc file have this content:
-->
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.microsoft.com/windows/2006/propertydescription"
schemaVersion="1.0">
<propertyDescriptionList publisher="Microsoft" product="SampleRecipe">
<propertyDescription name="Microsoft.SampleRecipe.Difficulty" formatID="{1794C9FE-74A9-497f-9C69-B31F03CE7EF9}" propID="100">
<description>This property indicates the preparation difficulty of a recipe.</description>
<searchInfo inInvertedIndex="true" isColumn="true" />
<typeInfo type="String" multipleValues="false" isViewable="true" />
<labelInfo label="Recipe difficulty" invitationText="Specify recipe difficulty" />
<displayInfo displayType="Enumerated" >
<editControl control="DropList"/>
<enumeratedList>
<enum value="Easy" text="Easy" />
<enum value="Medium" text="Medium" />
<enum value="Hard" text="Hard" />
</enumeratedList>
</displayInfo>
</propertyDescription>
<propertyDescription name="Microsoft.SampleRecipe.Keywords" formatID="{16D19FCB-7654-48AB-8057-DF8E51CC0755}" propID="100">
<description>This property indicates the preparation difficulty of a recipe.</description>
<searchInfo inInvertedIndex="true" isColumn="True"/>
<typeInfo type="String" multipleValues="true" isViewable="true" />
<labelInfo label="Recipe Keywords" invitationText="Specify recipe keyword" />
</propertyDescription>
</propertyDescriptionList>
</schema>
On Windows Properties:
I receive all informations from file... but when I try to search with Windows Search like:
SELECT Microsoft.SampleRecipe.Keywords FROM SystemIndex
Where directory='somedirectory'
I receive all lines empty... Any ideias why?
EDIT
I can see my properties on PropSchema:
I can use my Microsoft.SampleRecipe.DifficultyV2 on my WSSQL Query like:
SELECT Microsoft.SampleRecipe.KeywordsV2 FROM SystemIndex WHERE directory='C:\users\step\documents\rvffilter\'
But all the contents are empty
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.
I have a scenario in which I need to update a ini file during installation. I am using the IniFileElement thusly:
<Component Id="TestIni"
Guid="{058D0B2B-0145-4743-969F-C5AACA992936}" Directory="INSTALLFOLDER">
<CreateFolder />
<IniFile Id="Ini2" Action="addLine"
Directory="INSTALLFOLDER" Name="File.conf"
Key="theKey"
Value="[ComputerName]" />
</Component>
But it does not work since the Section attribute is missing. Is there a way I can write to the "default" section of the IniFile? My Ini file does not have sections defined.
Here's what I can tell you... the WiX XSD for the section attribute to exist and have a value. The underlying Windows Installer IniFile table also says that the Section column cannot be null. It also says it's of type Formatted which means I can give it a property name for a value. Just to really push the limites I tried to give it a null property ( [FAKE] ) and see what would happen. It failed with an error 2109 which is Section missing for .ini action.
So I'll admit, I'm getting a little old and forgetful.... In all my years, I don't recall INI values without sections. According to Wikipedia "Keys may (but need not) be grouped into arbitrarily named sections." I don't believe this though. There are no references for this assertion and the references that are listed are for ".conf" files (which are not .INI files) and a bunch of suspect web sites.
Let's dig a little deeper. GetPrivateProfileString function says "The name of the section containing the key name. If this parameter is NULL, the GetPrivateProfileString function copies all section names in the file to the supplied buffer."
WritePrivateProfileString function says for lpAppName "The name of the section to which the string will be copied. If the section does not exist, it is created. The name of the section is case-independent; the string can be any combination of uppercase and lowercase letters." It does not say that it can be null.
So, in my opinion INI files cannot / should not have keys without sections. Your ".conf" file may look like an INI file but it is not an INI file and you'll need custom actions to do search and replaces. Either that or you redesign your application (if possible) to conform to INI specifications.
I need to use the percentage char within the value attribute of the XmlFile element to configure path of log4net rolling file appender while application is installed.
Target is to get log4net configured like this
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="Log_[%processid].log" />
...
</appender>
The base wix code looks like this:
<util:XmlFile
Id="RollingFileAppenderLogPath"
File="[INSTALLLOCATION]log4net.config" Action="setValue" Permanent="yes"
ElementPath="/log4net/appender[\[]#name='RollingFileAppender'[\]]/file" Name="value"
Value="[LOGPATH]Log_[%processid].log"/>
I've tried some various replacements for [ ] and % like entities ([, ] and %), doubling, tripling, quadrupling but the value is always mentioned as environment variable or causes ICE03: Invalid string format.
The replacement result looks like this:
<file type="log4net.Util.PatternString" value="<inserted LOGPATH>Log_.log"/>
Is there a way to get [%processid] forced as string to get it inserted as intended?
Think I found the solution: the problem is with the square brackets!
If you open the WIX Documentation and navigate to the "XmlFile Element", on the "Value" property it reads:
The value to be written. See the Formatted topic for information how to escape square brackets in the value.
So, just ckeck the link above and alter the text, escaping the square brackets and all should work fine! :)