SHGetKnownFolderPath / Environment.GetFolderPath() returning wrong value for public documents
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开发者_JAVA技巧 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 isD:\TestDocuments
Actual behaviour:
Output isC:\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 CPUWhat 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:
- deactivate UAC on your computer [and restart!]
- go to C:\Users\Public\
- right click on "Public Documents" folder and select
Properties
- select the 'Path' tab
- click 'Move...' and select a (new) folder on drive
D:
calledTestDocuments
- click 'Apply'
- accept to move all files to the new location start the minimal application above
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.
精彩评论