'How to programmatically derive Windows Downloads folder "%USERPROFILE%/Downloads"?

In .NET, we can retrieve the paths to 'special folders', like Documents / Desktop etc. Today I tried to find a way to get the path to the 'Downloads' folder, but it's not special enough it seems.

I know I can just do 'C:\Users\Username\Downloads', but that seems an ugly solution. So how can I retrieve the path using .NET?



Solution 1:[1]

The problem of your first answer is it would give you WRONG result if the default Downloads Dir has been changed to [Download1]! The proper way to do it covering all possibilities is

using System;
using System.Runtime.InteropServices;

static class cGetEnvVars_WinExp    {
    [DllImport("Shell32.dll")] private static extern int SHGetKnownFolderPath(
        [MarshalAs(UnmanagedType.LPStruct)]Guid rfid, uint dwFlags, IntPtr hToken,
        out IntPtr ppszPath);

    [Flags] public enum KnownFolderFlags : uint { SimpleIDList = 0x00000100
        , NotParentRelative = 0x00000200, DefaultPath = 0x00000400, Init = 0x00000800
        , NoAlias = 0x00001000, DontUnexpand = 0x00002000, DontVerify = 0x00004000
        , Create = 0x00008000,NoAppcontainerRedirection = 0x00010000, AliasOnly = 0x80000000
    }
    public static string GetPath(string RegStrName, KnownFolderFlags flags, bool defaultUser) {
        IntPtr outPath;
        int result = 
            SHGetKnownFolderPath (
                new Guid(RegStrName), (uint)flags, new IntPtr(defaultUser ? -1 : 0), out outPath
            );
        if (result >= 0)            {
            return Marshal.PtrToStringUni(outPath);
        } else {
            throw new ExternalException("Unable to retrieve the known folder path. It may not "
                + "be available on this system.", result);
        }
    }

}   

To test it, if you specifically desire your personal download dir, you flag default to false -->

using System.IO;

class Program    {
    [STAThread]
    static void Main(string[] args)        {
        string path2Downloads = string.Empty;
        path2Downloads = 
            cGetEnvVars_WinExp.GetPath("{374DE290-123F-4565-9164-39C4925E467B}", cGetEnvVars_WinExp.KnownFolderFlags.DontVerify, false);
        string[] files = { "" };
        if (Directory.Exists(path2Downloads)) {
            files = Directory.GetFiles(path2Downloads);
        }
    }

Solution 2:[2]

Yes it is special, discovering the name of this folder didn't become possible until Vista. .NET still needs to support prior operating systems. You can pinvoke SHGetKnownFolderPath() to bypass this limitation, like this:

using System.Runtime.InteropServices;
...

public static string GetDownloadsPath() {
    if (Environment.OSVersion.Version.Major < 6) throw new NotSupportedException();
    IntPtr pathPtr = IntPtr.Zero;
    try {
        SHGetKnownFolderPath(ref FolderDownloads, 0, IntPtr.Zero, out pathPtr);
        return Marshal.PtrToStringUni(pathPtr);
    }
    finally {
        Marshal.FreeCoTaskMem(pathPtr);
    }
}

private static Guid FolderDownloads = new Guid("374DE290-123F-4565-9164-39C4925E467B");
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
private static extern int SHGetKnownFolderPath(ref Guid id, int flags, IntPtr token, out IntPtr path);

Solution 3:[3]

Try this:

string path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)+ @"\Downloads";

Solution 4:[4]

This is a refactor of the accepted answer as IMO it could be implemented a little nicer:

public static class KnownFolders
{
    public static Guid Contacts = new Guid("{56784854-C6CB-462B-8169-88E350ACB882}");
    public static Guid Desktop = new Guid("{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}");
    public static Guid Documents = new Guid("{FDD39AD0-238F-46AF-ADB4-6C85480369C7}");
    public static Guid Downloads = new Guid("{374DE290-123F-4565-9164-39C4925E467B}");
    public static Guid Favorites = new Guid("{1777F761-68AD-4D8A-87BD-30B759FA33DD}");
    public static Guid Links = new Guid("{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}");
    public static Guid Music = new Guid("{4BD8D571-6D19-48D3-BE97-422220080E43}");
    public static Guid Pictures = new Guid("{33E28130-4E1E-4676-835A-98395C3BC3BB}");
    public static Guid SavedGames = new Guid("{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}");
    public static Guid SavedSearches = new Guid("{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}");
    public static Guid Videos = new Guid("{18989B1D-99B5-455B-841C-AB7C74E4DDFC}");

