Installing multiple instances of the same windows service on a server - windows

So we've produced a windows service to feed data to our client application and everything is going great. The client has come up with a fun configuration request that requires two instances of this service running on the same server and configured to point at separate databases.
So far I haven't been able to get this to happen and was hoping my fellow stackoverflow members might be able to give some hints as to why.
Current setup:
I've set up the project that contains the windows service, we'll call it AppService from now on, and the ProjectInstaller.cs file that handles custom installation steps to set the service name based on a key in the App.config like so:
this.serviceInstaller1.ServiceName = Util.ServiceName;
this.serviceInstaller1.DisplayName = Util.ServiceName;
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
In this case Util is just a static class tha tloads the service name from the config file.
From here forward I have tried two different ways to get both services installed and both have failed in an identical way.
The first way was to simply install the first copy of the service, copy the installed directory and renamed it, and then ran the following command after modifying the app config to change the desired service name:
InstallUtil.exe /i AppService.exe
When that didn't work I tried to create a second installer project, edited the config file and built the second installer. When I ran the installer it worked fine but the service did not show up in services.msc so I ran the previous command against the second installed code base.
Both times i received the following output from InstallUtil (relevant parts only):
Running a transacted installation.
Beginning the Install phase of the installation.
Installing service App Service Two...
Service App Service Two has been successfully installed.
Creating EventLog source App Service Two in log Application...
An exception occurred during the Install phase.
System.NullReferenceException: Object reference not set to an instance of an object.
The Rollback phase of the installation is beginning.
Restoring event log to previous state for source App Service Two.
Service App Service Two is being removed from the system...
Service App Service Two was successfully removed from the system.
The Rollback phase completed successfully.
The transacted install has completed.
The installation failed, and the rollback has been performed.
Sorry for the long winded post, wanted to make sure there is enough relevant information. The piece that so far has me stumped is that it states that the installation of the service completes successfully and its only after it goes to create the EventLog source that the NullReferenceException seems to get thrown. So if anyone knows what I'm doing wrong or has a better approach it would be much appreciated.

Have you tried the sc / service controller util? Type
sc create
at a command line, and it will give you the help entry. I think I've done this in the past for Subversion and used this article as a reference:
http://svn.apache.org/repos/asf/subversion/trunk/notes/windows-service.txt

sc create [servicename] binpath= [path to your exe]
This solution worked for me.

You can run multiple versions of the same service by doing the following:
1) Copy the Service executable and config to its own folder.
2) Copy Install.Exe to the service executable folder (from .net framework folder)
3) Create a config file called Install.exe.config in the service executable folder
with the following contents (unique service names):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="ServiceName" value="The Service Name"/>
<add key="DisplayName" value="The Service Display Name"/>
</appSettings>
</configuration>
4) Create a batch file to install the service with the following contents:
REM Install
InstallUtil.exe YourService.exe
pause
5) While your there, create an uninstall batch file
REM Uninstall
InstallUtil.exe -u YourService.exe
pause
EDIT:
Note sure if I missed something, here is the ServiceInstaller Class (adjust as required):
using System.Configuration;
namespace Made4Print
{
partial class ServiceInstaller
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
private System.ServiceProcess.ServiceInstaller FileProcessingServiceInstaller;
private System.ServiceProcess.ServiceProcessInstaller FileProcessingServiceProcessInstaller;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.FileProcessingServiceInstaller = new System.ServiceProcess.ServiceInstaller();
this.FileProcessingServiceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller();
//
// FileProcessingServiceInstaller
//
this.FileProcessingServiceInstaller.ServiceName = ServiceName;
this.FileProcessingServiceInstaller.DisplayName = DisplayName;
//
// FileProcessingServiceProcessInstaller
//
this.FileProcessingServiceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
this.FileProcessingServiceProcessInstaller.Password = null;
this.FileProcessingServiceProcessInstaller.Username = null;
//
// ServiceInstaller
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] { this.FileProcessingServiceInstaller, this.FileProcessingServiceProcessInstaller });
}
#endregion
private string ServiceName
{
get
{
return (ConfigurationManager.AppSettings["ServiceName"] == null ? "Made4PrintFileProcessingService" : ConfigurationManager.AppSettings["ServiceName"].ToString());
}
}
private string DisplayName
{
get
{
return (ConfigurationManager.AppSettings["DisplayName"] == null ? "Made4Print File Processing Service" : ConfigurationManager.AppSettings["DisplayName"].ToString());
}
}
}
}

