A customer was having trouble with the
SHGetKnownFolderPath
function.
We are calling the
SHGetKnownFolderPath
function. from a service while impersonating a user, but it returnsE_ACCESSDENIED
. It's failing both forFOLDERID_
andProgramData FOLDERID_
:RoamingAppData hr = SHGetKnownFolderPath( FOLDERID_ProgramData, // folder ID 0, // flags nullptr, // token &path); // result
Before we could reply, the customer followed up:
We found that if we save the token that is being used for impersonation and pass it to the
SHGetKnownFolderPath
function. then it works. But we are not sure why is it working now since the call was already being made while impersonating, and the documentation for theSHGetKnownFolderPath
function says that if you want to get the known folder path for the current user, then you passNULL
as the token.
Remember that
the default answer to "Does this work while impersonating?" is No.
When the
SHGetKnownFolderPath
function
says "the current user",
it roughly means "the user under whose identity the process is running",
but more importantly,
it means
the user whose registry is mapped as HKEY_
,
which, as we saw earlier,
is a very tricky proposition for a service that impersonates.
In this case, what's probably happening is that the service is
running as something like SYSTEM, and HKEY_
points to SYSTEM's registry,
and the impersonated user does not have access to SYSTEM's registry,
hence
E_ACCESSDENIED
.
The backstory is that
the SHGetKnownFolderPath
function
(and its close relatives
like SHGetSpecialFolderPath
)
are overwhelmingly used by normal applications that do no impersonation,
so that's the use case they are optimized for.
if you pass NULL
as the token,
then the functions will read from
HKEY_
and cache the results.
If the thread is impersonating, but you fail to pass the impersonation
token to the
SHGetKnownFolderPath
function,
then it might return the (wrong) cached value,
or it might try to read the value from
the (wrong) HKEY_
hive.
Whatever happens, it's probably going to be wrong.
To say, "No, don't use any of your fancy optimizations for
normal applications, because I'm not a normal application.
Get the known folder path for this specific user by
reading from that user's registry."
You might say,
"Well,
the SHGetKnownFolderPath
function
should auto-detect whether it is being called when impersonating
and compensate accordingly."
Maybe, but that means introducing an expensive test to a
hot code path to cover a rare case.
that people shouldn't expect to work anyway because
the default answer to "Does this work while impersonating?" is No.
Instead, the extra work of dealing with impersonation is transferred
to the people who want to get this information while impersonating.
Bonus chatter:
Even if you pass the correct token to
the SHGetKnownFolderPath
function,
you may still run afoul of other requirements.
In particularly, note the sentence
In addition to passing the user's hToken, the registry hive of that specific user must be mounted.
If the registry hive is not mounted,
then there is no way to look up the information in the registry
because, well, it's not in the registry.
You can use the
LoadUserProfile
function to
load the profile into the registry.
Just remember to call
UnloadUserProfile
when you're done.
(See the documentation for details.)