DelphiFAQ Home Search:

Accessing HKCU registry from within service app

 

comments5 comments. Current rating: 5 stars (2 votes). Leave comments and/ or rate it.

Question:

I need to access HKEY_CURRENT_USER for the currently logged in user from my application which runs as a service. How can I do this?

Answer:

You need to use the ImpersonateLoggedOnUser() function which lets your calling thread impersonate the security context of a logged-on user. The user is represented by a token handle. The impersonation lasts until the thread exits or until it calls RevertToSelf().

Follow these steps:

  1. Locate a process in the users' session, specifically, explorer.exe.
  2. Use the process handle with OpenProcessToken() to get an access token to that process.
  3. Call ImpersonateLoggedOnUser() with the process token within a worker thread so that your worker thread behaves as the impersonated (logged-on) user. Your worker thread should be able to modify the HKCU registry without problems.


Comments:

2008-09-15, 05:20:04
anonymous from Turkey  
thanks
2008-11-17, 03:03:04   (updated: 2008-11-17, 03:14:14)
[hidden] from Rostov-na-donu, Russian Federation  
rating
Also while impersonating an user you have to use RegOpenCurrentUser() instead of RegCreateKeyEx(HKEY_CURRENT_USER, ...).
I lost some time figuring this out.

And I use WTSQueryUserToken() to get user token for impersonation. It is available since WinXP.
This image was also posted here:
Accessing HKCU registry from within service app



Keywords:
2008-12-02, 05:24:22
anonymous from Belgium  
Hi,

my service does the same thing as you explained, but the problem now is, my service starts up with the system. When doing so, it crashes sometimes and slows down the PC bootup. I guess, the service couldnt identify explorer.exe, while starting up. The reason could be that, services has high priority and hence starts up before the user login and hence gets hanged. How can i solve this issue?

Thanks in advance,

Regards,
Gomathy
2008-12-04, 02:11:59
[hidden] from Rostov-na-donu, Russian Federation  
Gomathy, is it possible at all?
I think there is no current user until someone logs on (i.e. no HKCU at bootup time). IMHO you should catch user logon event (SERVICE_CONTROL_SESSIONCHANGE / WTS_SESSION_LOGON). Search MSDN for WTSSESSION_NOTIFICATION.
This image was also posted here:
Accessing HKCU registry from within service app



Keywords:
2009-04-07, 06:48:02
59eatonroad@gmail.com from London, United Kingdom  
rating
Hi,

Explorer only exists when logged into a desktop session under Windows, whereas services can exist without anyone physically logged in. Also there could be multiple explorer.exe processes if this machine is a terminal server..

unit xbwWinLogon;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
Windows,
TLHelp32,
SysUtils,
Classes;

const
wtsapi = 'wtsapi32.dll';
advapi32 = 'advapi32.dll';
userenvlib = 'userenv.dll';
TOKEN_ADJUST_SESSIONID = $0100;
{$EXTERNALSYM TOKEN_ADJUST_SESSIONID}
SE_DEBUG_NAME = 'SeDebugPrivilege';
{$EXTERNALSYM SE_DEBUG_NAME}

type
_TOKEN_INFORMATION_CLASS = (TokenInfoClassPad0, TokenUser, TokenGroups,
TokenPrivileges, TokenOwner, TokenPrimaryGroup, TokenDefaultDacl, TokenSource,
TokenType, TokenImpersonationLevel, TokenStatistics, TokenRestrictedSids,
TokenSessionId, TokenGroupsAndPrivileges, TokenSessionReference,
TokenSandBoxInert, TokenAuditPolicy, TokenOrigin);

{ kernel32 }
TWTSGetActiveConsoleSessionId = function : DWORD; stdcall;
TProcessIdToSessionId = function (dwProcessId: DWORD; var pSessionId: DWORD): BOOL; stdcall;
{ wtsapi }
TWTSQueryUserToken = function (SessionId: ULONG; var phToken: THANDLE): BOOL; stdcall;
{ advpai32 }
TSetTokenInformation = function (TokenHandle: THANDLE; TokenInformationClass: _TOKEN_INFORMATION_CLASS;
TokenInformation: Pointer; TokenInformationLength: DWORD): BOOL; stdcall;
TAdjustTokenPrivileges = function (TokenHandle: THANDLE; DisableAllPrivileges: BOOL;
NewState: Pointer; BufferLength: DWORD;
PreviousState: Pointer; ReturnLength: LPDWORD): BOOL; stdcall;
{ userenvlib }
TCreateEnvironmentBlock = function (lpEnvironment: Pointer; hToken: THANDLE; bInherit: BOOL): BOOL; stdcall;

