How to impersonate a user from a service correctly?
You don't need to call ImpersonateLoggedOnUser()
since you are passing the user's token to LoadUserProfile()
. Call ImpersonateLoggedOnUser()
only if you need to call APIs that do not allow you to pass a user token to them.
If you read the rest of the LoadUserProfile()
documentation, it says:
The calling process must have the SE_RESTORE_NAME and SE_BACKUP_NAME privileges.
By impersonating the user you are trying to load a profile for, you are likely losing those privileges. So don't impersonate the user.
Update: Try something like this:
// get the active console session ID of the logged on user
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
if ( dwSessionID == 0xFFFFFFFF )
{
ShowErrorText( "WTSGetActiveConsoleSessionId failed.", GetLastError( ), true );
return;
}
if ( !WTSQueryUserToken( dwSessionID, &hToken ) )
{
ShowErrorText( "WTSQueryUserToken failed.", GetLastError( ), true );
return;
}
// duplicate the token
HANDLE hDuplicated = NULL;
if ( !DuplicateToken( hToken, SecurityImpersonation, &hDuplicated ) )
{
ShowErrorText( "DuplicateToken failed.", GetLastError( ), true );
CloseHandle( hToken );
return;
}
// retrieve the DC name
if ( !GetPrimaryDC( DC ) )
{
ShowErrorText( "GetPrimaryDC failed.", 0, true );
CloseHandle( hDuplicated );
CloseHandle( hToken );
return;
}
PROFILEINFO lpProfileInfo;
ZeroMemory( &lpProfileInfo, sizeof( PROFILEINFO ) );
lpProfileInfo.dwSize = sizeof( PROFILEINFO );
lpProfileInfo.lpUserName = CurrentUser;
// get type of profile. roaming, mandatory or temporary
USER_INFO_4 *UserInfo = NULL;
int ret = GetTypeOfProfile();
if ( ret == 2 )
{
// if roaming profile get the path of it
if ( NetUserGetInfo( DC, CurrentUser, 4, (LPBYTE*)&UserInfo) != NERR_Success )
{
ShowErrorText( "NetUserGetInfo failed.", 0, true );
CloseHandle( hDuplicated );
CloseHandle( hToken );
return;
}
lpProfileInfo.lpProfilePath = UserInfo->usri3_profile;
}
if ( !LoadUserProfile( hDuplicated, &lpProfileInfo ) )
{
ShowErrorText( "LoadUserProfile failed.", GetLastError(), true );
if ( UserInfo )
NetApiBufferFree(UserInfo);
CloseHandle( hDuplicated );
CloseHandle( hToken );
return;
}
if ( UserInfo )
NetApiBufferFree(UserInfo);
ShowErrorText( "LoadUserProfile succeeded.", 0, true );
//do some stuff
if ( !UnloadUserProfile( hDuplicated, lpProfileInfo.hProfile ) )
{
ShowErrorText( "UnloadUserProfile failed.", GetLastError( ), true );
}
else
{
ShowErrorText( "UnloadUserProfile succeeded.", 0, true );
}
CloseHandle( hDuplicated );
CloseHandle( hToken );
As for the Registry, the hProfile
handle is the opened HKEY
for the user's HKEY_CURRENT_USER
tree. Simpy type-cast it from HANDLE
to HKEY
when passing it to Registry API functions. It is already opened, so you do not need to call RegOpenKeyEx()
to open that same key again, but you can use it as the root key when creating/opening subkeys, or reading/writing values in the root key.
Comments
-
kampi almost 2 years
I'm working a service, which should impersonate the logged on user.
My code so far, with basic error handling:
// get the active console session ID of the logged on user if ( !WTSQueryUserToken( WTSGetActiveConsoleSessionId(), &hToken ) ) { ShowErrorText( "WTSQueryUserToken failed.", GetLastError( ), true ); return; } HANDLE hDuplicated; // duplicate the token if ( !DuplicateToken( hToken, SecurityImpersonation, &hDuplicated ) ) { ShowErrorText( "DuplicateToken failed.", GetLastError( ), true ); } else { ShowErrorText( "DuplicateToken succeeded.", 0, true ); } // impersonate the logged on user if ( !ImpersonateLoggedOnUser( hToken ) ) { ShowErrorText( "ImpersonateLoggedOnUser failed.", GetLastError(), true ); return; } // retrieve the DC name if ( !GetPrimaryDC( DC ) ) { ShowErrorText( "GetPrimaryDC failed.", 0, true ); } PROFILEINFO lpProfileInfo; ZeroMemory( &lpProfileInfo, sizeof( PROFILEINFO ) ); lpProfileInfo.dwSize = sizeof( PROFILEINFO ); lpProfileInfo.lpUserName = CurrentUser; // get type of profile. roaming, mandatory or temporary int ret = GetTypeOfProfile(); if ( ret == 2 ) { // if roaming profile get the path of it if ( !GetRoamingProfilePath( DC, CurrentUser, RoamingProfilePath ) ) { ShowErrorText( "Failed to retrieve roaming profile path.", GetLastError(), true ); } } if ( RevertToSelf( ) ) { ShowErrorText( "Impersonation ended successfully.", 0, true ); } if ( !LoadUserProfile( hDuplicated, &lpProfileInfo ) ) { ShowErrorText( "LoadUserProfile failed.", GetLastError(), true ); } else { ShowErrorText( "LoadUserProfile succeeded.", 0, true ); } //do some stuff if ( !UnloadUserProfile( hDuplicated, lpProfileInfo.hProfile ) ) { ShowErrorText( "UnloadUserProfile failed.", GetLastError( ), true ); } else { ShowErrorText( "UnloadUserProfile succeeded.", 0, true ); } if ( !ImpersonateLoggedOnUser( hToken ) ) { ShowErrorText( "ImpersonateLoggedOnUser failed.", GetLastError( ), true ); return; }
According to MSDN:
When a user logs on interactively, the system automatically loads the user's profile. If a service or an application impersonates a user, the system does not load the user's profile. Therefore, the service or application should load the user's profile with LoadUserProfile.
Services and applications that call LoadUserProfile should check to see if the user has a roaming profile. If the user has a roaming profile, specify its path as the lpProfilePath member of PROFILEINFO. To retrieve the user's roaming profile path, you can call the NetUserGetInfo function, specifying information level 3 or 4.
Upon successful return, the hProfile member of PROFILEINFO is a registry key handle opened to the root of the user's hive. It has been opened with full access (KEY_ALL_ACCESS). If a service that is impersonating a user needs to read or write to the user's registry file, use this handle instead of HKEY_CURRENT_USER. Do not close the hProfile handle. Instead, pass it to the UnloadUserProfile function.
If i use my code as it is now, then it works. However is it a little strange, because first i have to impersonate the logged on user, and then end the impersonation, to Load the users profile. If i don't end the impersonation then LoadUserProfile will fail with error 5 ( Access denied ). And after LoadUserProfile succeeded i should impersonate the user again?
So my question is, this meant to be done this way, or i am doing something wrong? Another question is, that if LoadUserProfile succeeded i could use hProfile as a Handle to the logged on users registry. Question is how? Because to use RegOpenKeyEy and RegSetValueEx i need to pass a HKEY, not a HANDLE. So how can i use this Handle?
Thank!
-
kampi over 10 yearsI'm a little confused. I called ImpersonateLoggedonUser at the first time, because i need to get, if the user has a roaming profile. If yes then i need it's path, and of course the username. I need these thing to call LoadUserProfile. If i'm not using ImpersonateLoggedOnUser then how could i get them?
-
Remy Lebeau over 10 yearsRead the
LoadUserProfile()
documentation: "To retrieve the user's roaming profile path, you can call theNetUserGetInfo()
function, specifying information level 3 or 4." You don't need impersonation for that. Also read the comments about Roaming Profiles at the bottom of thePROFILEINFO
documentation. -
kampi over 10 yearsThank you very much for your help! It does work, although i have on last problem. Untul now i used GetUsername, to retrieve the logged on users name. Because i was using ImpersonateLoggedOnUser it gave me back the correct username, which i have to pass to PROFILINFO. But how do i get it now?
-
Remy Lebeau over 10 years
GetUserName()
returns the username associated with the calling thread. That is why you have to use impersonation when callingGetUserName()
from a service. Since you already have the Session ID that the user token comes from, you can useWTSQuerySessionInformation()
to query the session's logged on username. Set theWTSInfoClass
parameter toWTSUserName
. You can also useWTSDomainName
to query the session's logged on domain. -
kampi over 10 yearsOkay, thank you. It is working. However is discovered another Problem. Hopefully the last one. I use GetProfileType() to get the type of the profile. Which was until now working fine, because I used impersonation. Unfortunately, now it returns, that the user has local profile, not roaming. This is also because, it will get the profile type of the calling thread. Do you know some solution, how could I get the logged on users profile type? Thanks in advance!
-
Remy Lebeau over 10 yearsI would ignore
GetProfileType()
and just callNetGetUserInfo()
unconditionally. Especially since theGetProfileType()
documentation says: "If the user profile is not already loaded, the function fails", and you have not loaded the user's profile yet. The user info will either have a roaming profile or it will not. -
kampi over 10 yearsThank you very much for your help. With you assistance I was able to correct my code.
-
c00000fd over 7 years@RemyLebeau: Hey, I know you've asked it a while back. I'm dealing with a similar issue and I was wondering how do you determine the type of profile in
GetTypeOfProfile
function? Namely the roaming user profile. Thanks. -
Remy Lebeau over 7 years@c00000fd: that should have been posted as a new question. But if you re-read my previous comment comment more carefully, I said: "I would ignore
GetProfileType()
and just callNetGetUserInfo()
unconditionally... The user info will either have a roaming profile or it will not". Read theNetGetUserInfo
documentation.