I am investigating the
proper place to put program config files in the Windows Environment. I am using Visual Studio 2012 as my IDE and
programming in C#. I am using
InstallShield LE for my installation process.
If you are having problems getting InstallShield LE working on Visual
Studio I did a little write up on it here http://www.whiteboardcoder.com/2013/02/visual-studio-pro-2012-install-shield-le.html
Where to put a program config file?
I am currently writing a
Windows .Net program that when it starts will need to read some data from a
config file and possibly write some data out to the same config file as well.
Looking around I found
this older article http://vb.mvps.org/articles/vsm20090119.pdf [1] that suggested a good place to put this file
Common AppData Folder. This folder can
differ between different Windows OS Systems, but in Windows 7 it is at
C:\ProgramData (A file that is typically
hidden)
First a Default config file
First I am going to create an
App.config file.
Right click on your program and
then select Add --> New Item
Select General, Then select Application Configuration File
and finally click add.
You should now have an
App.config file double click on it to
open it.
I updated it, adding a few
keys. Save it.
Now to the Install Shield I am
going to use the InstallShield program to place a default config file in the
Common AppData Folder.
Open the InstallShield Project
Assistant and click on Application Files.
Right clik on Destination
Computer then select Show predefined Folder and select [CommonAppDataFolder]
Right click on CommonAppDataFolder and select New Folder
Give the folder your company
name
Select the folder and click Add
Files.
Find the App.config file and
open it (add it to the deploy)
Right click on References and
select Add Reference
Search for System.Configuration. Checkbox System.Configuration
Now build the solution. Click Build--> Build Solution
Run the installer that this
created
Now in your Common Application
Data Folder.
Update Program to read it
As a proof of concept I updated
the code for a button click to the following.
private void button1_Click(object sender, EventArgs e)
{
//Check to see if the Config file is in CommonApplicationData
Folder
String cadAppPath Environment.GetFolderPath(
Environment.SpecialFolder.
CommonApplicationData) + @"\Whiteboardcoder\App.Config";
if (File.Exists(cadAppPath)){
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", cadAppPath);
}
String fName = ConfigurationManager.AppSettings["name"];
String lName = ConfigurationManager.AppSettings["lname"];
MessageBox.Show("Hello
World " + fName + " " + lName);
}
|
I spent several hours trying to
figure out how to update ConfigurationManager to read from another file. Everything I tried or found had issues. So instead of finding out how to switch that
I found this site http://stackoverflow.com/questions/4738/using-configurationmanager-to-load-config-from-an-arbitrary-location [2]
Where a user YoYo show how to change where APP_CONFIG_DATA is.
Where a user YoYo show how to change where APP_CONFIG_DATA is.
I wrote the code to use it if
it can find it else use the local one (useful when you are programming and it's
not installed)
The messageBox opens up and gets data from C:\ProgramData\Whiteboardcoder\App.config
file
Problem!
Well that kind of worked.
That is until I installed it then it refused to work properly. The program would only read the App.config
file in the same directory as the installed program.
Luckily I found this post http://stackoverflow.com/questions/6150644/change-default-app-config-at-runtime
[3] A forum Poster Daniel Hilgarth pointed out that there is a class called
ClientConfigPaths
that caches the path so updating it alone does
no good.
He posted
a simple class to help with the problem.
Here is the class
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Text;
namespace HelloWorld
{
public abstract class AppConfig : IDisposable
{
public static AppConfig Change(string path)
{
return new ChangeAppConfig(path);
}
public abstract void Dispose();
private class ChangeAppConfig : AppConfig
{
private readonly string oldConfig =
AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE").ToString();
private bool disposedValue;
public ChangeAppConfig(string path)
{
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", path);
ResetConfigMechanism();
}
public override void Dispose()
{
if (!disposedValue)
{
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", oldConfig);
ResetConfigMechanism();
disposedValue = true;
}
GC.SuppressFinalize(this);
}
private static void ResetConfigMechanism()
{
typeof(ConfigurationManager)
.GetField("s_initState", BindingFlags.NonPublic |
BindingFlags.Static)
.SetValue(null, 0);
typeof(ConfigurationManager)
.GetField("s_configSystem", BindingFlags.NonPublic |
BindingFlags.Static)
.SetValue(null, null);
typeof(ConfigurationManager)
.Assembly.GetTypes()
.Where(x => x.FullName ==
"System.Configuration.ClientConfigPaths")
.First()
.GetField("s_current", BindingFlags.NonPublic |
BindingFlags.Static)
.SetValue(null, null);
}
}
}
}
|
I added this class
to my own then updated my Form class as shown below
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace HelloWorld
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//Check to see if the Config file is in
//CommonApplicationData
Folder
String cadAppPath = Environment.GetFolderPath(
Environment.SpecialFolder.CommonApplicationData)
+ @"\Whiteboardcoder\App.Config";
AppConfig.Change(cadAppPath);
String fName = ConfigurationManager.AppSettings["name"];
String lName = ConfigurationManager.AppSettings["lname"];
MessageBox.Show("Hello
World " + fName + " " + lName);
}
}
}
|
Here is the
important part.
Now if you make an
installer for the program, install it, it will read data from C:\ProgramData\Whiteboardcoder\App.config (well that is where it will read on my
program)
Update the App.Config Data
I found some notes
at http://stackoverflow.com/questions/1357240/change-the-value-in-app-config-file-dynamicallay [4]
Here is my updated
code
using System;
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.Configuration;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using
System.Windows.Forms;
namespace HelloWorld
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//Check to see if the Config file is
in
//CommonApplicationData Folder
String cadAppPath = Environment.GetFolderPath(
Environment.SpecialFolder.CommonApplicationData)
+ @"\Whiteboardcoder\App.Config";
AppConfig.Change(cadAppPath);
//Now to update the data
Configuration config = ConfigurationManager.
OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings["name"].Value = "John";
config.AppSettings.Settings["lname"].Value = "Jones";
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
String fName = ConfigurationManager.AppSettings["name"];
String lName = ConfigurationManager.AppSettings["lname"];
MessageBox.Show("Hello World
"
+ fName + " " + lName);
}
}
}
|
This is the portion
I added
One last Problem!
I had one last problem.
I tried to install this on another Windows 7 machine using the installer
I created using the InstallShield LE in Visual Studio 2012 and I got an error.
Acces to path … denied.
If I run the program as an administrator it works (right click on .exe file and choose Run as
Administrator)
This of course assumes you have admin privileges. My user does have admin privileges so I am a bit confused
Ok I found the solution, it a UAC issue. You need to be an admin and turn off UAC.
Open up Control Panel
Do a search for "uac" then click on Change user Account Control
Settings.
Drop it down to Never Notify and click OK.
This requires a restart of your computer.
That solved my problem.
The other solution would have been to change the privileges on the file
itself so that my user could read it.
So that is a wrap.
The program is running, it installed the config file in the
CommonApplicationData folder, read from that file and updated it as well.
References
[1] Lemme Tell Ya Where to Stick
it
Accessed
02/2013
[2] Using ConfigurationManager
to load config from an arbitrary location
Accessed
02/2013
[3] Change default app.config at
runtime
Accessed
02/2013
[4] Change the value in
app.config file dynamicallay
Accessed
02/2013
About the last problem you have there. It would also work if you just change the access level of the ProgramData\YourApp folder to full control for all users, in order to prevent an overall security setting change in your system.
ReplyDeleteawesome article!
ReplyDelete