var WTSGetActiveConsoleSessionId: TWTSGetActiveConsoleSessionId=ni=nil;
ProcessIdToSessionId:TProcessIdToSessionId=nil;
WTSQueryUserToken:TWTSQueryUserToken=nil;
SetTokenInformation:TSetTokenInformation=nil;
AdjustTokenPrivileges:TAdjustTokenPrivileges=nil;
CreateEnvironmentBlock:TCreateEnvironmentBlock=nil;

function xbwSessionNumber: Integer;
function xbwActiveConsole: Integer;
function xbwGetProcessID(strProcess: String; iSessionID: Integer = -1): DWORD;
procedure xbwExecProcess(strParameters: String; strConfig: String = ''); { <<< starts command line under user process }
function xbwStartProcess(strProcess: String; bLocalSystem: Boolean = True; iSessionID: Integer = -1): Boolean;
function xbwImpersonateLoggedOnUser: Boolean;
function xbwRevertToSelf: Boolean;

implementation

uses IniFiles, SHFolder, Forms, CMCConsts;

var
LibsLoaded:integer=0;
FhUserTokenDup: THandle; { used for user impersonation }

function GetProcedureAddress(var P: Pointer; const ModuleName, ProcName: string):boolean;
var
ModuleHandle: HMODULE;
begin
if not Assigned(P) then
begin
ModuleHandle := GetModuleHandle(PChar(ModuleName));
if ModuleHandle = 0 then
ModuleHandle := LoadLibrary(PChar(ModuleName));
if ModuleHandle <> 0 then
P := Pointer(GetProcAddress(ModuleHandle, PChar(ProcName)));
Result:=Assigned(P);
end
else
Result:=True;
end;

function InitProcLibs:boolean;
begin
if LibsLoaded>0 then
Result:=True
else if LibsLoaded<0 then
Result:=False
else
begin
LibsLoaded:=-1;
if GetProcedureAddress(@WTSGetActiveConsoleSessionId, kernel32, 'WTSGetActiveConsoleSessionId') and
GetProcedureAddress(@ProcessIdToSessionId, kernel32, 'ProcessIdToSessionId') and
GetProcedureAddress(@WTSQueryUserToken, wtsapi, 'WTSQueryUserToken') and
GetProcedureAddress(@SetTokenInformation, advapi32, 'SetTokenInformation') and
GetProcedureAddress(@AdjustTokenPrivileges, advapi32, 'AdjustTokenPrivileges') and
GetProcedureAddress(@CreateEnvironmentBlock, userenvlib, 'CreateEnvironmentBlock') then
LibsLoaded:=1;
Result:=LibsLoaded=1;
end;
end;

function xbwSessionNumber: Integer;
var dwSessionID: DWord;
begin
Result := 0;
if not InitProcLibs then Exit;
ProcessIdToSessionId(GetCurrentProcessId(), dwSessionID);
Result := dwSessionID;
end;

function xbwActiveConsole: Integer;
begin
Result := 0;
if not InitProcLibs then Exit;
Result := WTSGetActiveConsoleSessionId;
end;

function xbwGetProcessID(strProcess: String; iSessionID: Integer = -1): DWORD;
var dwSessionId, winlogonSessId: DWord;
hsnap: THandle;
procEntry: TProcessEntry32;
myPID: Cardinal;
begin
Result := 0;
if not InitProcLibs then Exit;
{ check running processes and return ID of process in current session... }
if iSessionID = -1 then
dwSessionId := WTSGetActiveConsoleSessionId
else
dwSessionId := iSessionID;
hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap = INVALID_HANDLE_VALUE) then Exit;
strProcess:=UpperCase(ExtractFileName(strProcess));
myPID:= GetCurrentProcessId;
procEntry.dwSize := sizeof(TProcessEntry32);
if (not Process32First(hSnap, procEntry)) then Exit;
repeat
if (procEntry.th32ProcessID<>myPID) and ((UpperCase(procEntry.szExeFile) = strProcess) or
(UpperCase(ExtractFileName(procEntry.szExeFile)) = strProcess)) then
begin
winlogonSessId := 0;
if (ProcessIdToSessionId(procEntry.th32ProcessID, winlogonSessId) and (winlogonSessId = dwSessionId)) then
begin
Result := procEntry.th32ProcessID;
break;
end;
end;
until (not Process32Next(hSnap, procEntry));
end;

