Checking if a directory is writable in C#

Share on:

Overview

Today, I am going to demonstrate a simple way to check if the current executing user has a writing permission for a directory in the Windows file system. I came across this issue in a project when I needed to write a utility program that would do the following:

Output a list of all directories that the current user is NOT able to write to.

Background

Microsoft provides an API for manipulating or viewing security access permission via the System.Security.AccessControl namespace. However, using the AccessControl based solution, requires computing the effective permissions for the user identity running your code. It might not be an easy task, as it involves fetching a security descriptor, an access token, and properly calculating the effective permissions.

As a general solution approach, I am going to try to write a file in a specific directory without any permissions calculations. In case an exception is raised by the operating system, I am going to properly handle it and assume the directory is not writable.

My Stack

  • Visual Studio 2019 Community Edition (16.5.1)
  • .NET Framework 4.7.2 (C#) - 32/64 bit.
  • Windows 10 Pro 64-bit (10.0, Build 18363) (18362.19h1_release.190318-1202)

Implementation

Setting up the pInvoke imports

  • So as a first step, I am going to create a utility static class DirectoryUtils that will include the implementation.
  • As I am going to use several Win32 API functions in the sample, so let's import the following:

Note: When importing a Win32 API function into a .NET project, you need to generate the pInvoke signature. For such an operation, I highly recommend you use pinvoke.net.

 1public static class DirectoryUtils
 2{
 3  [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
 4  private static extern SafeFileHandle CreateFile(
 5      string fileName,
 6      uint dwDesiredAccess,
 7      FileShare dwShareMode,
 8      IntPtr securityAttrs_MustBeZero,
 9      FileMode dwCreationDisposition,
10      uint dwFlagsAndAttributes,
11      IntPtr hTemplateFile_MustBeZero);
12
13  [DllImport("kernel32.dll", SetLastError = true, EntryPoint = "SetFileTime", ExactSpelling = true)]
14  private static extern bool SetFileTime(
15      SafeFileHandle hFile,
16      IntPtr lpCreationTimeUnused,
17      IntPtr lpLastAccessTimeUnused,
18      ref long lpLastWriteTime);
19
20  private const uint FILE_ACCESS_GENERIC_READ = 0x80000000;
21  private const uint FILE_ACCESS_GENERIC_WRITE = 0x40000000;
22
23  private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
24  private const int OPEN_EXISTING = 3;
25}

Implementing the class

  1. In the DirectoryUtils class, create a static function DirectoryUtils.IsWritable that gets a directory path to check and returns bool.
  2. We need to create the temporary file using C# File.Create with a random generated file name. Note the FileOptions.DeleteOnClose flag, which ensures the file is deleted once we go out of the using scope.
  3. If the code below throws an exception, we assume the directory is not writable:
1using (File.Create(Path.Combine(dirPath, Path.GetRandomFileName()), 1, FileOptions.DeleteOnClose))
2{
3
4}

So far it looks very easy, but there is a small catch. If the directory is writable, its last write time will change every time we call DirectoryUtils.IsWritable, since we are creating a temporary file. This might look very ugly and unprofessional, especially if we are traversing a long directory tree. All directories will have the 'Date modified' changed in Windows Explorer as shown in the picture:

Windows File Explorer

Windows File Explorer

The solution is the following:

  1. Save the write time before creating the temporary file by using Directory.GetLastWriteTimeUtc.
  2. Restore the write time after the temporary file is deleted by using SetFileTime Win32 API.
 1public static bool SetDirectoryLastWriteUtc(string dirPath, DateTime lastWriteDate)
 2{
 3  using (var hDir = CreateFile(dirPath, FILE_ACCESS_GENERIC_READ | FILE_ACCESS_GENERIC_WRITE,
 4    FileShare.ReadWrite, IntPtr.Zero, (FileMode) OPEN_EXISTING,
 5    FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
 6  {
 7    // put back to the date before checking.
 8    var lastWriteTime = lastWriteDate.ToFileTime();
 9    if (!SetFileTime(hDir, IntPtr.Zero, IntPtr.Zero, ref lastWriteTime))
10    {
11      return false;
12    }
13  }
14
15  return true;
16}
17
18public static bool IsWritable(string dirPath)
19{
20  try
21  {
22    // Since there is a temp file that is being created,
23    // this will change the modified date of the directory.
24    // So if we have successful write operation, we need to
25    // revert the last write date.
26    var lastWriteDate = Directory.GetLastWriteTimeUtc(dirPath);
27
28    // if this fails -> it raises an exception.
29    using (File.Create(Path.Combine(dirPath, Path.GetRandomFileName()), 1, FileOptions.DeleteOnClose))
30    {
31    }
32
33    try
34    {
35      SetDirectoryLastWriteUtc(dirPath, lastWriteDate);
36    }
37    catch (Exception)
38    {
39      // add some log.
40    }
41
42    return true;
43  }
44  catch (UnauthorizedAccessException)
45  {
46     // add some log.
47  }
48  catch (Exception)
49  {
50    // add some log.
51  }
52
53  return false;
54}

Testing

Running some tests on a development machine:

  1. Positive result: I used Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData). In most cases, this directory is writable for the current non-admin user. Check that the last write date did not change after the function returned a ‘true' value.
  2. Negative result: If you are running the Visual Studio as a non-elevated process, the function should fail if you check the Environment.SystemDirectory.
1var dir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
2var result = DirectoryUtils.IsWritable(dir);
3Console.Write($"{dir} - result={result}");
4
5dir = Environment.SystemDirectory;
6result = DirectoryUtils.IsWritable(dir);
7Console.Write($"{dir} - result={result}");

Useful resources

  • Source code of this project on GitHub