Question

I got a somewhat strange error when trying to resolve the CommonDocuments directory. It keeps resolving to the wrong directory, after the CommonDocuments directory has been redirected / moved to a new location using Windows Explorer (Properties->Path from the context menu).

a minimal working piece of code would be:

namespace CommonDocumentsTest
{
    class Program
    {
        private static readonly Guid CommonDocumentsGuid = new Guid("ED4824AF-DCE4-45A8-81E2-FC7965083634");

        [Flags]
        public enum KnownFolderFlag : uint
        {
            None = 0x0,
            CREATE = 0x8000,
            DONT_VERFIY = 0x4000,
            DONT_UNEXPAND= 0x2000,
            NO_ALIAS = 0x1000,
            INIT = 0x800,
            DEFAULT_PATH = 0x400,
            NOT_PARENT_RELATIVE = 0x200,
            SIMPLE_IDLIST = 0x100,
            ALIAS_ONLY = 0x80000000
        }

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

        static void Main(string[] args)
        {
            KnownFolderFlag[] flags = new KnownFolderFlag[] {
                KnownFolderFlag.None,
                KnownFolderFlag.ALIAS_ONLY | KnownFolderFlag.DONT_VERFIY,
                KnownFolderFlag.DEFAULT_PATH | KnownFolderFlag.NOT_PARENT_RELATIVE,
            };


            foreach (var flag in flags)
            {
                Console.WriteLine(string.Format("{0}; P/Invoke==>{1}", flag, pinvokePath(flag)));
            }
            Console.ReadLine();
        }

        private static string pinvokePath(KnownFolderFlag flags)
        {
            IntPtr pPath;
            SHGetKnownFolderPath(CommonDocumentsGuid, (uint)flags, IntPtr.Zero, out pPath); // public documents

            string path = System.Runtime.InteropServices.Marshal.PtrToStringUni(pPath);
            System.Runtime.InteropServices.Marshal.FreeCoTaskMem(pPath);
            return path;
        }
    }
}

Expected behaviour:
Output is D:\TestDocuments

Actual behaviour:
Output is C:\Users\Public\Documents

None; P/Invoke==>C:\Users\Public\Documents
DONT_VERFIY, ALIAS_ONLY; P/Invoke==>
NOT_PARENT_RELATIVE, DEFAULT_PATH; P/Invoke==>C:\Users\Public\Documents

The correct value is stored in the Windows Registry (HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Common Documents), but it is not returned by SHGetKnownFolderPath (or Environment.GetFolderPath)

OS: Windows 7 Professional x64
.NET Framework v4.0.30319 Application is compiled for x86 CPU

What I tried so far:

  • restarting my application
  • restarting the computer
  • calling Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments);
  • direct calls to Win32-API SHGetKnownFolderPath

EDIT 2 Steps to reproduce:

  1. deactivate UAC on your computer [and restart!]
  2. go to C:\Users\Public\
  3. right click on "Public Documents" folder and select Properties
  4. select the 'Path' tab
  5. click 'Move...' and select a (new) folder on drive D: called TestDocuments
  6. click 'Apply'
  7. accept to move all files to the new location start the minimal application above
Was it helpful?

Solution

tl;dr: Behaviour is by design and only appears when you're running an assembly that was compiled for x86 CPUs on a x64 OS


Longer version:
Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments) accesses the 32-bit hive of the Windows Registry.
The actual path to the folder is stored in the 64-bit hive. The issue has been forwarded to the Windows team and may be fixed in a future version of Windows.

For a bit more information see the Microsoft connect report


Workaround create a console application with the following code and compile it for ANY CPU

static void Main(string[] args)
{
        Console.WriteLine("{0}", Environment.GetFolderPath(System.Environment.SpecialFolder.CommonDocuments));
}

then call it from your main application:

Process proc = new Process();
ProcessStartInfo info = new ProcessStartInfo("myConsoleApp.exe");

// allow output to be read
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.UseShellExecute = false;
proc.StartInfo = info;

proc.Start(); 
proc.WaitForExit();
string path = proc.StandardOutput.ReadToEnd();

this will launch the ANY CPU executable, which only prints out the desired path to the standard output. The output then is read in the main application and you get the real path.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top