Hello Oleg,
I've managed to setup a Wix# project which injects a custom CLR dialog after the InstallDir dialog to request data from the user (for customization after being deployed to the chosen folder).
But although data is fetched via session.Property("MYPROPERTY") correctly, it's not written to the actual deployed file. I suspect I'm just using the wrong Step and/or When, but I can't get it to work.
Here's my code so far:
var project = new Project(
projectName,
new Dir(
$@"%ProgramFiles%\Company\projectName",
new DirFiles(
$@"{outputDir}\*.*", fileName => !new[] { ".pdb", ".xml" }.Any(fileName.EndsWith))),
new ElevatedManagedAction(
ModifyDeployedConfiguration,
Return.check,
When.After,
Step.InstallFiles,
Condition.NOT_Installed)
{
UsesProperties = "INSTALLDIR,INPUTFOLDER"
})
{
Codepage = Encoding.GetEncoding(1252).CodePage.ToString(), // ANSI Latin 1/Western Europe; do NOT change!
ControlPanelInfo = new ProductInfo { Manufacturer = info.CompanyName },
Encoding = Encoding.UTF8,
GUID = new Guid("REMOVEDFORPRIVACY"),
InstallScope = InstallScope.perMachine,
Language = "en-US",
Platform = Platform.x64,
UI = WUI.WixUI_Common,
Version = new Version(productVersion)
};
project.Media.ForEach(media => media.CompressionLevel = CompressionLevel.high);
project.InjectClrDialog("ShowInputFolderDialog", NativeDialogs.InstallDirDlg, NativeDialogs.VerifyReadyDlg)
.RemoveDialogsBetween(NativeDialogs.WelcomeDlg, NativeDialogs.InstallDirDlg)
.BuildMsi();
(...)
static ActionResult ModifyDeployedConfiguration(Session session)
{
XDocument document = XDocument.Load(Path.Combine(session.Property("INSTALLDIR"), "MyApp.exe.config"));
List<XElement> settings = document.Descendants("MyApp.Properties.Settings").Elements().ToList();
if (settings.Count < 1)
{
return ActionResult.Failure;
}
XElement setting = settings.FirstOrDefault(node => node.Attribute("name")?.Value == name);
string value = session.Property(name.ToUpper());
if (setting != null)
{
setting.FindFirst("value").Value = value;
}
else
{
settings.Add(
new XElement(
"setting",
new XAttribute("name", name),
new XAttribute("serializeAs", "String"),
new XElement("value") { Value = value }));
}
}
Please, can you help me?
Thanks in advance,
Martin
I'm very sorry for answering when not being asked.
I had some problem which looks similar to yours.
My solution was to simply write the app.config file with correct data before WixSharps' Project
ConfigHelper.InsertParams(config, configParams, _productSufix);
...
var project = new Project(
...
);
...
Compiler.BuildMsi(project);
public static void InsertParams(string configFile, ConfigParams configParams, string productSufix)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(configFile);
XmlNodeList elementList = xmlDoc.GetElementsByTagName("add");
for (int i = 0; i < elementList.Count; i++)
{
XmlNode node = elementList[i];
if (node.Attributes["key"].Value == "BasePath")
{
node.Attributes["value"].Value = configParams.BasePath;
}
if (node.Attributes["key"].Value == "Version")
{
node.Attributes["value"].Value = configParams.Version;
}
...
}
xmlDoc.Save(configFile);
}
I'm very sorry for answering when not being asked.
Why??? This is a public discussion board not a personal blog. Yes I am trying to be responsive but sometimes I wander if my quick responses prevent others from participating.
Please, everyone, jump in every time you think you have something to say. Making this forum "Oleg consulting" would only be appropriate if Wix# support goes commercial. :)
I am not giving up :) I am not going to withdraw myself from the discussions but I would really like to see more multi-directional exchange here.
While I initially addressed my issue to @oleg-shilo, I totally agree with him that anyone is free to answer here. After all, it's diversity that enables us to get the best solutions. 馃槈
So, I tried your approach, @silentnightwalk, but it seems you modify the file prior to building the MSI while my use case is to write user-provided values into the file after deployment happened.
After several debug sessions, I finally came up with this:
string configFilePath = Path.Combine(session.Property("INSTALLDIR"), "MyApp.exe.config");
XDocument document = XDocument.Load(configFilePath, LoadOptions.PreserveWhitespace);
// This is where the magic happens.. ;)
document.XPathSelectElements($"//configuration/userSettings/MyApp.Properties.Settings/setting")
.ForEach(setting => setting.SetElementValue("value", session.Property(setting.Attribute("name")?.Value.ToUpper())));
document.Save(configFilePath);
So, as you can see, I select all setting nodes via XPath first, then set each node's value to its customized value and after that, I'm finally saving the file.
Again, thanks for coming by and either suggesting or pointing me in the right direction. It was (and still is) a real pleasure, working with such nice people and such an awesome framework! 馃槃
Excellent points....
As for the sample, I also thought abut XPath for the original problem in #165. Though it wasn't clear if it was required to add or update the element in that case.
The very code you provided will fail if _path_ does not exist. To address this you can use Wix# SelectOrCreate extension to handle this transparently (#165 sample):
``` C#
doc.root.SelectOrCreate("userSettings/MyApp.Properties.Settings/setting")
.SetAttributes("name=InputPath;serializeAs=String")
.SetElementValue("value", @"C:\Input");
The only one thing that I don't like there is the fact that the path supposed to start from the first element. I have updated the extension method to allow operations directly on the XDocument objects. Starting from the next release you it will be possible to do the operation directly on the document object:
```C#
var config = XDocument.Load(configFile);
if (installing)
config.SelectOrCreate("configuration/userSettings/MyApp.Properties.Settings/setting")
.SetAttributes("name=InputPath;serializeAs=String")
.SetElementValue("value", @"C:\Input");
else
config.Select("configuration/userSettings/MyApp.Properties.Settings")
?.Parent.Remove();
config.Save(configFile);
First of all, thanks for your contribution. I already applied it to my existing code.
Regarding your concern about starting at the first element: I might be wrong, but doesn't XPath, when used as XPathSelectElements("MyApp.Properties.Settings"), search through all nodes until it finds the node?
Because if so, updating your SelectOrCreate() method should be fairly easy. Of course, in case of creation (rather than selecting), the method would need to know where to insert the node(s). Maybe through a second parameter for the depth or parent node?
Lastly, please elaborate on why you don't like XPath starting from the first element. Too much power for Wix# users regarding wrong usage on their end?
I also noted that you're using ("configuration instead of ("//configuration). I'm just curious why you opted for this approach.
There quite a few questions so I'd rather answer them one by one. But before I do that I want to explain the objective of Wix# XML extensions.
XPath is great. It's simple and expressive. But similarly to another great content navigation technique _Regular Expressions_ it looses its attractiveness when it is used to handle complicated scenarios and XML-LINQ becomes more preferable.
Therefore Wix# XML extensions are not a wrapper around XPath but rather a domain specific API that extends XML-LINQ with XPath-like expressions and some missing _Fluent Interface_ APIs.
Thus Wix# XML extensions are shining when used within WiX domain but not necessarily the best (even though possible) choice for other common XML tasks.
The extensions exhibit the following main characteristics:
Select and SelectOrCreate support selection of a single element only. For multi item results FindAll is to be used.Now your questions.
I might be wrong, but doesn't XPath, when used as XPathSelectElements("MyApp.Properties.Settings"), search through all nodes until it finds the node?
No, I just tested and XPath only works with the full path:
```C#
var doc = new XDocument();
doc.SelectOrCreate("configuration/userSettings/MyApp.Properties.Settings/setting")
.SetAttributes("name=InputPath;serializeAs=String")
.SetElementValue("value", @"C:\Input");
var e1 = doc.XPathSelectElements("//configuration/userSettings/MyApp.Properties.Settings/setting").FirstOrDefault();
var e2 = doc.XPathSelectElements("MyApp.Properties.Settings").FirstOrDefault();
``
e1has the result ande2` is null.
Of course, in case of creation (rather than selecting), the method would need to know where to insert the node(s)
This part is easy. The latest SelectOrCreate() works on both XDocument and XElement, meaning that if you use doc.SelectOrCreate(...) a root will be selected/created and if you do 'element.SelectOrCreate(...)' then a descendant of that element is going to be selected/created.
Lastly, please elaborate on why you don't like XPath starting from the first element.
I actually do like XPath starting from the first element. In all my samples in this post it always start from 'configuration', which is the first element. The old API did not allow this, Th call SelectOrCreate on doc was not available so one had to apply it to root and drop the first element from the XPath expression. This is exactly what I have changed in that last commit.
...using ("configuration instead of ("//configuration). I'm just curious why you opted for this approach.
Because '//' is a true XPath prefix which has no meaning in Wix# extensions. Particularly because Wix# uses method names not path notation to indicate multiplicity. ele,ent.Select* always returns a single element and for multiple element.FindAll is to be used.
Most helpful comment
Why??? This is a public discussion board not a personal blog. Yes I am trying to be responsive but sometimes I wander if my quick responses prevent others from participating.
Please, everyone, jump in every time you think you have something to say. Making this forum "Oleg consulting" would only be appropriate if Wix# support goes commercial. :)
I am not giving up :) I am not going to withdraw myself from the discussions but I would really like to see more multi-directional exchange here.