Another quick way to specify a custom value for ServiceName and DisplayName is using installutil command line parameters.
In your ProjectInstaller class override virtual methods Install(IDictionary stateSaver) and Uninstall(IDictionary savedState)
public override void Install(System.Collections.IDictionary stateSaver)
{
GetCustomServiceName();
base.Install(stateSaver);
}
public override void Uninstall(System.Collections.IDictionary savedState)
{
GetCustomServiceName();
base.Uninstall(savedState);
}
//Retrieve custom service name from installutil command line parameters
private void GetCustomServiceName()
{
string customServiceName = Context.Parameters["servicename"];
if (!string.IsNullOrEmpty(customServiceName))
{
serviceInstaller1.ServiceName = customServiceName;
serviceInstaller1.DisplayName = customServiceName;
}
}
Build your project
Install the service with installutil adding your custom name using /servicename parameter:
installutil.exe /servicename="CustomServiceName" "c:\pathToService\SrvcExecutable.exe"
Please note that if you do not specify /servicename in the command line the service will be installed with ServiceName and DisplayName values specified in ProjectInstaller properties/config

Old question, I know, but I've had luck using the /servicename option on InstallUtil.exe. I don't see it listed in the built-in help though.
InstallUtil.exe /servicename="My Service" MyService.exe
I'm not entirely sure where I first read about this but I haven't seen it since. YMMV.

I didn't have much luck with the above methods when using our automated deployment software to frequently install/uninstall side-by-side windows services, but I eventually came up with the following which allows me to pass in a parameter to specify a suffix to the service name on the command line. It also allows the designer to function properly and could easily be adapted to override the entire name if necessary.
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
protected override void OnBeforeInstall(IDictionary savedState)
{
base.OnBeforeInstall(savedState);
SetNames();
}
protected override void OnBeforeUninstall(IDictionary savedState)
{
base.OnBeforeUninstall(savedState);
SetNames();
}
private void SetNames()
{
this.serviceInstaller1.DisplayName = AddSuffix(this.serviceInstaller1.DisplayName);
this.serviceInstaller1.ServiceName = AddSuffix(this.serviceInstaller1.ServiceName);
}
private string AddSuffix(string originalName)
{
if (!String.IsNullOrWhiteSpace(this.Context.Parameters["ServiceSuffix"]))
return originalName + " - " + this.Context.Parameters["ServiceSuffix"];
else
return originalName;
}
}
With this in mind, I can do the following:
If I've called the service "Awesome Service" then I can install a UAT verison of the service as follows:
InstallUtil.exe /ServiceSuffix="UAT" MyService.exe
This will create the service with the name "Awesome Service - UAT". We've used this to run DEVINT, TESTING and ACCEPTANCE versions of the same service running side-by-side on a single machine. Each version has its own set of files/configs - I haven't tried this to install multiple services pointing at the same set of files.
NOTE: you have to use the same /ServiceSuffix parameter to uninstall the service, so you'd execute the following to uninstall:
InstallUtil.exe /u /ServiceSuffix="UAT" MyService.exe

What I've done to make this work is to store the service name and display name in an app.config for my service. Then in my installer class, I load the app.config as an XmlDocument and use xpath to get the values out and apply them to ServiceInstaller.ServiceName and ServiceInstaller.DisplayName, before calling InitializeComponent(). This assumes you're not already setting these properties in InitializeComponent(), in which case, the settings from your config file will be ignored. The following code is what I'm calling from my installer class constructor, before InitializeComponent():
private void SetServiceName()
{
string configurationFilePath = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, "exe.config");
XmlDocument doc = new XmlDocument();
doc.Load(configurationFilePath);
XmlNode serviceName = doc.SelectSingleNode("/xpath/to/your/#serviceName");
XmlNode displayName = doc.SelectSingleNode("/xpath/to/your/#displayName");
if (serviceName != null && !string.IsNullOrEmpty(serviceName.Value))
{
this.serviceInstaller.ServiceName = serviceName.Value;
}
if (displayName != null && !string.IsNullOrEmpty(displayName.Value))
{
this.serviceInstaller.DisplayName = displayName.Value;
}
}
I don't believe reading the configuration file directly from ConfigurationManager.AppSettings or something similar will work as when the installer runs, it's run in the context of InstallUtil.exe, not your service's .exe. You may be able to do something with ConfigurationManager.OpenExeConfiguration, however in my case, this didn't work as I was trying to get at a custom configuration section that was not loaded.

