1. Introduction
I hope these writings will come to the aid of others like me who struggled or are struggling with this maybe, seemingly trivial issue, but which actually is not that straightforward. First of all a quick description of the problem at hand – we want to get the path of the Special Folders in Windows (My Documents, Downloads, Music and so on) from a Windows Service using a .NET based programming language (preferably C#).
You might be thinking that there already are out of the box methods of doing this in .NET/C# and that is partially true. The problem is that Microsoft as of yet has not updated the .NET Framework to support retrieval of newly added special folders starting from Windows Vista (like the Downloads folder for instance). Also, doing this from a Windows Service is made even more difficult and we will see why (hopefully). Having this said I will now try to explain as well as I can the solution that I came across after a few good hours of research, google search, reflection on the issue and stuff like that and also hope that it will help you as it helped me.
2. Solution
As I said in the introduction there are some special folders starting with Windows Vista that the .NET Framework does not have methods of retrieving them. One such special folder is the “Downloads” folder and is also the folder whose path I needed to retrieve. After some google search, I came across a library that was designed by some other developer specifically for this sort of thing. The library is pretty straightforward to install and to use and as such I will not be getting into these details.
- Find information about it at https://www.codeproject.com/Articles/878605/Getting-All-Special-Folders-in-NET
- Install it from https://www.nuget.org/packages/Syroot.Windows.IO.KnownFolders/
This library seems to work just fine in your typical .NET Application (Console, Windows Forms, WPF and so on…). The problem arises, as I said before when we need this functionality in a Windows Service (though it depends) because it is slightly different than your typical Windows Application. By default, a Windows Service runs in a different context and under a different account than the one you use to log into Windows and namely the “System” account. This means that it has different paths to the special folders and here is where the problem lies. Fortunately, the “KnownFolders” library mentioned above has a way of retrieving folders based on the WindowsIdentity instance.
Unfortunately, also mentioned above, the Windows Service runs under a different WindowsIdentity than the one of the logged in user (typical Windows User/Account). Fortunately again, there is a way (possibly more) to get the WindowsIdentity of the Windows User/Account from the Windows Service. It is a slightly hacky solution but since it worked for me I thought that it is worth sharing it in case someone else faces the same problem. So, what we first need to do, is to get the WindowsIdentity of the User. I managed to do this by first retrieving the Process Token of the “explorer.exe” process (this process is always running even if you do not have Windows Explorer opened) like this:
private static WindowsIdentity WindowsIdentityFromProcess(Process process) { var process_handle = IntPtr.Zero; try { OpenProcessToken(process.Handle, 8, out process_handle); return new WindowsIdentity(process_handle); } catch { return null; } finally { if (process_handle != IntPtr.Zero) { CloseHandle(process_handle); } } } [DllImport("advapi32.dll", SetLastError = true)] private static extern bool OpenProcessToken(IntPtr process_handle, uint desired_access, out IntPtr token_handle); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr h_object);
So with this method, we get the WindowsIdentity that we need. Now, based on this WindowsIdentity and the KnownFolders library, we should be able to retrieve the path that we want to like this:
private static string DefaultDownloadPath() { var windows_identity = WindowsIdentityFromProcess(Process.GetProcessesByName("explorer").FirstOrDefault()); return windows_identity != null ? new KnownFolder(KnownFolderType.Downloads, windows_identity).Path : KnownFolders.PublicDownloads.Path; }
As you can see if the windows_identity object is not null it will return the path of the Downloads Folder of the User and if it still did not manage to retrieve the windows_identity object it will return the path of the Public Downloads Folder.
3. Conclusion
God bless this beautiful age of software that sometimes makes us feel like gods (dramatic effect) and the satisfaction that we get when we accomplish something after a little bit of struggle is certainly noteworthy and hopefully, you find this article useful.
You can also check my previous article:
https://assist-software.net/blog/dependency-injection-aspnet-mvc-tutorial
Continue your upskilling with an article about Managed and Unmanaged Code and the differences in development and testing which you will surely find useful in your .NET journey.