    static Dictionary<string, Guid> Map { get; } = new Dictionary<string, Guid> {
        { nameof(Contacts), Contacts },
        { nameof(Desktop), Desktop },
        { nameof(Documents), Documents },
        { nameof(Downloads), Downloads },
        { nameof(Favorites), Favorites },
        { nameof(Links), Links },
        { nameof(Music), Music },
        { nameof(Pictures), Pictures },
        { nameof(SavedGames), SavedGames },
        { nameof(SavedSearches), SavedSearches },
        { nameof(Videos), Videos },
    };

    public static string GetPath(string knownFolder,
        KnownFolderFlags flags = KnownFolderFlags.DontVerify, bool defaultUser = false) =>
        Map.TryGetValue(knownFolder, out var knownFolderId)
            ? GetPath(knownFolderId, flags, defaultUser)
            : ThrowUnknownFolder();

    public static string GetPath(Guid knownFolderId, 
        KnownFolderFlags flags=KnownFolderFlags.DontVerify, bool defaultUser=false)
    {
        if (SHGetKnownFolderPath(knownFolderId, (uint)flags, new IntPtr(defaultUser ? -1 : 0), out var outPath) >= 0)
        {
            string path = Marshal.PtrToStringUni(outPath);
            Marshal.FreeCoTaskMem(outPath);
            return path;
        }
        return ThrowUnknownFolder();
    }

    //[DoesNotReturn]
    static string ThrowUnknownFolder() => 
        throw new NotSupportedException("Unable to retrieve the path for known folder. It may not be available on this system.");

    [DllImport("Shell32.dll")]
    private static extern int SHGetKnownFolderPath(
        [MarshalAs(UnmanagedType.LPStruct)]Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr ppszPath);
}

Can be called with:

var downloadPath = KnownFolders.GetPath(KnownFolders.Downloads);

Or sometimes it's more convenient to fetch it using a string:

var downloadPath = KnownFolders.GetPath(nameof(KnownFolders.Downloads));

Solution 5:[5]

Hans Passant's answer above about using SHGetKnownFolderPath is (as usual) absolutely correct. But if you like, you can channel some more P/Invoke functionality to simplify the import and make its signature more ".NET-esque":

  • We specify CharSet = CharSet.Unicode since the returned string is always a Unicode PWSTR. There are no A and W overloads, thus we also set ExactSpelling = true to prevent runtime searching for such. This now allows us to replace out IntPtr path with out string path which makes the marshaller convert the string automatically, including freeing the memory1. That removes the burden put on us to manually call PtrToStringUni and FreeCoTaskMem:

    public static string GetDownloadsPath() {
        if (Environment.OSVersion.Version.Major < 6) throw new NotSupportedException();
        string path;
        SHGetKnownFolderPath(ref FolderDownloads, 0, IntPtr.Zero, out path);
        return path;
    }
    
    private static Guid FolderDownloads = new Guid("374DE290-123F-4565-9164-39C4925E467B");
    [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    private static extern int SHGetKnownFolderPath(ref Guid id, int flags,
        IntPtr token, out string path);
    
  • We can specify PreserveSig = false to convert failure HRESULTs returned by the method into appropriate thrown exceptions, and replace the "return" value with the last out parameter, e.g. out string path:

    public static string GetDownloadsPath() {
        if (Environment.OSVersion.Version.Major < 6) throw new NotSupportedException();
        return SHGetKnownFolderPath(ref FolderDownloads, 0, IntPtr.Zero);
    }
    
    private static Guid FolderDownloads = new Guid("374DE290-123F-4565-9164-39C4925E467B");
    [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true,
        PreserveSig = false)]
    private static extern string SHGetKnownFolderPath(ref Guid id, int flags,
        IntPtr token);
    
  • To further simplify passing parameters to the method, we can tell the marshaller to automatically pass the Guid as a reference with MarshalAs(UnmanagedType.LPStruct):

    public static string GetDownloadsPath() {
        if (Environment.OSVersion.Version.Major < 6) throw new NotSupportedException();
        return SHGetKnownFolderPath(FolderDownloads, 0, IntPtr.Zero);
    }
    
    private static Guid FolderDownloads = new Guid("374DE290-123F-4565-9164-39C4925E467B");
    [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true,
        PreserveSig = false)]
    private static extern string SHGetKnownFolderPath(
        [MarshalAs(UnmanagedType.LPStruct)] Guid id, int flags, IntPtr token);
    
  • To simulate the token parameter being declared optional in the WinAPI method, we can specify = 0 or = default, and use C# 9's new nint instead of an IntPtr:

    public static string GetDownloadsPath() {
        if (Environment.OSVersion.Version.Major < 6) throw new NotSupportedException();
        return SHGetKnownFolderPath(FolderDownloads, 0);
    }
    
    private static Guid FolderDownloads = new Guid("374DE290-123F-4565-9164-39C4925E467B");
    [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true,
        PreserveSig = false)]
    private static extern string SHGetKnownFolderPath(
        [MarshalAs(UnmanagedType.LPStruct)] Guid id, int flags, nint token = 0);
    

