How to use relative paths in a Windows Service

Relative paths are resolved relative to the current working directory. When you’re running a Windows Service, the default working directory is C:\Windows\system32 or C:\Windows\SysWOW64. Therefore relative paths are resolved from these system folders, which can lead to problems when read/writing files.

Here are the most common problems you’ll run into:

  • File or Directory cannot be found.

System.IO.DirectoryNotFoundException: Could not find a part of the path ‘C:\WINDOWS\system32\Configs\Test.ini’.

  • Access error. Your service is trying to access a system folder and doesn’t have permission.

System.UnauthorizedAccessException: Access to the path ‘C:\WINDOWS\system32\makolyteService.log‘ is denied.

  • When writing a file, it doesn’t appear in the expected directory. Instead, it shows up in the system folder, like this:
C:\Windows\System32\makolyteService.log

This problem is not specific to Windows Services. It happens anytime anytime you’re trying to use relative paths from an unexpected working directory.

You have two options for using relative paths correctly:

  • Change the current working directory.
  • Resolving the relative paths from where the executable is located.

Example

I created a Windows Service that does the following:

  • Reads text from relative path: .\Configs\Test.ini
  • Writes the text to relative path: makolyteService.log

First, get your current working directory

If you are having problems with relative paths, the first step is to figure out where your code is running from. For this, you can use System.IO.Directory.GetCurrentDirectory(), like this:

EventLog.WriteEntry($"Current working directory: {System.IO.Directory.GetCurrentDirectory()}");
Code language: C# (cs)

This is telling me that the current working directory is C:\Windows\system32.

Option 1 – Changing the current working directory

Before calling any file read/write operations, change the current working directory to where the executable is located on disk.

System.IO.Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
Code language: C# (cs)

This is the best option. It fixes all file read/write operations without any additional code changes.

Option 2 – Resolve paths from the executable’s location on disk

If you can’t use option 1, then your other option is to resolve the file paths before reading/writing.

  • Create a method that resolves paths from the executable’s location on disk.
private string ResolvePath(string filePath)
{
	if(Path.IsPathRooted(filePath))
	{
		return filePath;
	}
	else
	{
		return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePath);
	}
}

Code language: C# (cs)

Note: IsPathRoot(…) is a heuristic approach to finding out if a path is absolute. If a path is rooted, it is almost always an absolute path. There are some path formats that are rooted and relative, but I’ve never seen these used in practice.

  • Use the resolved paths when reading/write. For example, this is reading a file using the resolved path:
private string ReadFile(string filePath)
{
	return File.ReadAllText(ResolvePath(filePath));
}
Code language: C# (cs)

Code

For reference, here is my Windows Service. This is showing how to fully use option 2.

protected override void OnStart(string[] args)
{
	//This commented out line is for option 1. Use this if you can. 
	//System.IO.Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);

	EventLog.WriteEntry($"Current working directory: {System.IO.Directory.GetCurrentDirectory()}");

	string readFromFilePath = @".\Configs\Test.ini";
	string writeToFilePath = "makolyteService.log";

	string fileContents = "";

	try
	{
		fileContents = ReadFile(readFromFilePath);
	}
	catch(Exception ex)
	{
		fileContents = $"Exception while trying to read the file. {ex}";
	}

	WriteFile(writeToFilePath, fileContents);

   
}
private string ResolvePath(string filePath)
{
	if(Path.IsPathRooted(filePath))
	{
		return filePath;
	}
	else
	{
		return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePath);
	}
}

private string ReadFile(string filePath)
{
	return File.ReadAllText(ResolvePath(filePath));
}
private void WriteFile(string filePath, string fileContents)
{
	File.WriteAllText(ResolvePath(filePath), fileContents);
}
Code language: C# (cs)

Leave a Comment