83

I have made some code that will search directories and display files in a listbox.

DirectoryInfo dinfo2 = new DirectoryInfo(@"C:\Users\Hunter\Downloads");
FileInfo[] Files2 = dinfo2.GetFiles("*.sto");
foreach (FileInfo file2 in Files2)
{
     listBox1.Items.Add(file2.Name);
}

However, where it says Users\Hunter - well, when people get my software, their name is not Hunter. So how can I automatically detect the user's Downloads folder?

I have tried this:

string path = Environment.SpecialFolder.UserProfile + @"\Downloads";
DirectoryInfo dinfo2 = new DirectoryInfo(Environment.SpecialFolder.UserProfile + path);
FileInfo[] Files2 = dinfo2.GetFiles("*.sto");
foreach (FileInfo file2 in Files2)
{
     listBox1.Items.Add(file2.Name);
}

I get an error though.

janw
  • 8,758
  • 11
  • 40
  • 62
Hunter Mitchell
  • 7,063
  • 18
  • 69
  • 116
  • 1
    Maybe using `Environment.SpecialFolder` **enum** ? did yout tried `path = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); path = Path.Combine(path, "Downloads");` ? – Kiquenet Mar 14 '17 at 11:32
  • @Kiquenet Don't do that, see my answer. – Ray Sep 10 '19 at 11:06

6 Answers6

172

The Downloads folder is a so called "known" folder, together with Documents, Videos, and others.

Do NOT:

  • combine hardcoded path segments to retrieve known folder paths
  • assume known folders are children of the user folder
  • abuse a long deprecated registry key storing outdated paths

Known folders can be redirected anywhere in their property sheets. I've gone into more detail on this several years ago in my CodeProject article.

Do:

  • use the WinAPI method SHGetKnownFolderPath as it is the intended and only correct method to retrieve those paths.

