How can I dynamically change a connectionString in the app.config file?
I have an application written with windows forms, c# 3.0 and Linq to Sql. I need to change the connection string when i install the application. How i do this?
When the user installs the program it must show a form with an option to change the connection string if exists or add one if it doesn't.
If you are using a .NET deployment project, can achieve this by using Custom Actions.
Write a secondary config file with an appSettings block using the settings from the installer. In your main config file, use the file attribute in appSettings to reference the second config file, like so:
<appSettings file="User.config">
Settings in the secondary config will override any matching keys in the main config.
In your installer:
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
string server = Context.Parameters["Server"];
string port = Context.Parameters["Port"];
string targetDir = Context.Parameters["TargetDir"];
// Build your connection string from user-input parameters and add them to dictionary
WriteAppConfig(targetDir, server, port);
}
private void WriteAppConfig(string targetDir, string server, string port)
{
string configFilePath = Path.Combine(targetDir, "User.config");
IDictionary<string, string> userConfiguration = new Dictionary<string, string>();
userConfiguration["Server"] = server;
userConfiguration["Port"] = port;
ConfigGenerator.WriteExternalAppConfig(configFilePath, userConfiguration);
}
public class ConfigGenerator
{
public static void WriteExternalAppConfig(string configFilePath, IDictionary<string, string> userConfiguration)
{
using (XmlTextWriter xw = new XmlTextWriter(configFilePath, Encoding.UTF8))
{
xw.Formatting = Formatting.Indented;
xw.Indentation = 4;
xw.WriteStartDocument();
xw.WriteStartElement("appSettings");
foreach (KeyValuePair<string, string> pair in userConfiguration)
{
xw.WriteStartElement("add");
xw.WriteAttributeString("key", pair.Key);
xw.WriteAttributeString("value", pair.Value);
xw.WriteEndElement();
}
xw.WriteEndElement();
xw.WriteEndDocument();
}
}
}
Check out this question. It has what you need to change values in the app.config dynamically through code.
Related
After I disconnect from the vpn using this code System.Diagnostics.Process.Start("rasdial.exe", "My_VPN /d"); It still shows on the VPN Connection list. How can I remove it from there through my program?
If you want to delete VPN connection you need to delete file "rasphone.pbk" or section with [VPN Name] in this file. The file is INI file with extension PBK.
By default the file located in %APPDATA%\Microsoft\Network\Connections\Pbk\rasphone.pbk
After delete operation you will need to restart "explorer.exe"
You can remove it by using WMI's PS_VpnConnection class.
using System.Management; // need to add a reference to the assembly [System.Management]
public class Program
{
public static void Main()
{
const string WMIScope = "root/Microsoft/Windows/RemoteAccess/Client";
const string WMIClass = "PS_VpnConnection";
using (var cls = new ManagementClass(WMIScope, WMIClass, null))
using (var methodParams = cls.GetMethodParameters("Remove"))
{
methodParams["Name"] = new[]{"your_vpn_name"};
methodParams["Force"] = true;
cls.InvokeMethod("Remove", methodParams, null);
}
}
}
I'm creating a setup project for my C# desktop application.
What the data source should be written in the connection string for the access database ?and where I should put my database file in the solution project ?
Assuming you're using the VS setup project, you need to add the access database file as content and place it in the application directory, for example. To specify the location in the configuration file, you need to write a custom action that modifies the connection string accordingly.
The following example is an installer class that sets the connection string after install phase (not tested):
[RunInstaller(true)]
public partial class Installer1 : System.Configuration.Install.Installer
{
public Installer1()
{
InitializeComponent();
this.AfterInstall += new InstallEventHandler(Installer1_AfterInstall);
}
void Installer1_AfterInstall(object sender, InstallEventArgs e)
{
string sTargetDir = Context.Parameters["TargetDir"];
string sAppConfig = Path.Combine(sTargetDir, "<your app>.exe.config");
string sDBPath = Path.Combine(sTargetDir, "<your db>.mdb");
XDocument doc = XDocument.Load(sAppConfig);
var elem = doc.Root.Element("/configuration/connectionStrings/add[#name='<your connection name>']");
string connectionString = String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};", sDBPath);
elem.SetAttributeValue("connectionString", connectionString);
doc.Save(sAppConfig);
}
}
Alternatively, you can use Wix which has the XmlFile utility in the util extension which does it for you without you writing a custom action.
I need to create an address string in app.config as:
<client>
<endpoint address="http://ServerName/xxx/yyy.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IClientIInfoService"
contract="DocuHealthLinkSvcRef.IClientIInfoService" name="BasicHttpBinding_IClientIInfoService" />
</client>
The ServerName need to be entered by the user during installation.
For that i have created a new UI dialog in the Installer. I have also written an Installer.cs class and overrided the install () as:
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
string targetDirectory = Context.Parameters["targetdir"];
string ServerName = Context.Parameters["ServerName"];
System.Diagnostics.Debugger.Break();
string exePath = string.Format("{0}myapp.exe", targetDirectory);
Configuration config = ConfigurationManager.OpenExeConfiguration(exePath);
config.AppSettings.Settings["ServerName"].Value = ServerName;
config.Save();
}
}
But how do i use this ServerName in my app.config to create the specified string.
I'm working on VS2010.
You could use WiX (Windows Installer XML toolset) to build your MSI, in which case you can use the XmlFile utility tag to update the server name:
<util:XmlFile Id="UpdateServerName" File="[INSTALLLOCATION]AppName.exe.config" Action="setValue" ElementPath="/client/endpoint" Name="address" Value="http://[SERVERNAME]/xxx/yyy.svc" />
You can capture the server name during installation using a WixUI extension form.
Advantages of WiX: WiX is msbuild compliant (unlike .vdproj files), and gives you much finer-grained control over your installer, among other things
Assuming you are using the full ServiceModel section group in the app.config
Essentially you follow these steps:
Load ServiceModel config section
Get Client Section
Get ChannelEndpoint Element
Change Address value by replacing string "ServerName" with entered value
Set Address attribute to new value
Save config
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
string targetDirectory = Context.Parameters["targetdir"];
string ServerName = Context.Parameters["ServerName"];
System.Diagnostics.Debugger.Break();
string exePath = string.Format("{0}myapp.exe", targetDirectory);
Configuration config = ConfigurationManager.OpenExeConfiguration(exePath);
config.AppSettings.Settings["ServerName"].Value = ServerName;
//Get ServiceModelSectionGroup from config
ServiceModelSectionGroup group = ServiceModelSectionGroup.GetSectionGroup (config);
//get the client section
ClientSection clientSection = group.Client;
//get the first endpoint
ChannelEndpointElement channelEndpointElement = clientSection.Endpoints[0];
//get the address attribute and replace servername in the string.
string address = channelEndpointElement.Address.ToString().Replace("ServerName", ServerName);
//set the Address attribute to the new value
channelEndpointElement.Address = new Uri(address);
config.Save();
}
At the end of the day, app.config is xml file. You can use Linq To XML or XPathNavigator to replace the address attribute of endpoint element.
Below code uses Linq to Xml
using System.Xml.Linq;
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
string targetDirectory = Context.Parameters["targetdir"];
string ServerName = Context.Parameters["ServerName"];
System.Diagnostics.Debugger.Break();
string configPath = string.Format("{0myapp.exe.config", targetDirectory);
XElement root = XElement.Load(configPath);
var endPointElements = root.Descendants("endpoint");
foreach(var element in endPointElements)
{
element.Attribute("address").Value = ServerName;
}
root.Save(configPath);
}
}
Since you have a windows-installer tag, I assume you either have an MSI package, or can create one...
Then:
You can create a public MSI property like ENDPOINTSERVER you require
during installation.
Add a custom action that modifies app.config to
run after "InstallFinalize" with the value of ENDPOINTSERVER
A silent installation will be possible using:
msiexec /i app.msi ENDPOINTSERVER=www.MyServer.com /qb-
try to use below two lines before saving the config file changes:
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("Section Name");
root.Save(configPath);
P.S: it doesn't update the solution item 'app.config', but the '.exe.config' one in the bin/ folder if you run it with F5.
I have following class, which is used by a Windows Installer project, to install a service:
[RunInstaller(true)]
public sealed class Installer : System.Configuration.Install.Installer
{
private readonly string _installDir;
public Installer()
{
var locatedAssembly = this.GetType().Assembly.Location;
this._installDir = Path.GetDirectoryName(locatedAssembly);
var serviceProcessInstaller = new ServiceProcessInstaller
{
Account = ServiceAccount.LocalSystem
};
var serviceInstaller = new ServiceInstaller
{
ServiceName = Settings.Service.Name,
StartType = ServiceStartMode.Automatic
};
this.Installers.Add(serviceProcessInstaller);
this.Installers.Add(serviceInstaller);
this.Context = new InstallContext(this._installDir + #"\install.log", new[]
{
string.Format("/assemlypath={0}", locatedAssembly)
});
}
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
var serviceController = new ServiceController(Settings.Service.Name);
serviceController.Start();
serviceController.WaitForStatus(ServiceControllerStatus.Running);
}
}
If we call the following code inside a console application, the directory of the assembly will be taken:
using (var stream = File.Open("foo.store", FileMode.OpenOrCreate))
If I run the line from my Windows Service, C:\Windows\System32\ will be taken instead.
How can I change this behaviour?
For clarification: I do not want to utilize any assembly-spying (get the path of the assembly from this.GetType()...) or anything in the appsettings. I want it to work straight without any magic on the caller side :)
Don't trust the current directory. If the file is located besides the service use:
string sdir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
to recover the path in which the executable is, and use it as a base path to look for the file.
You will need to read the folder location from a configuration file, or the registry. There's no analogue of starting directory.
<configuration>
<configSections>
<section name="ADMIN" type="System.Configuration.DictionarySectionHandler"/>
</configSections>
<User>
<add key="ExtendTime" value="20"/>
<add key="Name" value="sss"/>
</User>
<configuration>
i have to remove first child element in user config section i.e . Reply me if you have any idea for this.
i am using
Configuration config = ConfigurationManager.OpenExeConfiguration(Context.Parameters["assemblypath"]);
ConfigurationSection section = config.GetSection("USER");
This article may have what you're looking for : http://raquila.com/software/configure-app-config-application-settings-during-msi-install/
Excerpt from article:
string exePath = string.Format("{0}MyWindowsFormsApplication.exe", targetDirectory);
Configuration config = ConfigurationManager.OpenExeConfiguration(exePath);
config.AppSettings.Settings["Param1"].Value = param1;
config.AppSettings.Settings["Param2"].Value = param2;
config.AppSettings.Settings["Param3"].Value = param3;
config.Save();
EDIT: Adding additional code sample and blog reference: http://ryanfarley.com/blog/archive/2004/07/13/879.aspx
using System;
using System.Xml;
using System.Configuration;
using System.Reflection;
//...
public class ConfigSettings
{
private ConfigSettings() {}
public static string ReadSetting(string key)
{
return ConfigurationSettings.AppSettings[key];
}
public static void WriteSetting(string key, string value)
{
// load config document for current assembly
XmlDocument doc = loadConfigDocument();
// retrieve appSettings node
XmlNode node = doc.SelectSingleNode("//appSettings");
if (node == null)
throw new InvalidOperationException("appSettings section not found in config file.");
try
{
// select the 'add' element that contains the key
XmlElement elem = (XmlElement)node.SelectSingleNode(string.Format("//add[#key='{0}']", key));
if (elem != null)
{
// add value for key
elem.SetAttribute("value", value);
}
else
{
// key was not found so create the 'add' element
// and set it's key/value attributes
elem = doc.CreateElement("add");
elem.SetAttribute("key", key);
elem.SetAttribute("value", value);
node.AppendChild(elem);
}
doc.Save(getConfigFilePath());
}
catch
{
throw;
}
}
public static void RemoveSetting(string key)
{
// load config document for current assembly
XmlDocument doc = loadConfigDocument();
// retrieve appSettings node
XmlNode node = doc.SelectSingleNode("//appSettings");
try
{
if (node == null)
throw new InvalidOperationException("appSettings section not found in config file.");
else
{
// remove 'add' element with coresponding key
node.RemoveChild(node.SelectSingleNode(string.Format("//add[#key='{0}']", key)));
doc.Save(getConfigFilePath());
}
}
catch (NullReferenceException e)
{
throw new Exception(string.Format("The key {0} does not exist.", key), e);
}
}
private static XmlDocument loadConfigDocument()
{
XmlDocument doc = null;
try
{
doc = new XmlDocument();
doc.Load(getConfigFilePath());
return doc;
}
catch (System.IO.FileNotFoundException e)
{
throw new Exception("No configuration file found.", e);
}
}
private static string getConfigFilePath()
{
return Assembly.GetExecutingAssembly().Location + ".config";
}
}
Then you would use it like this:
// read the Test1 value from the config file
string test1 = ConfigSettings.ReadSetting("Test1");
// write a new value for the Test1 setting
ConfigSettings.WriteSetting("Test1", "This is my new value");
// remove the Test1 setting from the config file
ConfigSettings.RemoveSetting("Test1");
I have come to the conclusion that it is not possible to access a custom configuration section during installation using:
MyCustomConfigurationSection section = (MyCustomConfigurationSection)config.GetSection("MyCustomConfigurationSection");
When the MSI package is installed, the program executed is the Windows Install(MsiExec), not the program which contains the installer class.
'%windir%\system32\msiexec.exe
In order to access the config we need to workaround this issue either by using the context:
Configuration config = ConfigurationManager.OpenExeConfiguration(this.Context.Parameters["assemblypath"]);
Or, by using reflection retrieve the location of the executing assembly:
Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
As Chuck suggested, you can access the AppSettings and modify them:
AppSettingsSection appSettings = (AppSettingsSection)config.GetSection("appSettings");
appSettings.Settings["Environment"].Value = _Environment;
config.Save();
This is fine, because the installer knows exactly how to deal with
System.Configuration.AppSettingsSection
because that library is part of .NET. However, when it comes to a custom section, the installer needs to know how to deal with that custom config. More than likely, you have it in a class library (in a DLL) that is referenced by your application and that DLL has now been installed in the install directory.
The problem is, as we know from above, MSIExec.exe isn't running in the context of that directory, so the install fails, when it can't find the appropriate DLL in system32, it throws the error:
An error occurred creating the
configuration section handler for
'XXX': Could not load file or assembly
'XXX.dll' or one of its dependencies.
The system cannot find the file
specified.
The only way therefore, to access the custom config, is to treat the config file as an XML document, and edit it using traditional XML management tools:
// load the doc
XmlDocument doc = new XmlDocument();
doc.Load(Assembly.GetExecutingAssembly().Location + ".config");
// Get the node
XmlNode node = doc.SelectSingleNode("//MyCustomConfigurationSection");
// edit node here
// ...
// Save
doc.Save(Assembly.GetExecutingAssembly().Location + ".config");
This technique is described on Ryan Farley's blog, as Chuck pointed out in the comments to his original answer.
Good news! I have found a way how to work-around this problem.
Solution is to intercept loading of assembly and return one we have. To do so
ResolveEventHandler handler = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
AppDomain.CurrentDomain.AssemblyResolve += handler;
try
{
section = config.GetSection("mySection") as MySection;
}
catch(Exception)
{
}
AppDomain.CurrentDomain.AssemblyResolve -= handler;
and
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name == "Dead.Beef.Rocks")
{
return typeof(MySection).Assembly;
}
return null;
}