How to Get the Path of a Windows "Special Folder" for a Specific User

How can I get the path of a Windows special folder for a specific user?

I would mount the user's registry hive and look for the path value. Yes, it's a sub-optimal solution, for all the reasons mentioned (poor forwards compatibility, etc.). However, like many other things in Windows, MS didn't provide an API way to do what you want to do, so it's the best option available.

You can get the SID (not GUID) of the user by using LookupAccountName. You can load the user's registry hive using LoadUserProfile, but unfortunately this also requires a user token, which is going to require their password. Fortunately, you can manually load the hive using RegLoadKey into an arbitrary location, read the data, and unload it (I think).

Yes, it's a pain, and yes, it's probably going to break in future versions of Windows. Perhaps by that time MS will have provided an API to do it, back-ported it into older versions of Windows, and distributed it automatically through Windows update... but I wouldn't hold my breath.

P.S. This information intended to augment the information provided in your related question, including disclaimers.

How to get a path to some special folders in windows universal apps without Enviroment

Please use KnownFolders class in Windows.Storage namespace.
For example, to access the PictureLibrary use:

StorageFolder storageFolder = KnownFolders.PicturesLibrary;

StorageFolder in its turn has a read-only property Path. By you should rethink the entire concept of manipulating files with the new API.

For example, to create an image file, you would use the following:

StorageFile file =
await storageFolder.CreateFileAsync("sample.png",
CreationCollisionOption.ReplaceExisting);

You can find more examples on MSDN: KnownFolders class.

Get special folder for different user without credentials

Assuming that it isn't too late to modify the application to support your uninstaller, you could create a folder in ProgramData at install time, and then each time your application starts up it could write a file for the current user containing the path(s) to the directories and/or files in question. (You could possibly even retrofit this in as an update, though it wouldn't work for users that hadn't logged in between the time the update was installed and the time the product was uninstalled.)

You'd need to be a bit careful about security. Non-admin users should only have "create files" access to the folder, which should be set to be non-inheritable. That way they can't see or manipulate one another's files. You might also want to assign inheritable full access to CREATOR OWNER.

You also need validate the saved path information rather than trusting it blindly, or a malicious user could make your uninstaller delete the wrong files. I'd suggest that you check that the file or folder you're about to delete is owned by the same person who owns the file containing the saved path information, and if not, call for help. For additional safety, your application could also tag the files that it creates which should be deleted on uninstall, perhaps by using an alternate data stream.

How to obtain Windows special paths for a user account from a service

I'm no expert on this, but it seems you can use the hToken argument to SHGetFolderPath to pass in another user's token. I think you can create such a token using impersonation.

If that does not work: these folders are in the registry under HKEY_USERS/<user's-sid>/Software/Microsoft/Windows/CurrentVersion/Explorer/Shell Folders. How to get the SID is explained in this question. It's in C# but I think it'll actually be easier in C++.

Check if a given path is a special folder path?

You can use IKnownFolderManager::FindFolderFromPath

Available since Vista.

PS: check out the CComPtr<> class for simpler interfacing with COM.

Here is a sample i just made up, showing how to use it:

#include <atlsafe.h>
#include <Shobjidl.h>
#include <comdef.h>

void PrintKnownFolder( const CComPtr<IKnownFolder>& folder )
{
KNOWNFOLDER_DEFINITION def;
HRESULT hr = folder->GetFolderDefinition( &def );
if( SUCCEEDED(hr) ) {
std::wcout << L"Result: " << def.pszName << std::endl;
FreeKnownFolderDefinitionFields( &def );
} else {
_com_error err(hr);
std::wcout << L"Error while querying GetFolderDefinition: " << err.ErrorMessage() << std::endl;
}
}

class CCoInitialize
{
public:
CCoInitialize() : m_hr(CoInitialize(NULL)) { }
~CCoInitialize() { if (SUCCEEDED(m_hr)) CoUninitialize(); }
operator HRESULT() const { return m_hr; }
private:
HRESULT m_hr;
};

