Send Custom Action Data via Command Line for Visual Studio Installer - c#

I have a Visual Studio Installer that has a custom UI with one text box recovering a value that is set to QUEUEDIRECTORY property. Then I have a custom action (an Installer class) that passes in that property value with this line /queuedir="[QUEUEDIRECTORY]" - and the installer works great.
Now, I need to send that value via the command-line so that this installer can be run by system administrators all across the organization. So, I tried the following command line statements but it just doesn't work.
msiexec /i Setup.msi QUEUEDIRECTORY="D:\temp"
Setup.msi QUEUEDIRECTORY="D:\temp"
Setup.msi queuedir="D:\temp"
msiexec /i Setup.msi queuedir="D:\temp"
Further, I can't seem to find anything online that doesn't feel like they hacked it because they just couldn't find the solution. I mean I've found some solutions where they are editing the MSI database and everything, but man that just doesn't seem like it's the right solution - especially since I'm using Visual Studio 2010 - Microsoft has surely made some enhancements since its initial release of this offering.
Here is one of the articles that appears would work but still really feels like a hack.
At any rate, I hope that you can help me out!

This is what I did to add command line only property values to my MSI in Visual Studio 2010. It's similar to the accepted answer, but less hacky. Create CommandLineSupport.js in setup project (.vdproj) directory, with the following code:
//This script adds command-line support for MSI installer
var msiOpenDatabaseModeTransact = 1;
if (WScript.Arguments.Length != 1)
{
WScript.StdErr.WriteLine(WScript.ScriptName + " file");
WScript.Quit(1);
}
WScript.Echo(WScript.Arguments(0));
var filespec = WScript.Arguments(0);
var installer = WScript.CreateObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
var sql
var view
try
{
sql = "INSERT INTO `Property` (`Property`, `Value`) VALUES ('MYPROPERTY', 'MYPROPERTY=\"\"')";
view = database.OpenView(sql);
view.Execute();
view.Close();
database.Commit();
}
catch(e)
{
WScript.StdErr.WriteLine(e);
WScript.Quit(1);
}
Then click on your Deployment Project in Visual Studio to view the Properties of the project, and set the PostBuildEvent to this:
cscript.exe "$(ProjectDir)CommandLineSupport.js" "$(BuiltOuputPath)"
Then set up the Delopyment Project with a Custom Action. Click on the Primary Output to get to the Custom Action Properties, and set the CustomActionData field to /MYPROPERTY="[MYPROPERTY]"
You can then access that property in your Custom Action installer class like this:
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
string the_commandline_property_value = Context.Parameters["MYPROPERTY"].ToString();
}
In the end you can run the cmd. C:\>Setup.msi MYPROPERTY=VALUE
This doesn't require any messing about in Orca, or using any custom dialog controls like in the accepted answer. You don't have to modify the PostBuildEvent to have the correct .msi name either. Etc. Also can add as many properties as you wish like this:
INSERT INTO `Property` (`Property`, `Value`) VALUES ('MYPROPERTY', 'MYPROPERTY=\"\"'),('MYPROPERTY2', 'MYPROPERTY2=\"\"', ('MYPROPERTY3', 'MYPROPERTY3=\"\"')) ";
Have fun!

