Sunday, December 14, 2008

Application.StartupPath != Environment.CurrentDirectory

When developing the .NET app, you are not only creating an .exe. Most of the time, you will need some other files like .bmp, App.Config, and .xml. For those resource files(data or images), you could simply embed into the main exe and access it class "ResourceManager", though it might increase the size tremendously. But what if for those XML file that need user customization? You might put them along with your main executable file during deployment, for initialization. Thus you will need to find where your .exe starts, and to read XML file that relative to it. So now you might think you got at least two choices, "Environment.CurrentDirectory" & "Application.StartupPath".

From the MSDN, the definition of:
a) Environment.CurrentDirectory
Gets or sets the fully qualified path of the current working directory.
b) Application.StartupPath
Gets the path for the executable file that started the application, not including the executable name.

They look similar & match your purpose. "Environment.CurrentDirectory" looks good, especially it is located inside "System" namespace, so you no need to add any reference; while "Application.StartupPath" needs to add reference to assembly "System.Windows.Forms". You might think the former one is better for console application, and even windows service! Windows service does not need any GUI and would suppress any messagebox from pop-up. So using "Environment.CurrentDirectory" seems a good choice to prevent the developers simply calling messagebox in windows service if without adding reference to assembly "System.Windows.Forms", from design perspective.

So I decided to use "Environment.CurrentDirectory" for my dll, which is eventually called by the windows service, and the code to load the XML file would be similar to this:

Dim strXMLFile As String = Environment.CurrentDirectory & "\" & INITIALIZATION_XML
If Not File.Exists(strXMLFile) Then
Throw New FileNotFoundException("The initialization file " & INITIALIZATION_XML & " does not exist.")
End If
I created a WinForm for test harness of the dll, and everything was fine. Then this dll consumed by the windows services. Bang! I get an "FileNotFoundException" exception. I use Debug.Print to find that my XML look-up path was at: "C:\WINDOWS\system32"???

Why? It's because Windows service applications run in their own security context.
and more definition for "Environment.CurrentDirectory":

Gets and sets the fully qualified path of the current directory; that is, the directory from which this process starts.

Using the Reflector:
a) Environment.CurrentDirectory

b) Application.StartupPath


So, the "Environment.CurrentDirectory" is equivalent to "Application.StartupPath" when running as WinForm/console app and will become variable "%SystemDirectory%" eventually if run under windows service! A lesson learned.

p/s: You could use reflection from class Assembly as well, to find the start-up path.

5 comments:

Dean Harding said...

You can also use:

Assembly.GetEntryAssembly().Location

To get the full path to the executable you're running inside of and the use Path.GetDirectoryName() to get the directory.

No extra dependencies and 100% foolproof :-) You can even use Assembly.GetExecutingAssembly().Location if your DLL is not in the same folder as the .exe!

gary said...

Thanks Dean, though I have already mentioned it in #p/s(using Assembly class). I really appreciate it

Rich said...

Not quite foolproof. Assembly.GetEntryAssembly() does not work from Unmanaged code. I had it return null when running unit tests from the IDE. Assembly.GetExecutingAssembly() seemed to work ok though...

gary said...

Unmanaged code? I never think of that, haha. Thanks for ur input.

Garkin said...

> So, the "Environment.CurrentDirectory" is equivalent to "Application.StartupPath" when running as WinForm/console app and will become variable "%SystemDirectory%" eventually if run under windows service! A lesson learned.

This isn't fully correct, even if you run WinForm/console you can run it from directory different from Application.StartupPath, or you can run it from icon, where special property define value of Environment.CurrentDirectory (editable).