Just to improve perfect answer of #chris.house.00 this, you can consider following function to read from your app settings:
public void GetServiceAndDisplayName(out string serviceNameVar, out string displayNameVar)
{
string configurationFilePath = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, "exe.config");
XmlDocument doc = new XmlDocument();
doc.Load(configurationFilePath);
XmlNode serviceName = doc.SelectSingleNode("//appSettings//add[#key='ServiceName']");
XmlNode displayName = doc.SelectSingleNode("//appSettings//add[#key='DisplayName']");
if (serviceName != null && (serviceName.Attributes != null && (serviceName.Attributes["value"] != null)))
{
serviceNameVar = serviceName.Attributes["value"].Value;
}
else
{
serviceNameVar = "Custom.Service.Name";
}
if (displayName != null && (displayName.Attributes != null && (displayName.Attributes["value"] != null)))
{
displayNameVar = displayName.Attributes["value"].Value;
}
else
{
displayNameVar = "Custom.Service.DisplayName";
}
}

I had a similar situation, where i to needed have a previous service, and an updated service running side by side on the same server. (It was more than just a database change, it was code changes as well). So I couldn't just run the same .exe twice. I needed a new .exe that was compiled with new DLLs but from the same project. Just changing the service name and display name of the service did not work for me, I still received the "service already existed error" which I believe is because I am using a Deployment Project. What finally did work for me is within my Deployment Project Properties there is a property called "ProductCode" which is a Guid.
After that, rebuilding the Setup Project to a new .exe or .msi installed successfully.

The simplest approach is is based the service name on the dll name:
string sAssPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string sAssName = System.IO.Path.GetFileNameWithoutExtension(sAssPath);
if ((this.ServiceInstaller1.ServiceName != sAssName)) {
this.ServiceInstaller1.ServiceName = sAssName;
this.ServiceInstaller1.DisplayName = sAssName;
}

Related

File being used by another process in a Windows Service with a StreamReader