You can p/invoke it as follows (I've provided only a few GUIDs which cover the new user folders):

public enum KnownFolder
{
    Contacts,
    Downloads,
    Favorites,
    Links,
    SavedGames,
    SavedSearches
}

public static class KnownFolders
{
    private static readonly Dictionary<KnownFolder, Guid> _guids = new()
    {
        [KnownFolder.Contacts] = new("56784854-C6CB-462B-8169-88E350ACB882"),
        [KnownFolder.Downloads] = new("374DE290-123F-4565-9164-39C4925E467B"),
        [KnownFolder.Favorites] = new("1777F761-68AD-4D8A-87BD-30B759FA33DD"),
        [KnownFolder.Links] = new("BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968"),
        [KnownFolder.SavedGames] = new("4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4"),
        [KnownFolder.SavedSearches] = new("7D1D3A04-DEBB-4115-95CF-2F29DA2920DA")
    };

    public static string GetPath(KnownFolder knownFolder)
    {
        return SHGetKnownFolderPath(_guids[knownFolder], 0);
    }

    [DllImport("shell32",
        CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
    private static extern string SHGetKnownFolderPath(
        [MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags,
        nint hToken = 0);
}

Here's an example of retrieving the path of the Downloads folder:

string downloadsPath = KnownFolders.GetPath(KnownFolder.Downloads);
Console.WriteLine($"Downloads folder path: {downloadsPath}");

NuGet Package

If you don't want to p/invoke yourself, have a look at my NuGet package (note that the usage is different, please check its README).

Ray
  • 7,940
  • 7
  • 58
  • 90
  • 2
    This class doesnt seem to work in vs2015 - it complains that GetPath doesnt have 3 parameters - which is right, theres no mention of that in the code. – BugFinder Aug 12 '15 at 15:23
  • There is a also nuget for this created originally by MS, mentioned in a similar SO answer: http://stackoverflow.com/a/12610045/540061 – Robb Sadler Jan 31 '17 at 15:12
  • Using **Impersonator with LOGON32_LOGON_INTERACTIVE** not working `Environment.GetFolderPath(Environment.SpecialFolder.UserProfile​)` and `SHGetKnownFolderPath` returns <0. ***Note:*** `The user might have moved the folder to another location (which is quite easy in 8, 8.1 and 10)...` – Kiquenet Mar 14 '17 at 16:10
  • Behaviour as is: `Environment.GetFolderPath(Environment.SpecialFolder.UserProf‌​‌​ile​)` returns _C:\Users\Myusername._ Using ***Impersonate code*** (with LogonType LOGON32_LOGON_INTERACTIVE ) returns _empty string_ – Kiquenet Mar 15 '17 at 08:57
13

The easiest way is:

Process.Start("shell:Downloads");

If you only need to get the current user's download folder path, you can use this:

I extracted it from @PacMani 's code.

 // using Microsoft.Win32;
string GetDownloadFolderPath() 
{
    return Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders", "{374DE290-123F-4565-9164-39C4925E467B}", String.Empty).ToString();
}
Gürkan D.
  • 457
  • 5
  • 7
  • 10
    That key contains an ominous key with the name ***"!Do not use this registry key"*** and the value ***"Use the SHGetFolderPath or SHGetKnownFolderPath function instead"*** the key was put there by Microsoft developer Raymond Chen, "Microsoft's Chuck Norris". [Why is there the message ‘!Do not use this registry key’ in the registry?](https://blogs.msdn.microsoft.com/oldnewthing/20110322-00/?p=11163) [The long and sad story of the Shell Folders key](https://blogs.msdn.microsoft.com/oldnewthing/20031103-00/?p=41973/) – Kiquenet Mar 16 '17 at 12:00
  • 2
    ***"!Do not use this registry key"*** [Getting All "Special Folders" in .NET](https://www.codeproject.com/articles/878605/getting-all-special-folders-in-net) – Kiquenet Mar 16 '17 at 12:00
5

string download = Environment.GetEnvironmentVariable("USERPROFILE")+@"\"+"Downloads";

Leo
  • 157
  • 1
  • 1
  • 4
    This is a code only answser, we expect a little bit more of good answers. Technical note: This will break on international version of windows. The accepted answer on the question is the correct way to do it that will work on all version of windows. – rene Dec 04 '14 at 21:12
  • 3
    IMHO, better, delete answer because not useful. Not good answer because *Downloads* is **NOT value Specialfolder enum** and use "\Downloads" only works on EN-* systems. And _user can change location_. Full explain [Getting All "Special Folders" in .NET](https://www.codeproject.com/articles/878605/getting-all-special-folders-in-net) – Kiquenet Mar 16 '17 at 12:24
  • 3
    This answer is simply wrong. While the _default_ value for the special folder "Downloads" is a folder named "Downloads" under the user profile home directory, it is trivial for the user to change the "Downloads" special folder to any other location to which they have access. The above _will not work_ for any user who has modified the target of the "Downloads" special folder. – Peter Duniho Sep 17 '17 at 05:14
  • I have a machine with Windows 10 in Spanish. Woks fine, no conflicts. – Sith2021 Mar 25 '18 at 21:43
  • 3
    This would break on my machine. My machine has an SSD & mechanical hard drive. So while the user profile is on C: - the Desktop, Downloads, Documents, Pictures, Music, Videos folders are all on the D: drive purely for the storage space. Personally not a good answer. – Scott Jun 26 '19 at 14:32
  • Literally the cleanest one-liner solution! Windows 10 Pro. Thanks!! – WiiLF Dec 14 '21 at 01:48
  • 1
    @WiiLF It's also the most incorrect one. – Ray Apr 21 '22 at 15:54
  • @Ray my application had no problem. Worst case, just read the system registry - extra overhead. – WiiLF Apr 21 '22 at 19:24
  • 1
    @WiiLF Are you writing the application only for personal use? If not, this will break on user profiles where the folder is redirected to somewhere outside the user folder. Check my answers about more info on this. – Ray Apr 21 '22 at 19:49
4

Note:
SHGetKnownFolderPath will return the WRONG value if you changed the download-folder.

The only thing that will ever return you the correct value is reading the shell-folders registry-key 374DE290-123F-4565-9164-39C4925E467B on Windows.

Now you can either use the "!Do not use this registry key", or you can get the wrong value.
You decide which is better for you.

Cross-Platform version:

public static string GetHomePath()
{
    // Not in .NET 2.0
    // System.Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
    if (System.Environment.OSVersion.Platform == System.PlatformID.Unix)
        return System.Environment.GetEnvironmentVariable("HOME");

    return System.Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
}


public static string GetDownloadFolderPath()
{
    if (System.Environment.OSVersion.Platform == System.PlatformID.Unix)
    {
        string pathDownload = System.IO.Path.Combine(GetHomePath(), "Downloads");
        return pathDownload;
    }

    return System.Convert.ToString(
        Microsoft.Win32.Registry.GetValue(
             @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
            ,"{374DE290-123F-4565-9164-39C4925E467B}"
            ,String.Empty
        )
    );
}
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • 4
    That key contains an ominous key with the name ***"!Do not use this registry key"*** and the value ***"Use the SHGetFolderPath or SHGetKnownFolderPath function instead"*** the key was put there by Microsoft developer Raymond Chen, "Microsoft's Chuck Norris". [Why is there the message ‘!Do not use this registry key’ in the registry?](https://blogs.msdn.microsoft.com/oldnewthing/20110322-00/?p=11163) [The long and sad story of the Shell Folders key](https://blogs.msdn.microsoft.com/oldnewthing/20031103-00/?p=41973/) – Kiquenet Mar 16 '17 at 12:02
  • 2
    ***"!Do not use this registry key"*** [Getting All "Special Folders" in .NET](https://www.codeproject.com/articles/878605/getting-all-special-folders-in-net) – Kiquenet Mar 16 '17 at 12:03
  • **IMHO, Microsoft developer Raymond Chen explains it in 2 posts.** `Forbidden interesting :-) Better Good patterns, amazing :-)` – Kiquenet Mar 17 '17 at 11:30
  • 3
    This answer is simply wrong. While the _default_ value for the special folder "Downloads" is a folder named "Downloads" under the user profile home directory, it is trivial for the user to change the "Downloads" special folder to any other location to which they have access. The above _will not work_ for any user who has modified the target of the "Downloads" special folder. – Peter Duniho Sep 17 '17 at 05:13
  • 1
    @Peter Duniho: Actually, I have modified the target for "Downloads", that's why I wrote the code in the first place. This is the only thing that worked in .NET 2.0. The rest didn't, or gave wrong results. – Stefan Steiger Sep 17 '17 at 21:32
  • 5
    On Unix, where there's no equivalent of the Windows user-defined "Downloads" folder, you hard-code a folder name. On Windows, instead of calling the `SHGetKnownFolderPath()` function like you're supposed to, you try to get the folder directly from the registry. Either way, this answer is not useful and potentially harmful to those who use it. – Peter Duniho Sep 17 '17 at 22:07
  • 1
    @Peter Duniho: On Ubuntu Linux (System.PlatformID.Unix - since .NET non-core has no separate platformID for Linux), the download-directory is named "Downloads" and is in the user home directory (by default). MacOS regrettably falls through the cracks, since PlatformID.Unix also returns true on MacOS, and platformID MacOSX also returns true on Linux. Not sure anybody is seriously using Unix anymore. But if you like, you can always pinvoke native calls on both Unix/Linux and Windows, which will require adding a dependency on Mono.Unix, if you do it properly. I prefer not adding such dependencies – Stefan Steiger Sep 18 '17 at 14:03
  • It is shocking that MS maintains on Registry the original folder for Downloads... I'm one of the guys that redirected it outside SSD... in my development PC `C:\users...` couldn't be wronger... – Marcelo Scofano Diniz Dec 02 '20 at 15:17
  • I have renamed Downloads folder on Win 10, .NET 4.8 and the SHGetKnownFolderPath() returned correct path. – sebetovsky Jun 05 '23 at 16:19
  • @sebetovsky: Maybe now, but there were a lot of other framework and windows versions before that. Don't even know anymore what it was. – Stefan Steiger Jun 06 '23 at 08:31
-2

typically, your software shall have a configurable variable that stores the user's download folder, which can be assigned by the user, and provide a default value when not set. You can store the value in app config file or the registry.

Then in your code read the value from where it's stored.

pink li
  • 102
  • 6
  • 1
    IMHO, better, delete answer because not useful. Not good answer because `reinvent the wheel`. ***App.config*** not requires, only **WinAPI** [Getting All "Special Folders" in .NET](https://www.codeproject.com/articles/878605/getting-all-special-folders-in-net) – Kiquenet Mar 16 '17 at 12:21
-4

http://msdn.microsoft.com/en-us//library/system.environment.specialfolder.aspx

There are the variables with the path to some special folders.

Marduk
  • 101
  • 3
  • wait, when i put that in my code, i get an error: *Could not find a part of the path 'C:\Users\Hunter\Documents\Visual Studio 2010\Projects\Setup Mover2\Setup Mover2\bin\Debug\UserProfile\Downloads\'.* – Hunter Mitchell May 19 '12 at 16:45
  • 2
    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) + "\Downloads"; // my fault, sorry – Marduk May 19 '12 at 17:48
  • 3
    @Marduk Please delete your comments, since they provide a solution that only works on English systems, and even only some of them. – Roland Illig Dec 03 '16 at 00:16
  • 4
    IMHO, better, delete answer because not useful. Not good answer because *Downloads* is **NOT value Specialfolder enum** and use "\Downloads" only works on EN-* systems. And _user can change location_. Full explain [Getting All "Special Folders" in .NET](https://www.codeproject.com/articles/878605/getting-all-special-folders-in-net) – Kiquenet Mar 16 '17 at 12:25
  • 2
    the newer folders which exist since Windows Vista, are not listed in SpecialFolder enum. [The list of special folders published in MSDN](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396) – Kiquenet Mar 16 '17 at 12:44