procedure xbwExecProcess(strParameters: String; strConfig: String = '');

function GetSpecialPath(csidl: Integer): String;
var i: Integer;
begin
SetLength(Result, MAX_PATH);
SHGetFolderPath(0, csidl or CSIDL_FLAG_CREATE, 0, 0, PChar(Result));
i := Pos(#0, Result);
if i > 0 then SetLength(Result, Pred(i));
end;

var strCommonAppData, strResult: String;
iLoop: Integer;
begin
{ execute command using lower process executable... }
strCommonAppData IncludeTrailingBackslash( GetSpecialPath(CSIDL_COMMON_APPDATA)TA)) + STR_PRODUCT + '\';
with TIniFile.Create(strCommonAppData + INI_SESSION) do
try
WriteString(STR_PRODUCT, INI_EXEC, strParameters);
{ collect config into INI transfer... }
if strConfig <> '' then
begin
strConfig := StringReplace(strConfig, #13, '&RC&', [rfReplaceAll]);
strConfig := StringReplace(strConfig, ',', '&CM&', [rfReplaceAll]);
with TStringList.Create do
try
StrictDelimiter := True;
Delimiter := '|';
DelimitedText := strConfig;
for iLoop := 0 to Count -1 do
WriteString(STR_PRODUCT, Names[iLoop], ValueFromIndex[iLoop]);
finally
Free;
end;
end;
{ start process as standard user... }
xbwStartProcess(ExtractFilePath(Application.ExeName) + 'CMCProcess.exe', False, xbwSessionNumber);
Sleep(1000);
{ check result... }
strResult := ReadString(STR_PRODUCT, INI_RESULT, '');
if strResult <> '' then
raise Exception.Create(strResult);
finally
Free;
end;
end;

function xbwStartProcess(strProcess: String; bLocalSystem: Boolean = True; iSessionID: Integer = -1): Boolean;
var pi: PROCESS_INFORMATION;
si: STARTUPINFO;
winlogonPid, dwSessionId: DWord;
hUserToken, hUserTokenDup, hPToken, hProcess: THANDLE;
dwCreationFlags: DWORD;
tp: TOKEN_PRIVILEGES;
lpenv: pointer;
bError: Boolean;
strClone: String;
begin
{ start process as elevated by cloning existing process, as we're running as admin... }
Result := True;
bError := False;
if not InitProcLibs then Exit;
if bLocalSystem then strClone := 'winlogon.exe' else strClone := 'explorer.exe';
winlogonPid := xbwGetProcessID(strClone, iSessionID);
try
{ get user token for winlogon and duplicate it... (this gives us admin rights) }
dwSessionId := WTSGetActiveConsoleSessionId();
WTSQueryUserToken(dwSessionId, hUserToken);
dwCreationFlags := NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE;
ZeroMemory(@si, sizeof(STARTUPINFO));
si.cb := sizeof(STARTUPINFO);
si.lpDesktop := PChar('Winsta0\Default');
ZeroMemory(@pi, sizeof(pi));
hProcess := OpenProcess(MAXIMUM_ALLOWED, FALSE, winlogonPid);
if (not OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY or TOKEN_DUPLICATE or
TOKEN_ASSIGN_PRIMARY or TOKEN_ADJUST_SESSIONID or TOKEN_READ or TOKEN_WRITE, hPToken)) then
bError := True;
if (not LookupPrivilegeValue(nil, SE_DEBUG_NAME, tp.Privileges[0].Luid)) then
bError := True;
tp.PrivilegeCount := 1;
tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, Nil, SecurityIdentification, TokenPrimary, hUserTokenDup);
{ adjust token privilege }
SetTokenInformation(hUserTokenDup, TokenSessionId, pointer(dwSessionId), sizeof(DWORD));
if (not AdjustTokenPrivileges(hUserTokenDup, FALSE, @tp, sizeof(TOKEN_PRIVILEGES), nil, nil)) then bError := True;
if (GetLastError() = ERROR_NOT_ALL_ASSIGNED) then bError := True;
lpEnv := nil;
if (CreateEnvironmentBlock(lpEnv, hUserTokenDup,TRUE)) then
dwCreationFlags := dwCreationFlags or CREATE_UNICODE_ENVIRONMENT
else
lpEnv := nil;
{ launch the process in the client's logon session... }
if not CreateProcessAsUser(hUserTokenDup, nil, PChar(strProcess), nil, nil, FALSE,
dwCreationFlags, lpEnv, PChar(ExtractFilePath(strProcess)), si, pi) then bError := True;
{ perform all the close handles tasks... }
try
CloseHandle(hProcess);
CloseHandle(hUserToken);
CloseHandle(hUserTokenDup);
CloseHandle(hPToken);
except
{}
end;
except
bError := True;
end;
Result := not bError;
end;

function xbwImpersonateLoggedOnUser: Boolean;
var pi: PROCESS_INFORMATION;
si: STARTUPINFO;
winlogonPid, dwSessionId: DWord;
hUserToken, hPToken, hProcess: THANDLE;
tp: TOKEN_PRIVILEGES;
bError: Boolean;
strClone: String;
begin
{ start process as elevated by cloning existing process, as we're running as admin... }
Result := True;
bError := False;
if not InitProcLibs then Exit;
strClone := 'explorer.exe';
winlogonPid := xbwGetProcessID(strClone);
try
{ get user token for winlogon and duplicate it... (this gives us admin rights) }
dwSessionId := WTSGetActiveConsoleSessionId();
WTSQueryUserToken(dwSessionId, hUserToken);
ZeroMemory(@si, sizeof(STARTUPINFO));
si.cb := sizeof(STARTUPINFO);
si.lpDesktop := PChar('Winsta0\Default');
ZeroMemory(@pi, sizeof(pi));
hProcess := OpenProcess(MAXIMUM_ALLOWED, FALSE, winlogonPid);
if (not OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY or TOKEN_DUPLICATE or
TOKEN_ASSIGN_PRIMARY or TOKEN_ADJUST_SESSIONID or TOKEN_READ or TOKEN_WRITE, hPToken)) then
bError := True;
if (not LookupPrivilegeValue(nil, SE_DEBUG_NAME, tp.Privileges[0].Luid)) then
bError := True;
tp.PrivilegeCount := 1;
tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, Nil, SecurityIdentification, TokenPrimary, FhUserTokenDup);
{ adjust token privilege }
SetTokenInformation(FhUserTokenDup, TokenSessionId, pointer(dwSessionId), sizeof(DWORD));
if (not AdjustTokenPrivileges(FhUserTokenDup, FALSE, @tp, sizeof(TOKEN_PRIVILEGES), nil, nil)) then bError := True;
if (GetLastError() = ERROR_NOT_ALL_ASSIGNED) then bError := True;

{ do the impersonation... }
ImpersonateLoggedOnUser(FhUserTokenDup);

{ perform all the close handles tasks... }
try
CloseHandle(hProcess);
CloseHandle(hUserToken);
// CloseHandle(FhUserTokenDup); { this is closed later... }
CloseHandle(hPToken);
except
{}
end;
except
bError := True;
end;
Result := not bError;
end;

function xbwRevertToSelf: Boolean;
begin
RevertToSelf;
CloseHandle(FhUserTokenDup);
end;

end.

Enjoy!

 

 

NEW: Optional: Register   Login
Email address (not necessary):

Rate as
Hide my email when showing my comment.
Please notify me once a day about new comments on this topic.
Please provide a valid email address if you select this option, or post under a registered account.
 

Show city and country
Show country only
Hide my location
You can mark text as 'quoted' by putting [quote] .. [/quote] around it.
Please type in the code:

Please do not post inappropriate pictures. Inappropriate pictures include pictures of minors and nudity.
The owner of this web site reserves the right to delete such material.

photo Add a picture: