Get special folder for different user without cred

2020-03-26 11:18发布

问题:

I'm writing an uninstaller, and as part of the process, I want to clean up caches, temporary files, etc. for all local users. The app will run elevated for this to work.

The files I'm looking for are found in special folders such as AppData\Local, so I need the paths. For the currently logged in user, this is trivial to do with Environment.GetFolderPath. However, this won't work for a different user.

According to referencesource.microsoft.com, GetFolderPath ultimately calls into SHGetFolderPath, which in turn wraps SHGetKnownFolderPath. Both of those Win32 APIs do have an optional hToken parameter which lets me specify an account token.

And, indeed, this will work:

private static string GetFolderPath(Guid knownfolderid, string user, string domain, string password)
{
    const int LOGON32_PROVIDER_DEFAULT = 0;
    const int LOGON32_LOGON_INTERACTIVE = 2;

    SafeTokenHandle safeTokenHandle;

    if (!LogonUser(user, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeTokenHandle))
        throw new System.Security.Authentication.InvalidCredentialException();

    IntPtr pPath;
    SHGetKnownFolderPath(knownfolderid,
                         0,
                         safeTokenHandle.DangerousGetHandle(),
                         out pPath);
    string result = System.Runtime.InteropServices.Marshal.PtrToStringUni(pPath);
    System.Runtime.InteropServices.Marshal.FreeCoTaskMem(pPath);

    return result;
}

…but it's impractical for my uses, of course — I don't want the admin who uninstalls something to have to know and supply each username and password.

So:

  • is there a way to get an appropriate access token without needing to know the user's credentials?
  • failing that, is there a different way to accomplish my goal? Reading the information from the registry seems unsafe, as does hardcoding the folder names, of course.

回答1:

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.