Alright, so I ended up going with the solution I linked to in the question. But let me put the script here for completeness. The first thing I needed to do was build a JS file that had the following code (I named it CommandLineSupport.js) and put it in the same directory as the .vdproj.
//This script adds command-line support for MSI build with Visual Studio 2008.
var msiOpenDatabaseModeTransact = 1;
if (WScript.Arguments.Length != 1)
{
WScript.StdErr.WriteLine(WScript.ScriptName + " file");
WScript.Quit(1);
}
WScript.Echo(WScript.Arguments(0));
var filespec = WScript.Arguments(0);
var installer = WScript.CreateObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
var sql
var view
try
{
//Update InstallUISequence to support command-line parameters in interactive mode.
sql = "UPDATE InstallUISequence SET Condition = 'QUEUEDIRECTORY=\"\"' WHERE Action = 'CustomTextA_SetProperty_EDIT1'";
view = database.OpenView(sql);
view.Execute();
view.Close();
//Update InstallExecuteSequence to support command line in passive or quiet mode.
sql = "UPDATE InstallExecuteSequence SET Condition = 'QUEUEDIRECTORY=\"\"' WHERE Action = 'CustomTextA_SetProperty_EDIT1'";
view = database.OpenView(sql);
view.Execute();
view.Close();
database.Commit();
}
catch(e)
{
WScript.StdErr.WriteLine(e);
WScript.Quit(1);
}
You of course would need to ensure that you're replacing the right Action by opening the MSI in Orca and matching that up to the Property on the custom dialog you created.
Next, now that I had the JS file working, I needed to add a PostBuildEvent to the .vdproj and you can do that by clicking on the setup project in Visual Studio and hitting F4. Then find the PostBuildEvent property and click the elipses. In that PostBuildEvent place this code:
cscript "$(ProjectDir)CommandLineSupport.js" "$(BuildOutputPath)Setup.msi"
Making sure to replace Setup.msi with the name of your MSI file.
Though I still feel like it's a hack ... because it is ... it works and will do the job for now. It's a small enough project that it's really not a big deal.

This is an old thread, but there's a simpler, working solution that still seems hard to find, hence why I'm posting it here.
In my scenario we're working with VS2013 (Community Ed.) and VS 2013 Installer Project extension. Our installer project has a custom UI step collecting two user texts and a custom action bound to Install\Start step that receives those texts.
We were able to make this work from GUI setup wizard, but not from command line. In the end, following this workaround, we were able to make also command line work, without any MSI file Orca editing.
The gist of the thing was to set a value for all needed custom dialog properties directly from Visual Studio, and such value should be in the form [YOUR_DIALOG_PROPERTY_NAME]. Also, it seems like such "public" properties must be named in all caps.
Here's the final setup:
Custom dialog properties
Note e.g. Edit1Property and Edit1Value.
Custom action properties
Note as property key used later in code can be named in camel case.
Custom action code
string companyId = Context.Parameters["companyId"];
string companyApiKey = Context.Parameters["companyApiKey"];
Command line
> setup.exe COMPANYID="Some ID" COMPANYAPIKEY="Some KEY" /q /l mylog.txt
HTH

Related

Using WixSharp how to get the proper InstallDir path during a Custom Action which is executed at uninstall

