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
Related
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.
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 am new to the WiX installer.
I am using Session.Log to log some useful data for the process.
session.Log("Begin register the Vdds interface.");
But I am not sure where can find the log. Is there a default path where it logs? Or should I need to specify the path that I need to provide in installer .wxs file?
You will need to run your installer from the command line using msiexec.exe and then include the L command line option to specify where the logs are to be saved.
For example:
msiexec /i app.msi /l*v thelog.txt
For more information about msiexec's parameters see Command-Line Options
Session.Log adds your log to the standard MSI Log. In case you provided the /l*v <LogPath> switch during execution, logs of custom action would be found in LogPath.
In case you used Property MsiLogging with value as vx in installer,
it would generate Standard MSI Log in user temp location (type %temp% in run), with LogName looking like MSI*.LOG, automatically even without the /L*v switch. LogPath could be overridden though with the /L*v switch.
Things you must know:
session.Log does not log when executed by any UI action.
Sometimes the installer cannot generate the MSI* log because of memory leak issues. In this scenario, you could restart the explorer.exe process.
session.Log works like session.Message with Info level:
public void Log(string msg)
{
if (msg == null)
throw new ArgumentNullException("msg");
using (Record record = new Record(0))
{
record.FormatString = msg;
int num = (int) this.Message(InstallMessage.Info, record);
}
}
You can use dirty trick: define a property in the referencing module and set its value in CA to a message You want to log. It seems that WIX logs changes of properties:
<Property Id="WIX_MAGIX_TRICK_PROPERTY" Value="0" />
and in CA:
session["WIX_MAGIX_TRICK_PROPERTY"] = "message to log";
The result should be similar to this:
MSI (c) (78!34) [09:48:13:770]: PROPERTY CHANGE: Modifying WIX_MAGIX_TRICK_PROPERTY property. Its current value is '0'. Its new value: 'message to log'.
Running the msi with the arguments: /L!*vx solved this for me.
E.g.
msiexec /i MyMsi.msi /L!*vx install.log
Now all my calls to Session.Log("..") now show in install.log
I am not sure where session.Log logs messages. However session.Message:
Record record = new Record();
record.FormatString = string.Format("Something has gone right!");
session.Message(InstallMessage.Info, record);
shows up in the log file generated by:
msiexec /i app.msi /l*v thelog.txt