I have searched numerous articles and tried several different ways of solving this issue.
Note that this process works fine in a Console app or a Windows app with the exact same code. So, there's nothing wrong with the logic. It's the way the Windows Service is processing these files that is somehow not letting subsequent files get through.
I tried reading a simple article to actually debug the service, but I don't have the "Attach To Process" selection under my Debug menu using Visual Studio 2010.
http://msdn.microsoft.com/en-us/library/7a50syb3(v=vs.100).aspx
I really need some help here, because my options have really run out.
Here is the simple scenario:
I have a FileSystemWatcher in the OnStart method that kicks off another method to process a file on my local machine. These are just simple txt files. When the first file is processed, everything is normal and I get the following in my Event log:
Data successfully inserted for file 20121212_AZM_Journey_MIS.txt
When I try and process subsequent files (exact same format, but a different name --- with the date), I get the following in my Event log:
The process cannot access the file 'C:\Projects\Data\VendingStats\20121213_AZM_Journey_MIS.txt' because it is being used by another process.
Note that the files are watched for in this same directory when created.
Here is my code in my Windows Service. Can someone please let me know how I can solve this issue? Note that e.FullPath evaluates to "C:\Projects\Data\VendingStats" on my local machine.
public partial class VendingStatsService : ServiceBase
{
public VendingStatsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
FileSystemWatcher Watcher = new FileSystemWatcher(#"C:\Projects\Data\VendingStats");
Watcher.EnableRaisingEvents = true;
Watcher.Created += new FileSystemEventHandler(**Watcher_Created**);
Watcher.Filter = "*.txt";
Watcher.IncludeSubdirectories = false;
}
private void Watcher_Created(object sender, FileSystemEventArgs e)
{
try
{
using (TextReader tr = new StreamReader(e.FullPath))
{
// code here to insert data from file into a DB
}
Dispose();
}
catch (Exception ex)
{
EventLog.WriteEntry("VendorStats", "Error in the Main:" + "\r\n\r\n" + ex.Message + "\r\n\r\n" + ex.InnerException);
return;
}
}
public void Dispose()
{
GC.Collect();
}

Entity Framework 4.3.1 add-migration error: "model backing the context has changed"

I'm getting an error when trying to run the EF 4.3.1 add-migrations command:
"The model backing the ... context has changed since the database was created".
Here's one sequence that gets the error (although I've tried probably a dozen variants which also all fail)...
1) Start with a database that was created by EF Code First (ie, already contains a _MigrationHistory table with only the InitialCreate row).
2) The app's code data model and database are in-sync at this point (the database was created by CF when the app was started).
3) Because I have four DBContexts in my "Services" project, I didn't run 'enable-migrations' command (it doesn't handle multipe contexts). Instead, I manually created the Migrations folder in the Services project and the Configuration.cs file (included at end of this post). [I think I read this in a post somewhere]
4) With the database not yet changed, and the app stopped, I use the VS EDM editor to make a trivial change to my data model (add one property to an existing entity), and have it generate the new classes (but not modify the database, obviously). I then rebuild the solution and all looks OK (but don't delete the database or restart the app, of course).
5) I run the following PMC command (where "App" is the name of one of the classes in Configuration.cs):
PM> add-migration App_AddTrivial -conf App -project Services -startup Services -verbose
... which fails with the "The model ... has changed. Consider using Code First Migrations..." error.
What am I doing wrong? And does anyone else see the irony in the tool telling me to use what I'm already trying to use ;-)
What are the correct steps for setting-up a solution starting with a database that was created by EF CF? I've seen posts saying to run an initial migration with -ignorechanges, but I've tried that and it doesn't help. Actually, I've spent all DAY testing various permutations, and nothing works!
I must be doing something really stupid, but I don't know what!
Thanks,
DadCat
Configuration.cs:
namespace mynamespace
{
internal sealed class App : DbMigrationsConfiguration
{
public App()
{
AutomaticMigrationsEnabled = false;
MigrationsNamespace = "Services.App.Repository.Migrations";
}
protected override void Seed(.Services.App.Repository.ModelContainer context)
{
}
}
internal sealed class Catalog : DbMigrationsConfiguration<Services.Catalog.Repository.ModelContainer>
{
public Catalog()
{
AutomaticMigrationsEnabled = false;
MigrationsNamespace = "Services.Catalog.Repository.Migrations";
}
protected override void Seed(Services.Catalog.Repository.ModelContainer context)
{
}
}
internal sealed class Portfolio : DbMigrationsConfiguration<Services.PortfolioManagement.Repository.ModelContainer>
{
public Portfolio()
{
AutomaticMigrationsEnabled = false;
MigrationsNamespace = "Services.PortfolioManagement.Repository.Migrations";
}
protected override void Seed(Services.PortfolioManagement.Repository.ModelContainer context)
{
}
}
internal sealed class Scheduler : DbMigrationsConfiguration<.Services.Scheduler.Repository.ModelContainer>
{
public Scheduler()
{
AutomaticMigrationsEnabled = false;
MigrationsNamespace = "Services.Scheduler.Repository.Migrations";
}
protected override void Seed(Services.Scheduler.Repository.ModelContainer context)
{
}
}
}
When using EF Migrations you should have one data context per database. I know that it can grow really large, but by trying to split it you will run into several problems. One is the migration issue that you are experiencing. Later on you will probably be facing problems when trying to make queries joining tables from the different contexts. Don't go that way, it's against how EF is designed.

How to debug Windows Service Applications?