I created a msi Setup with WiX using WixSharp. It includes several Custom Actions.
For instance during installation time I am executing some batch files which are installing and starting a service. And during uninstall it should stop and uninstall the service again.
var dir = new InstallDir(#"%ProgramFiles%\MyCompany\MyProduct",
new Files(#"..\..\..\AllMyFiles\*.*"));
var project = new Project("MyProduct", dir) {
GUID = new Guid("7f22db65-2b23-4df2-b2b2-495f2d369c3d"),
Version = new Version(1, 0, 0, 0),
UI = WUI.WixUI_InstallDir,
Platform = Platform.x64
};
project.Actions = new WixSharp.Action[] {
new ElevatedManagedAction(CustomActions.InstallService,Return.check, When.Before, Step.InstallFinalize, Condition.NOT_Installed),
new ElevatedManagedAction(CustomActions.StartService,Return.check, When.After, Step.PreviousAction, Condition.NOT_Installed),
new ElevatedManagedAction(CustomActions.StopService,Return.check, When.Before, Step.RemoveFiles, Condition.Installed),
new ElevatedManagedAction(CustomActions.UninstallService,Return.check, When.After, Step.PreviousAction, Condition.Installed)
};
Now here comes the crucial part. I need to execute a batch file during install and uninstall which is located somewhere in INSTALLDIR:
[CustomAction]
public static ActionResult StartService(Session session) {
string installDir = session.Property("INSTALLDIR"); //<--this works on install even when using a custom path
string workingDir = Path.Combine(installDir, #"\SomePathToTheBatchFile");
RunCmdMethode(workingDir, "something.bat -some arguments");
return ActionResult.Success;
}
[CustomAction]
public static ActionResult UninstallService(Session session) {
string installDir = session.Property("INSTALLDIR"); //<--this does not give back the right path on uninstall in case the default path was changed during installation
string workingDir = Path.Combine(installDir, #"\SomePathToTheBatchFile");
RunCmdMethode(workingDir, "something.bat -some arguments");
return ActionResult.Success;
}
Everything runs smoothly when using the default path for installation. But if I change the default install path during installation to some custom path the installation step properly finds the .bat and executes it but during uninstall it searches for the .bat file in the default folder. Although the Uninstaller properly removes the files on the correct location. So the custom install path must be saved somewhere. How do I access it properly?
I finally could solve the problem by myself and with some help of Oleg (https://github.com/oleg-shilo/wixsharp/issues/486).
Since session.Property("INSTALLDIR") should actually work and so I did not make a mistake at that point, I could figure out the root cause, which is setting the IsInstallDir property to true by using the InstallDir class instead of the Dir class. It overwrote the INSTALLDIR property when uninstalling back to the hard coded default path.
This explains why the setup worked fine as long as using the default path and also why it worked for all the install-custom steps even when using a custom path but not for uninstalling anymore. The reason tho, why I set the IsInstallDir property to true in the first place is because of some weird behavior when adding all the files to the setup using wildcards. As long as there are multiple files and folders in the source directory it would work just as expected, getting all paths right and so on. But once the source folder only contains a single folder inside which then contains the rest of the setup files within, it sets the inner folder to be the new root folder (kinda strange but once you know about this behavior things start making sense) and so screws up many necessary paths. Using InstallDir instead of Dir fixed that.
I might put some work into restructuring the whole thing (if this is even possible in my use case), but for now simply adding a readme file on the same level as the single inner folder solves that problem and that way I could go back using Dir in the first line:
var dir = new Dir(#"%ProgramFiles%\MyCompany\MyProduct",
new Files(#"..\..\..\AllMyFiles\*.*"));
That happens because you call the action "after" the uninstall. it should be "When.Before"
new ManagedAction(CustomActions.UninstallService,Return.check, When.Before, Step.InstallFinalize, Condition.Installed)

Disable Wix Custom action on /quiet /silent

In a WIX custom action, is there a way to detect whether the MSI was invoked with /silent or /quiet command line switches? Basically what I want is to not execute the custom action (since it shows a form) or handle it differently if these command line switches were passed but I am unable to find this out.
Is there a way to possibly detect it?
You can check the property UILevel and execute your CA based on your conditions.
I finally figured it out. Wix basically always sets the UILevel property to 2.0. It has its own property called WixBundleUILevel. Now important thing here is that prior to Wix 3.11 this WixBundleUILevel was an internal property and was not accessible to Bundle projects or MSI Custom Actions. So here is what I did
Defined a property in MSI called UI_LEVEL (important, make it all upper case)
In Bundle.wxs, right where I call MSIPackage, I set the UI_LEVEL property like so
<MsiPackage SourceFile="$(var.MsiPath)">
<MsiProperty Name="UI_LEVEL" Value="[WixBundleUILevel]" />
</MsiPackage>
Then finally in the custom action I check for this property like
int uiLevel;
if (int.TryParse(session["UI_LEVEL"], out uiLevel))
{
if (uiLevel == 4)
using (var form = new WhatsNew())
{
form.ShowDialog();
}
else
session.Log("Skipping What's new dialogue as UI Level is not 4");
}
else
{
session.Log("Couldnt figure out the UI level, so skipped the prompt");
}
And finally
here are the possible values of this f**ed up property
WixBundleUILevel Value Burn parameters
BOOTSTRAPPER_DISPLAY_FULL 4 (none)
BOOTSTRAPPER_DISPLAY_PASSIVE 3 /silent
BOOTSTRAPPER_DISPLAY_NONE 2 /quiet

Use value dialog installation in windows service c#

We are develop a windows service for open a specific port.
Now this port can be custom for the user during the installation in a dialog.
I want know a possibility of capture this value and pass to the code of the service
if (myServer == null)
{
int port= int.Parse(ConfigurationManager.AppSettings["port1"]);
myServer = new NHttp.HttpServer
{
EndPoint = new System.Net.IPEndPoint(0, port)
};
}
myServer.Start();
I try using a value in app.config and editing this value in the installer:
public override void Install(System.Collections.IDictionary stateSaver)
{
string portServer= this.Context.Parameters["CTPUERTO"];
System.Configuration.ConfigurationManager.AppSettings.Set("port1", portServer);
base.Install(stateSaver);
}
CTPUERTO is the name of the textbox in the dialog install
You add the optional TextBoxes(A) dialog to your setup project and the user enters that text (in EDITA1 in the docs):
https://msdn.microsoft.com/en-us/library/e04k6f53(v=vs.100).aspx
Then in your custom action you'd add the parameter with something like:
/port1=[EDITA1]
in CustomActionData, then access it using the kind of code you showed, in an installer class.
These might be useful:
.net Setup Project: How to pass multiple CustomActionData fields
https://www.codeproject.com/Articles/12780/A-Setup-and-Deployment-project-that-passes-paramet
The main issue with this (because of the way VS setup projects work) is that you cannot validate it at the time it's entered. Custom actions in VS setup projects run after the UI and after everything is installed, so if your custom finds it's incorrect then you fail the whole install and roll back.

Extension for Specflow in visual studio

I have a set of tests using specflow on Visual Studio, some of them have steps that looks like:
Given the data in file /foo/bar/data.txt
I would like to implement a Visual Studio extension so I can click on /foo/bar/data.txt and get the file opened.
I had a vague idea of using something like a Visual Studio text adorn, but I really don't know if there is a simpler way. Moreover, I'm looking for a solution that works in Visual Studio 2013 and above, and adorns are not supported in older versions as far as I know. Any ideas?
If you prefix the file path with file:// it will become a clickable link. Replace spaces with "%20" as you would with any URL.
I know that's not the answer to the question you are asking, but maybe you don't need to implement an extension?
One possible solution is to create a new menu entry with a Visual Studio Add-in, this way when you click in a line, and choose this menu option, you can execute an action (read and parse the line and open the file). This can be done as follows:
File->New Project->Other Types -> Extensibility -> Visual Studio Add-in, and the implement IDTCommandTarget
Commands2 commands = (Commands2)_applicationObject.Commands;
object[] contextGUIDS = new object[] { };
CommandBars cmdBars = (CommandBars)(_applicationObject.CommandBars);
CommandBar vsBarProject = cmdBars["Code Window"];
scenarioCommand = commands.AddNamedCommand2(_addInInstance, "OpenScenario", "Open scenario", "Open scenario data", true);
scenarioCommand.AddControl(vsBarProject);
Then in the Exec method, just read the line, get the file path, and:
Process.Start(resource)
And of course, just show the menu option if is a specFlow file in the QueryStatus method:
dynamic docName = _applicationObject.ActiveDocument.FullName;
if (CmdName == OpenScenarioCmd && !((string)docName).EndsWith(".feature"))
{
StatusOption = (vsCommandStatus)vsCommandStatus.vsCommandStatusInvisible;
}
else if (CmdName == OpenScenarioCmd)
{
StatusOption = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
}
It's not perfect, because you have to show a menu, but it works.

Pass value from command line to Custom action-Burn

I am in a need to pass the value from command line to custom action.The custom action should get the value from command line and modify one file(app.config) during installation of EXE.I have the below code in custom action
if (Context.Parameters["ENV"] == "test") //Test environment
{
authEndPoint = "http://192.168.168.4/api/user_authentication/";
}
else if (Context.Parameters["ENV"] == " ") //Production environment
{
authEndPoint = "https://livesiteurl/api/user_authentication/";
}
I want to install the exe file like below
myapplication.exe env=test
I seen lot of sample to pass the value from command line to msi. How to pass the value from command line to CA and modify the app.config file.
There are better ways to do what you're trying to do here than use a custom action. Take a look at this for how use the WiXUtilExtension to modify the file, then create a property and reference it from the command line. If you still need/want to use a bootstrapper you can set that property you created with MsiProperty inside MsiPackage in the bundle.

Categories