I've provided a complete example retrieving more than just the Downloads folder in my other answer, and go into further detail on it on my CodeProject article.


1 Automatic string conversion only works in this specific case. The marshaller assumes that 1) the buffer pointed to by the IntPtr was allocated with CoTaskMemAlloc before, and 2) the caller must free it. Thus it always calls CoTaskMemFree on it after conversion, which crashes if the conditions weren't met. It also requires an appropriate CharSet in the DllImport attribute, or converts strings with wrong encoding.

Solution 6:[6]

I used the below code and it works for .net 4.6 with Windows 7 and above. The below code gives the user profile folder path -> "C:\Users\<username>"

string userProfileFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);

Next to access the downloads folder just combine additional path strings as below:

string DownloadsFolder = userProfileFolder + "\\Downloads\\";

Now, the final result will be

"C:\Users\<username>\Downloads\"

Hope it saves time for someone in the future.

Solution 7:[7]

It's not that hard. If you want to get the downloads folder directory in vb.net, Just follow these steps:

1.Add a label named Special_Direcories.

2.Add this code when the form loaded:

Special_directories.Text = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\downloads\"

This will set the label text as the user folder path and "\downloads\". This is how it will look like:

C:\users\USERFOLDER\downloads\

Solution 8:[8]

Hans's answer works perfectly! And I appreciate it's a very old question, but seeing as .Net (for whatever reason) still hasn't plugged this functionality hole, I figured I'd post the below refactoring of Han's answer in case someone finds it useful.

  • added some missing error handling
  • access folder via a property instead of a method, i.e. consistent with other .Net Environment usage (e.g Environment.CurrentDirectory)

copy/paste snippet..

using System;
using System.IO;
using System.Runtime.InteropServices;

namespace Utils
{
    public static class SpecialFolder
    {
        public static string Downloads => _downloads ??= GetDownloads();

        // workaround for missing .net feature SpecialFolder.Downloads
        // - https://stackoverflow.com/a/3795159/227110
        // - https://stackoverflow.com/questions/10667012/getting-downloads-folder-in-c
        private static string GetDownloads()
        {
            if (Environment.OSVersion.Version.Major < 6)
                throw new NotSupportedException();

            var pathPtr = IntPtr.Zero;
            try
            {
                if (SHGetKnownFolderPath(ref _folderDownloads, 0, IntPtr.Zero, out pathPtr) != 0)
                    throw new DirectoryNotFoundException();
                return Marshal.PtrToStringUni(pathPtr);
            }
            finally
            {
                Marshal.FreeCoTaskMem(pathPtr);
            }
        }

        [DllImport("shell32.dll", CharSet = CharSet.Auto)]
        private static extern int SHGetKnownFolderPath(ref Guid id, int flags, IntPtr token, out IntPtr path);

        private static Guid _folderDownloads = new Guid("374DE290-123F-4565-9164-39C4925E467B");
        private static string _downloads;
    }
}

usage

var downloadFolder = SpecialFolder.Downloads;

Solution 9:[9]

This is my simplest possible solution for getting the SavedGames path. For getting the path of other folders, you must change the GUID accordingly.

public static string GetSavedGamesPath()
{
    SHGetKnownFolderPath(new Guid("{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}"), 0x00004000, new IntPtr(0), out var PathPointer);
    var Result = Marshal.PtrToStringUni(PathPointer);
    Marshal.FreeCoTaskMem(PathPointer);

    return Result;
}

[DllImport("Shell32.dll")]
static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr ppszPath);

Solution 10:[10]

try:

Dim Dd As String = Environment.GetFolderPath(Environment.SpecialFolder.Favorites)
Dim downloD As String = Dd.Replace("Favorites", "Downloads")
txt1.text = downLoD

its just a trick , not solution .

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ray
Solution 2
Solution 3 Shaahin
Solution 4 mythz
Solution 5
Solution 6 Jabez
Solution 7 Lorenzo Isidori
Solution 8 stoj
Solution 9 Xtro
Solution 10