I'm creating a Windows Service Application in Visual Studio 2010 Ultimate SP1.
I was following 'How to' from MSDN:
http://msdn.microsoft.com/en-us/library/7a50syb3.aspx
I have encountered two problems:
I cannot start a service via Server Explorer - my service is listed there, but in the context menu I have only two options available: Refresh and Properties. There is no "Start" though MSDN documentation says that there should be that option.
Fortunately, I can avoid this hassle by using Services Control Manager.
The next step is: "In Visual Studio, choose Processes from the Debug menu".
That option doesn't exist in Debug menu. I have only "Attach to Process", but services aren't listed there.
Does somebody know what is wrong and how I am supposed to debug my application?
Thank you in advance.
As a thought: I have built a lot of Windows services and for one of many reasons, I do not create the core code in the service itself. The service is essentially the "operational layer", if you will. Creating the core code in a dll permits debugging and testing of that particular code. You can create a console or desktop app that will run the core code which can be used during development and testing phases.
Personally, I created a service runner application which captures logging in conjunction with the start and stop functionality. My OnStart and OnStop code blocks are literally identical to that of the service.
Next, when you test the service, you should be able to start the service (e.g. myService.exe) and attach to process. However, another note is that you should pause/wait the service thread (for say 30 seconds) with a debug build so you have time to attach to the process and you don't miss your initialization code. Just remember, you have to install your service then start via the Windows service manager.
Here is some code you that might point you in the direction that I use. In the service program.cs file I use the below; then in the Service OnStart() method you call your dll and run. Also, you can stop your service, replace the dll with an updated version then restart. With C# you can replace the service exe as well, but these are only C# characteristics: in C++ you cannot.
static class Program
{
public const string SERVICE_NAME = "myService";
public const string SERVICE_DISPLAY_NAME = "My Service";
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(string[] args)
{
if (args != null && args.Length > 0)
{
foreach (string arg in args)
{
switch (arg.ToLower())
{
case "-install":
ManageService(true);
return;
case "-remove":
ManageService(false);
return;
}
}
}
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service()
};
ServiceBase.Run(ServicesToRun);
}
private static void ManageService(bool bInstall)
{
string parms;
if (bInstall == true)
{
parms = string.Format("Create {0} type= own start= demand binPath= \"{1}\" DisplayName= \"{2}\"", SERVICE_NAME,
System.Reflection.Assembly.GetExecutingAssembly().Location, SERVICE_DISPLAY_NAME);
}
else // remove
{
parms = string.Format("Delete {0}", SERVICE_NAME);
}
try
{
string output = string.Empty;
System.Diagnostics.Process proc = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo("sc.exe", parms);
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
startInfo.CreateNoWindow = true;
proc.StartInfo = startInfo;
proc.Start();
output = proc.StandardOutput.ReadToEnd();
proc.WaitForExit(10000);
if (proc.HasExited == true)
{
// NOTE: The project type has been changed from Windows Service to Console Application
// so that Console.WriteLine will output to the console
Console.WriteLine(output);
}
else
{
proc.Close();
Console.WriteLine("Timed out waiting to install service");
}
}
catch (System.ComponentModel.Win32Exception)
{
Console.WriteLine("Unable to locate sc.exe");
}
}
}
//From the main function a method from service class can be called like below code
//DebugApp method can be called from main and so the service can be debug:
//Service class
public partial class CServices : ServiceBase
{
public CServices()
{
InitializeComponent();
}
**public void DebugApp()
{
OnStart(new string[]{});
}**
protected override void OnStart(string[] args)
{
System.Console.WriteLine("Testing");
System.Console.Read();
}
protected override void OnStop()
{
}
}
//Calling from main:
static void Main()
{
Services1.CServices uc = new CServices();
uc.DebugApp();
}

Dynamics AX 2009: Batch Trouble with AsciiIO class

