This is my first project with WiX.I'm creating an installer for Windows Service and during the installation, I need to collect some configuration data that the service will use. This includes the connection string to the database.
<Binary Id="CustomActionBinary" SourceFile="$(var.ServiceSetupActions.TargetDir)$(var.ServiceSetupActions.TargetName).CA.dll"/>
<CustomAction
Id="ServiceSetupActions"
BinaryKey="CustomActionBinary"
DllEntry="SaveCompanySettings"
Execute="deferred"
Return="check"
Impersonate="no" />
<CustomAction
Id="SetupCustomProperties"
Property="ServiceSetupActions"
Value="DBTYPE=[DBTYPE];CONNECTIONSTRING=[CONNECTIONSTRING];INSTALLFOLDER=[INSTALLFOLDER]"/>
<InstallExecuteSequence>
<Custom Action="SetupCustomProperties" Before="ServiceSetupActions" />
<Custom Action="ServiceSetupActions" After="InstallFiles">NOT Installed</Custom>
</InstallExecuteSequence>
Problem is that WiX is using a semicolon as a data separator (https://github.com/wixtoolset/Dtf/blob/09dc7b4e4494182c0906bf5492f12e09c918444f/src/WixToolset.Dtf.WindowsInstaller/customactiondata.cs#L32), so the connection string that is entered during the setup is incorrectly deserialized in my custom action.
The question is: How can I correctly pass a string that contains a semicolon to custom action (using a session.CustomActionData) so it will not be misformed.
The alternative is a full SQL connection dialog that will ask for server, database, user name, and password, but the service can handle PostgresSQL and MS SQL and sometimes the connection strings may contain some modifications.
I see you tagged this C# so I'm assuming your using DTF custom actions.
In DTF you can create another custom action and run it in immediate execution. In that action you can create a CustomActionData collection, populate the key value pairs and then call session.DoAction() and pass it the name of the deferred custom action to schedule and the CustomActionData collection. The serialization magic will happen magically and the key value pairs will be available in the deferred custom action simply by saying session.CustomActionData(key);
Please check this sample and see if it works for you:
https://github.com/glytzhkof/WiXDeferredModeSample
Here is another version of the sample, this one uses the DTF CustomActionData class for more "auto-magic": https://github.com/glytzhkof/WiXDeferredModeSampleDTF - it will schedule the custom actions for you and you can just access the properties in deferred mode. Limited testing done.
Set an upgrade GUID in this source line where it says "PUT-GUID-HERE" - you can create a GUID here.
Change the property MYPROPERTY to contain semicolons ;. Do so at this WiX source line. You can use the below sample text if you like (anything else will do of course):
<Property Id="MYPROPERTY" Hidden="yes" Secure="yes">Test;Test1;Test2</Property>
Compile and do a test run. There will be a message box showing and it should contain the full string you specified in the source file.
If you want to combine several property values inside the string you send to deferred mode you have a few options. The simplest is to set several properties with the values you need and then combine them in a string sent to deferred mode. You can set the properties in several ways: dialog, command line, input boxes, etc...:
MYCOMPANY = "Some Company"
MYDATABASE = "TheDatabaseName"
Etc...
Then you call Session.Format in an immediate mode custom action to resolve values in your string. Something like this:
COMPANYID=[COMPANYID];DBTYPE=[DBTYPE];CONNECTIONSTRING=[CONNECTIONSTRING];INSTALLFOLDER=[INSTALLFOLDER]
var CAdata = Session.Format("MYCOMPANY=[MYCOMPANY];MYDB=[MYDB];MYDBTYPE=[MYDBTYPE]");
session["MYPROPERTYSENTTODEFERREDMODE"] = CAdata;
You also need a Type 51 CA to actually send that string to the deferred mode custom action. You see how that is done in the sample above.
Then you retrieve the string value of MYPROPERTYSENTTODEFERREDMODE in a deferred mode custom action and you should be able to use it directly?
Links:
An existing answer on deferred mode and WiX markup.
Related
I have written a C# custom action for my WIX V3 Installer which is supposed to modify my appsettings.json in the INSTALLFOLDER. The action´s Execute-attribute is set to immediate and Impersonate="no",it is called after InstallFinalize but it encounters a problem within this action which is the missing admin permission.
The action modifies appsettings.json in the INSTALLFOLDER which is Program File (x86).
The custom action reads, deserializes, modifies, and serializes the data normally with no error.
The error happens during writing to appsettings.json in InstallFolder.
Although the error appears the rest of the application is installed and is working fine.
I have tried combining Execute and Custom actions in ALL possible combinations, and while I get privileges to write to InstallFolder if I change the Custom Action to run before the installation is finished I can't find the appsettings.json file because all the files at that point are temporary files (.tmp), and with non-relevant names.
The error that appears:
Error message
Part of my Product.wsx code:
<Property Id="Password" Value="Error password" />
<Property Id="FilePath" Value="C:\Program Files (x86)\Company\Product\" />
<CustomAction Id="SetUserName" Property="Username" Value="[ACCOUNT]"/>
<CustomAction Id="SetPassword" Property="Password" Value="[PASSWORD]"/>
<CustomAction Id="SetFilePath" Property="FilePath" Value="[INSTALLFOLDER]"/>
<Binary Id="GetData" SourceFile="$(var.SetupExtensions.TargetDir)\$(var.SetupExtensions.TargetName).CA.dll" />
<CustomAction Id="ChangeJSON" BinaryKey="GetData" DllEntry="CustomAction1" Execute="immediate" Impersonate="no" Return="check"/>
<InstallExecuteSequence>
<Custom Action="SetUserName" After="CostFinalize" />
<Custom Action="SetPassword" After="CostFinalize" />
<Custom Action="SetFilePath" After="CostFinalize"/>
<Custom Action='ChangeJSON' After='InstallFinalize'></Custom>
</InstallExecuteSequence>
My Custom Action code:
public static ActionResult CustomAction1(Session session)
{
try
{
session.Log( "Begin CustomAction1" );
string user = session["Username"];
string password = session["Password"];
string loc = session["FilePath"];
var json = System.IO.File.ReadAllText( loc +"appsettings.json" );
var root = JsonConvert.DeserializeObject<Root>(json);
root.Default.UserName = user;
root.Default.Password = password;
json = JsonConvert.SerializeObject( root, Formatting.Indented );
System.IO.File.WriteAllText( loc + "appsettings.json", json );
//The MessageBox bellow shows(and is with correct info) when I remove System.IO.File.WriteAllText above ^^
MessageBox.Show("Username: "+ user +"\nPassword: "+password +"\nFilePath: " + loc);
return ActionResult.Success;
}
catch(Exception ex )
{
session.Log( "Error: " + ex.Message );
MessageBox.Show(ex.Message);
return ActionResult.Failure;
}
How do I modify appsettings.json through my Custom Action?
Custom actions that change the system state should run between InstallIntialize and InstallFinalize. This means you should schedule it Before InstallFinalize not after.
It should also run in deferred execution with no impersonation. You will need another custom action scheduled prior to this one that creates custom action data and passes the data to the deferred custom action.
Ideally you should also have rollback and commit actions to support rollbacks and test using the WIXFAILWHENDEFERRED custom action.
Read:
http://www.installsite.org/pages/en/isnews/200108/index.htm
http://blog.iswix.com/2011/10/beam-me-up-using-json-to-serialize.html
Application Launch: I would consider updating the JSON via the application launch sequence if you can. 1) Single source solution, 2) familiar territory for developers, 3) easier and better debugging features and 4) no impersonation-, sequencing- and conditioning-complexities like you get for custom actions: Why is it a good idea to limit the use of custom actions in my WiX / MSI setups?
Deferred CA Sample: You can find a sample of a deferred mode custom action WiX solution here:
https://github.com/glytzhkof/WiXDeferredModeSample
Inline Sample: This older answer shows the key constructs inline in the answer:
Wix Custom Action - session empty and error on deferred action.
CustomActionData: Deferred mode custom actions do not have access to the session objects property values like immediate mode custom actions do. As Painter has written you need to "send" text or settings to deferred mode by writing the data into the execution script. The data is then available in deferred mode by reading the special property CustomActionData. The above sample should have the required constructs to see how this works. See the MSI documentation as well and also Robert Dickau's MSI Properties and Deferred Execution.
Transaction: Deferred mode custom actions can only exist between InstallInitialize and InstallFinalize. Actions between these actions run with elevated rights and can write to per-machine locations (not writeable for normal users). You can schedule yourself right before InstallFinalize for starters to test your current delete mechanism (I would try other approaches).
Rollback CAs: Rollback custom actions are intended to undo what was done with your JSON custom action and then revert everything to the previous state (cleanup after failed install). Writing these is quite involved and takes a lot of testing - many just skip them. It is best to try to find a library or a framework that does the job for you. I am not aware of any except the one linked to below, and I don't know its state. Rollback custom actions must precede the actual CA in the InstallExecuteSequence (when an MSI is rolling back it executes the sequence in reverse).
With all this complexity, custom actions become the leading source of deployment errors: https://robmensching.com/blog/posts/2007/8/17/zataoca-custom-actions-are-generally-an-admission-of-failure/
Links:
How to pass CustomActionData to a CustomAction using WiX?
https://github.com/NerdyDuck/WixJsonExtension (not sure of status of this)
I’m currently working on a project where I am using Wix for the installer. My application is developed using .net core and having appsettings.json as a configuration file.
I would like to update the values on the appsettings.json with the values which passed as a parameter during command-line installation
For example, I am passing value 500 through parameter BUFFER.SIZE
msiexec.exe /i c:\PathToMyMsi\MyMsi.msi BUFFER.SIZE="500" /L*vx c:\PathToMyLog.txt
To achieve this, I have defined property and custom action in Product.wxs as follow
<Property Id="BUFFER.SIZE" />
<Binary Id="GetParameters.CA" SourceFile="..\..\Installer\CustomActions\bin\$(var.Configuration)\CustomActions.CA.dll" />
<CustomAction Id="GetParValues"
BinaryKey="GetParameters.CA"
DllEntry="ConfigureBufferSize"
Execute="deferred"
Return="asyncWait"
Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="GetParValues" After="InstallFiles"><![CDATA[NOT Installed]]></Custom>
</InstallExecuteSequence>
Here is my custom action
[CustomAction]
public static ActionResult ConfigureBufferSize(Session session)
{
try
{
session.Log("Begin ConfigureBufferSize");
string size = "size = "+ session["BUFFER.SIZE"];
session.Log(size); // I do not see any log like "size = 50"
session.Log("End ConfigureBufferSize");
return ActionResult.Success;
}
catch (Exception e)
{
return ActionResult.Failure;
}
}
But, I am stuck here because I am not able to read the values inside the custom function. the log does not contain below string
"size = 500"
But, I see values in log as follow.
MSI (c) (D0:54) [10:47:06:515]: Command Line: BUFFER.SIZE=500
CURRENTDIRECTORY=50 CLIENTUILEVEL=0 CLIENTPROCESSID=17360
MSI (s) (84:DC) [10:47:19:361]: PROPERTY CHANGE: Adding BUFFER.SIZE property. Its value is '500'.
Property(C): BUFFER.SIZE = 500
How do I read these values in custom action and update the appsettings.json
I tried to useComponent as follow but it's not executing post installation
<Component Id="config" Guid="*">
<File Id="appconfig" Source="$(var.BasePath)\appsettings.json" KeyPath="yes" Vital="yes"/>
<util:XmlFile
Id="_pathFormat_" File="$(var.BasePath)\appsettings.json"
Action="setValue"
Name="pathFormat" Value="[BUFFER.SIZE]"
ElementPath="/ApplicationLog/BufferSize"
Sequence='1' />
</Component>
Confused!!
Update
This is how I am able to get the passed values in custom actions
Declare a property
<Property Id="BUFFER.SIZE" Secure="yes"/>
Define the Binary
<Binary Id="CustomActionDLL" SourceFile="..\..\Installer\CustomActions\CustomActions\bin\$(var.Configuration)\CustomActions.CA.dll" />
Define custom actions
<CustomAction Id="SetGetParsValues"
Property="GetParsValues"
Value="BUFFER.SIZE=[BUFFER.SIZE]"/>
<CustomAction Id="GetParsValues"
BinaryKey="CustomActionDLL"
DllEntry="ConfigureBufferSize"
Execute="deferred"
Return="check"
Impersonate="no" />
Set up the installation sequence
<InstallExecuteSequence>
<Custom Action="GetParsValues" After="InstallFiles"><![CDATA[NOT Installed]]></Custom>
<Custom Action="SetGetParsValues" Before="GetParsValues"><![CDATA[NOT Installed]]></Custom>
</InstallExecuteSequence>
Now, I am able to see the passed parameters in the log.
But, when I try to pass json file path, it fails
<Property Id="APPLICATION.PATH" Secure="yes" Value="$(var.BasePath)\appsettings.json;"/>
<CustomAction Id="SetFilePathID"
Property="SetFilePath"
Value="APPLICATION.PATH=[APPLICATION.PATH]"
Return="check"/>
This fails.
You can't use session["BUFFER.SIZE"] in a deferred custom action.
To pass a property from the MSI into a deferred custom action you need to use another action to set the value and then read that value in your custom action using a slightly different mechanism.
On the wixtoolset page for custom action you'll see it has a special mention in the Property description pointing to this microsoft article which talks about how getting context in a deferred custom action works.
An important thing to note about the second action is that its property value must be an exact match to the deferred custom action's Id value.
<CustomAction Id="SetGetParsValues" Property="GetParsValues" Value="BUFFER.SIZE=[BUFFER.SIZE]" />
<InstallExecuteSequence>
<Custom Action="SetGetParsValues" Before="GetParsValues"><![CDATA[NOT Installed]]></Custom>
</InstallExecuteSequence>
then in your custom action you can access the value by changing your session["BUFFER.SIZE"] to be session.CustomActionData["BUFFER.SIZE"]
It might also be useful for you to know about [#FileId] which gets evaluated as the install location of a component's File using the File's Id value. Then you can pass in two values to your custom action by updating the Value in the SetGetParsValues custom action to be Value="BUFFER.SIZE=[BUFFER.SIZE];JsonFilePath=[#JsonFileId]". I'm not 100% sure doing [#JsonFileId] will work there so you can also just set a property value before that and use the property value in the Custom action's Value.
I need to:
get data from users (from a UI), and put that data into Properties
import the Properties into some C# code using a Custom Action
do some stuff to the Properties (encrypt the values),
export the values back to WiX, where I will
create a registry key and put the encrypted values into them
I can accomplish everything on that list except for #4. That is, I can't seem to import and export values into the C# code. I think the problem is in the timing.
Here's an example of a Custom Action that is used to import some properties into some C# code:
<Property Id="VALUE" Value="value"/>
<SetProperty Id="CustomAction_PassProperty"
Value="VALUE=[VALUE]"
Sequence="execute"
Before="CustomAction_PassProperty"/>
<Binary Id="Binary_PassProps"
SourceFile="$(var.CreateRegistryKey.TargetDir)CreateRegistryKey.CA.dll"/>
<!-- Note that 'Impersonate="no"' elevates the privilege of the C# code, needed to create keys -->
<CustomAction Id="CustomAction_PassProperty"
BinaryKey="Binary_PassProps"
DllEntry="CreateKeys"
Execute="deferred"
Impersonate="no"
Return="check"
HideTarget="yes"/>
<InstallExecuteSequence>
<Custom Action="CustomAction_PassProperty"
After="InstallInitialize"/>
</InstallExecuteSequence>
Notice that the action is done after InstallInitialize.
Next, how to take the imported properties and convert them into variables in the C# code:
[CustomAction]
public static ActionResult CreateKeys(Session session)
{
string value = session.CustomActionData["VALUE"];
return ActionResult.Success;
}
Next, here's an example of how to export variables in C# code back into WiX, as properties:
<Binary Id="Binary_CustomActionTemplate"
SourceFile="$(var.CustomAction.TargetDir)CustomAction.CA.dll"/>
<CustomAction Id="CustomAction_CustomActionTemplate"
BinaryKey="Binary_CustomActionTemplate"
DllEntry="CustomActionTemplate"
Execute="immediate"
Return="check"/>
<InstallUISequence>
<Custom Action="CustomAction_CustomActionTemplate" After="LaunchConditions"/>
</InstallUISequence>
And this time the action is done after LaunchConditions.
Finally, how to create a Property, give it a value, and send it back to WiX in C#:
[CustomAction]
public static ActionResult CreateKeys(Session session)
{
session["VALUE"] = "Hello, world.";
return ActionResult.Success;
}
I think the problem lies in when -- that is, when during the installation sequence -- that I do both things (import and export), but I'm not sure. That is, I need to import the data into the C# code, do stuff in the C# code, then export data from C# code. But how?!? (waves hands dramatically at sky)
To sum up: how to import and export data into C# Custom Action in WiX (using the same C# code)?
AFAIK you cannot alter property values once in the execute sequence. Someone can correct me if I'm wrong and that would be an alternative answer to your question.
You should be able to encrypt the properties in the UI phase of the install. In your immediate custom action you can access the msi properties with session["PROPERTYNAME"] as opposed to session.CustomActionData["PROPERTYNAME"]. You can also set the properties: session["PROPERTYNAME"] = "new value string";.
Then on your Next/Install control on the page where the information is added you can put
<Control Id="Install" ...>
<Publish Event="DoAction" Value="EncodeAndSetValuesAction">1</Publish>
....
</Control>
If they are marked Secure='yes' and public (all caps name) then they should be usable in the execute phase of your installation with the encoded values.
Do note that if you launch your custom action with a DoAction control event (clicking next on the page where the user enters the values) you won't be able to get logging information in the msi log. See here for more info.
Small edit: I was writing this answer when you edited the question which shows you already know how to get the properties and set them in an immediate action.
Alternatively, you may be able to just schedule an immediate custom action to encrypt and set the values in the InstallExecuteSequence before InstallInitialize (or before WriteRegistryValues or anywhere?) and it will set the properties before the elevated Server portion of the install starts. I think the InstallExecuteSequence happens in two parts. One where it does a pass figuring out values and which files to install making an install script for the elevated Server portion of the install where it actually copies the files around and writes registry keys ect. This part is still the client context where you can run immediate custom actions and read/set properties. Secondly it will start the Server portion of the install with set in stone properties and follow the execution script that was just created.
I decided to put a short simplified explanation about the differences between immediate and deferred custom actions here as well.
There are basically two main differences between deferred and immediate custom action. Immediate custom actions can access the msi DB directly to read and/or modify properties but can't do anything that requires elevation unless the installer was run "as administrator" since it runs in the user's context.
Deferred actions can only run in the execute sequence and can only read property values set explicitly through CustomActionData with the same name as the action but they always have elevated privileges.
I have a need to be able to analyse the requested image URL and apply resize commands based on the URL requested without the user seeing the resize commands in the url. E.g.
http://someurl.com/image/category/image-title.jpg
To do this I have hooked into Pipeline_Rewrite - basic example below:
void Pipeline_Rewrite(IHttpModule sender, HttpContext context, ImageResizer.Configuration.IUrlEventArgs e)
{
// Clear the current command string - we're going to create a new one
e.QueryString.Clear();
var process = ((Global)HttpContext.Current.ApplicationInstance).ContainerProvider.RequestLifetime.Resolve<Interfaces.IRequestBuilder>();
// Set the "new" command string
e.QueryString = process.BuildCommandList(context);
}
BuildCommandList(context) analyses the current url and constructs a list of appropriate commands to add to the querystring, and returns a NameValueCollection. This all works fine, and the commands are applied, unless the new commands are a Preset defined in the config.
E.g.
If BuildCommandList(context) returns the equivalent of width=150&height=150, this works.
If BuildCommandList(context) returns the equivalent of preset=thumbnail this doesn't work, I just get the full size original image.
On further investigation, it looks like any Preset commands have already been expanded to their relevant command strings before the Pipeline_Rewrite method runs, so adding them here is too late.
Extract from the web.config file:
<resizer>
<presets onlyAllowPresets="false">
<preset name="thumbnail" settings="width=150;height=150" />
</presets>
<plugins>
<add name="Presets" />
</plugins>
</resizer>
Is there any way to clear the command string, apply a preset, and have ImageResizer reprocess the presets at this point?
If you want Presets to work with your event handler, your event handler needs to be registered first.
I would suggest removing <add name="Presets" /> from web.config, and instead installing it after you register your event handler, via new ImageResizer.Plugins.Basic.Presets().Install(Config.Current);
I am developing an MSI installer which includes a tool.exe file as a <Binary> element. At some point during installation, i need to run the tool.exe.
So i have a custom action to execute it:
<CustomAction Id="RunToolExe"
BinaryKey="ToolExe"
ExeCommand=" -r 240 -name appservice"
Execute="immediate"
Return="check"
/>
Then i schedule in <InstallExecuteSequence>
Problem: When the custom action runs, a cmd line window flashes very fast during installation. This is a bit unconfortable for the user. Is there way to hide this screen?
I can't use WixQuietExecCA beacuse there is no way i can reference the Binary tool.exe in Wix.
<CustomAction Id="SettoolEXEPATH" Property="EXEPATH" Value=""[INSTALLDIR]tool.exe" <additional commands> Execute="immediate"/>
<CustomAction Id="EXEPATH" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
you can use WixQuietExecCA like in example shown above, schedule the custom actions accordingly. First action SettoolEXEPATH set the property EXEPATH to path of tool.exe, this property name is used as custom action id for WixQuietExecCA which acts as the command line parameters.
Did you try to do like on this page, to run the CA in silent mode
Look at this link for RemoveFiles