bool test()
{
CCoInitialize co;
CComPtr<IKnownFolderManager> knownFolderManager;
HRESULT hr = knownFolderManager.CoCreateInstance( CLSID_KnownFolderManager );
if( !SUCCEEDED(hr) ) {
_com_error err(hr);
std::wcout << L"Error while creating KnownFolderManager: " << err.ErrorMessage() << std::endl;
return false;
}

CComPtr<IKnownFolder> folder;
hr = knownFolderManager->FindFolderFromPath( L"C:\\Users\\All Users\\Microsoft", FFFP_NEARESTPARENTMATCH, &folder );
if( SUCCEEDED(hr) ) {
PrintKnownFolder(folder);
} else {
_com_error err(hr);
std::wcout << L"Error while querying KnownFolderManager for nearest match: " << err.ErrorMessage() << std::endl;
}

// dispose it.
folder.Attach( NULL );

hr = knownFolderManager->FindFolderFromPath( L"C:\\Users\\All Users\\Microsoft", FFFP_EXACTMATCH, &folder );
if( SUCCEEDED(hr) ) {
PrintKnownFolder(folder);
} else {
_com_error err(hr);
std::wcout << L"Error while querying KnownFolderManager for exact match: " << err.ErrorMessage() << std::endl;
}

return true;
}

CCoInitialize borrowed from The Old New Thing

C++ : How to get actual folder path when the path has special folder names

The Win32 API that expands environment variable references of the form %variable% in strings is ExpandEnvironnmentStrings.

Is there any way to get Environment.SpecialFolder from file path?

You could do it something like this (assuming you want to get the actual enum value for the special folder):

public static Environment.SpecialFolder? FindSpecialFolder(string filePath)
{
filePath = Path.GetFullPath(filePath);

foreach (var folder in Enum.GetValues<Environment.SpecialFolder>())
{
string directory = Environment.GetFolderPath(folder);

if (directory.Length > 0 && filePath.StartsWith(directory, StringComparison.OrdinalIgnoreCase))
return folder;
}

return null;
}

Note that I had to return a nullable because Microsoft failed to follow their own guidelines and didn't include a special "None" zero value in the Environment.SpecialFolder enum that I could return to indicate "not found".

The usage would be something like this:

string filePath = @"C:\Program Files (x86)\text.txt";

var folder = FindSpecialFolder(filePath);

if (folder == null)
Console.WriteLine("No folder found");
else
Console.WriteLine(folder.Value);

If you want both the path and the enum value, you could return them both in a tuple:

public static (Environment.SpecialFolder? specialFolder, string? directory) FindSpecialFolder(string filePath)
{
filePath = Path.GetFullPath(filePath);

foreach (var folder in Enum.GetValues<Environment.SpecialFolder>())
{
string directory = Environment.GetFolderPath(folder);

if (directory.Length > 0 && filePath.StartsWith(directory, StringComparison.OrdinalIgnoreCase))
return (folder, directory);
}

return default;
}

Which you could use like:

var folder = FindSpecialFolder(filePath);

if (folder.specialFolder == null)
Console.WriteLine("No folder found");
else
Console.WriteLine($"{folder.specialFolder.Value}: {folder.directory}");

Actually, it's possible that some of the special folders may be nested underneath other special folders, for example you might have:

C:\Path1\Path2
C:\Path1\Path2\Path3

In that event, the code above will return the first path that it matches, rather than the longest; i.e. looking for "C:\Path1\Path2\Path3\SomeFile.txt" might return the special folder for "C:\Path1\Path2" rather than the one for "C:\Path1\Path2\Path3".

If you want to handle that possibility, you'll have to find the longest matching path, for example:

public static (Environment.SpecialFolder? specialFolder, string? directory) FindSpecialFolder(string filePath)
{
filePath = Path.GetFullPath(filePath);

int longest = 0;
Environment.SpecialFolder? longestFolder = null;
string? longestDir = null;

foreach (var folder in Enum.GetValues<Environment.SpecialFolder>())
{
string directory = Environment.GetFolderPath(folder);

if (directory.Length > longest && filePath.StartsWith(directory, StringComparison.OrdinalIgnoreCase))
{
longestDir = directory;
longestFolder = folder;
longest = directory.Length;
}
}

return (longestFolder, longestDir);
}

And use it like:

var folder = FindSpecialFolder(filePath);

if (folder.specialFolder == null)
Console.WriteLine("No folder found");
else
Console.WriteLine($"{folder.specialFolder.Value}: {folder.directory}");

Another thing to be aware of is that multiple special folders might have the same path. In this case it's not possible to differentiate them, so the code will just return the first match it finds.

Also note the use of filePath = Path.GetFullPath(filePath); to ensure that relative paths are converted to absolute paths, otherwise the matching likely wouldn't work.



Related Topics



Leave a reply



Submit