I have a custom class, we'll call it FileProcessUpload and it extends RunBaseBatch. It more or less creates a CSV file and then uploads it to an FTP server. When the class is run manually, everything works fine. However, when submitted as a Batch Job, there is an error in the infolog stating "AsciiIO object not initialized".
Probably the most important thing to note here is that this Batch Job is being delegated to a different AOS.
Here is a cropped down version of the offending code:
void CreateFiles()
{
#File
AsciiIO asciiio;
FileIOPermission permission;
ATable aTable;
str outputFile;
str directory;
;
directory = #'C:\Uploads';
ouptutFile = directory + #'\output.csv';
if (!WinAPI::folderExists(directory))
{
WinAPI::createDirectory(directory);
}
// Try to assert the appropriate file access mode
permission = new FileIOPermission(outputFile, #io_write);
permission.assert();
// Try to open the file for writing
asciiio = new AsciiIO(outputFile, #io_write);
if (asciiio != null)
{
while select aTable
{
// Write the necessary lines into the file
asciiio.write(aTable.field1 + ',' + aTable.field2);
}
}
else
{
error('Could not create file: ' + outputFile);
}
// Close file and release permission assertion
asciiio = null;
CodeAccessPermission::revertAssert();
}
Does the service user that Ax is running under have permissions to read/write the file?
You are using the WinAPI class, but should you be using WinAPIServer class instead? You may be executing on the server of course.
Do you need to add to your class the following public boolean runsImpersonated() { return false; } and run this class on a client?
Good luck
Edit: Executing your code via the server static void mainOnServer(Args args) method signature is commonly used (see PurchFormLetter class for it's usage) to make sure that you execute on the server. It is called from static void main(Args args)
Use file path and file name instead of str as directory and name
If runbasebatch then should put pack/uppack filePath and fileName and put it into currentVersion control at classdeclaration.
If you move/delete/encrytion/read file, using system.io.file /system.io.stream, or streamreader, or system.net.ftpwebrequest, and system.net.ftpwebresponse, remember to run on server static void method for this...
Any file format I have done, txt, .csv, .gpg, I move around file easily in/out ax to other server, no problem to just write a file inside of AX by fellowing the above rule..

Renaming config files with Visual Studio setup project

My applications have a handful of config files, each one particular to the machine it's being deployed to (dev, production, etc.) I'm wanting to add them to the setup project, use radio buttons to select one and then have a script (or maybe custom action?) rename the selected files to connectionStrings.config, settings.config, etc.
Is this feasible/possible with a setup project?
To give you an idea, my configs might look like this:
DEV connectionStrings.config
PROD connectionStrings.config
After the user chooses DEV or PROD in the installer radiobutton UI, I would like the chosen config to be renamed to
connectionStrings.config
Considering it's a VS setup project, I have a feeling I'm asking for way too much and that I will get an interesting response as most setup project questions do :)
I created a setup project to set connection strings and i used the following which works perfectly for me.
Create a installer.cs file for the setup.
using System;
using System.Data.SqlClient;
using System.Xml;
using System.Configuration;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;
using System.Configuration.Install;
namespace YOURNAMESPACE
{
[RunInstaller(true)]
public partial class installer : System.Configuration.Install.Installer
{
public installer()
{
InitializeComponent();
}
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
}
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
try
{
string DatabaseString1 = "FULL NAME OF CONNECTION STRING";
ConnectionConfigure(DatabaseString1);
}
catch (Exception e)
{
base.Rollback(savedState);
}
}
public override void Rollback(IDictionary savedState)
{
base.Rollback(savedState);
}
public override void Uninstall(IDictionary savedState)
{
base.Uninstall(savedState);
}
private void ConnectionConfigure(string DatabaseString)
{
string dataSource = "";
dataSource = "Provider="+ Context.Parameters["InitialCatalog"]+ ";" + "Data Source=" + Context.Parameters["DataSource"];
ExeConfigurationFileMap map = new ExeConfigurationFileMap();
string configFile = string.Concat(Assembly.GetExecutingAssembly().Location, ".config");
map.ExeConfigFilename = configFile;
System.Configuration.Configuration config = System.Configuration.ConfigurationManager.
OpenMappedExeConfiguration(map, System.Configuration.ConfigurationUserLevel.None);
string connectionsection = config.ConnectionStrings.ConnectionStrings
[DatabaseString].ConnectionString;
ConnectionStringSettings connectionstring = null;
if (connectionsection != null)
{
config.ConnectionStrings.ConnectionStrings.Remove(DatabaseString);
}
connectionstring = new ConnectionStringSettings(DatabaseString, dataSource);
config.ConnectionStrings.ConnectionStrings.Add(connectionstring);
config.Save(ConfigurationSaveMode.Modified, true);
ConfigurationManager.RefreshSection("connectionStrings");
}
}
}
Add project output to your setup project then begin to setup this setup project.
Right click setup project
Add text boxes and create the UI
Set custom actions
Create project output actions
Custom action properties
That is how i setup mine (I have attached screenshots to help explain my process but in short. Create setup project and installer.cs file. Add project output to setup project, add a UI so that the user can input a connection string and or provider for connection string, add custom actions so that the inputs can be read by the installer.cs file and then congratulations it should change the connection string.
Hope this helps.
I've come to the conclusion that this is impossible due to VS severely lacking on setup project configuration options. Instead I added a radio button control during the setup process and assigned the choices a variable name. In the file system I added all of my config files and then set conditions to each one. They referenced values to my radio button choices in order to copy during deployment.
I've done this many times, and basically I just install both files with different names. The application can ask the user which file to use, and change it anytime they want because many users don't know enough about the application to make this choice at install time.
You get interesting answers to setup questions like this because many people want to configure the application during the installation. Why not just let the setup install the files and have the app do its own configuration?

Resources