Question

I am making a custom action for an installer. It must read a file stored in CSIDL_COMMON_DOCUMENTS to determine the install directory. (I'm hoping it won't be an issue to change the install directory in a custom action, but that's a different question.)

I see that .NET 4 added CommonDocuments to Environment.SpecialFolder. Unfortunately, I'm stuck with .NET 3.5. What's the next easiest way to get this path?

Was it helpful?

Solution

The easiest way I know of is to P/Invoke the SHGetFolderPath function, which is very likely what the .NET Framework uses internally to retrieve the Environment.SpecialFolders values.

The definition would look like this:

[DllImport("shell32.dll"), CharSet = CharSet.Auto]
static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken,
                                  uint dwFlags, [Out] StringBuilder pszPath);

You'll also need the CSIDL_COMMON_DOCUMENTS constant. Direct from the Windows headers:

const int CSIDL_COMMON_DOCUMENTS = 0x002e;

If you want to force the creation of the folder if it does not already exist, you'll need to pass the CSIDL_FLAG_CREATE flag. That is defined as follows:

const int CSIDL_FLAG_CREATE = 0x8000;

Call it like this:

public static string GetCommonDocumentsFolder()
{
    StringBuilder sb = new StringBuilder();
    int retVal = SHGetFolderPath(IntPtr.Zero,
                                 CSIDL_COMMON_DOCUMENTS | CSIDL_FLAG_CREATE,
                                 IntPtr.Zero,
                                 0,
                                 sb);
    Debug.Assert(retVal >= 0);  // assert that the function call succeeded
    return sb.ToString();
}

Just for your information, the SHGetFolderPath function has been deprecated as of Windows Vista in favor of SHGetKnownFolderPath (the shell team just loves to change these things around). That new function brings with it a new set of identifiers; instead of CSIDL values, it now uses KNOWNFOLDERID values. They recommend that all new applications use the new function.

But considering that you're targeting an old version of the .NET Framework and don't want to upgrade, it's a good bet that you probably have no need to call the latest API function, either. :-)

The old one continues to work just fine in Windows Vista and 7, even if it's implemented internally merely as a thin wrapper over the new function. If it fails in Windows 8, you'll have to segregate your code paths, or finally bite the bullet and upgrade to the latest version of .NET, which handles it all for you.

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