Thanks, it works now.
Here is the full code fromm a test service:
This is what I wanted to do:
procedure TWorkerThread.SetControlPanelAccess(Access: Boolean);
const
cExplorerPolicies = 'Software\Microsoft\Windows\CurrentVersion\Policies\Explorer'; // No leading backslash
begin
if not xbwImpersonateLoggedOnUser then begin
{ log error }
exit;
end;
if Access then xbwWriteCurrUserRegKey(cExplorerPolicies,'NoControlPanel',0)
else xbwWriteCurrUserRegKey(cExplorerPolicies,'NoControlPanel',1);
xbwRevertToSelf;
end; { SetControlPanelAccess }
With the xbw.... procs in:
(the Write2Log calls are just some log calls for testing }
unit xbwLogin;
{$WARN SYMBOL_PLATFORM OFF}
(* This is a unit picked from
* http://www.delphifaq.com/faq/delphi_windows_API/f663.shtml
* We only need xbwImpersonateLoggedOnUser and xbwRevertToSelf.
* Other procedures read from an INI file which will not be applicable,
* so before using them, update and test this code (also the defined Consts)
*)
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;
TWTSQueryUserToken = function (SessionId: ULONG; var phToken: THANDLE): BOOL; stdcall;
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;
TRegOpenCurrentUser = function(samDesired: REGSAM; var phkResult: HKEY): DWORD; stdcall;
{ userenvlib }
TCreateEnvironmentBlock = function (lpEnvironment: Pointer; hToken: THANDLE; bInherit: BOOL): BOOL; stdcall;
var
WTSGetActiveConsoleSessionId : TWTSGetActiveConsoleSessionId=nil;
ProcessIdToSessionId : TProcessIdToSessionId=nil;
WTSQueryUserToken : TWTSQueryUserToken=nil;
SetTokenInformation : TSetTokenInformation=nil;
AdjustTokenPrivileges : TAdjustTokenPrivileges=nil;
CreateEnvironmentBlock : TCreateEnvironmentBlock=nil;
RegOpenCurrentUser : TRegOpenCurrentUser=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;
procedure xbwRevertToSelf;
procedure xbwWriteCurrUserRegKey(NameOfKey,StringName: PChar; IntValue: DWORD);
implementation
uses IniFiles, SHFolder, Forms,
uviselog;
const
STR_PRODUCT = 'Vise';
INI_SESSION = 'Sessie';
INI_EXEC = 'Execute';
INI_RESULT = 'Result';
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; { GetProcedureAddress }
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(@RegOpenCurrentUser, advapi32, 'RegOpenCurrentUser') and
GetProcedureAddress(@CreateEnvironmentBlock, userenvlib, 'CreateEnvironmentBlock') then
LibsLoaded := 1;
Result := ( LibsLoaded=1);
end;
end; { InitProcLibs }
function xbwSessionNumber: Integer;
var dwSessionID: DWord;
begin
Result := 0;
if not InitProcLibs then Exit;
ProcessIdToSessionId(GetCurrentProcessId(), dwSessionID);
Result := dwSessionID;
end; { xbwSessionNumber }
function xbwActiveConsole: Integer;
begin
Result := 0;
if not InitProcLibs then Exit;
Result := WTSGetActiveConsoleSessionId;
end;
function xbwGetProcessID(strProcess: String; iSessionID: Integer = -1): DWORD;
(* Returns the process ID of process StrProcess in session iSessionID.
* If iSessionID is omitted, the active console session. *)
var
dwSessionId,
winlogonSessId: DWord;
hsnap : THandle;
procEntry : TProcessEntry32;
myPID : Cardinal;
begin
Result := 0;
if not InitProcLibs then Exit;
if iSessionID = -1 then
// Get Remote Desktop Services session that is currently attached to the physical console. The physical console is the monitor, keyboard, and mouse.
dwSessionId := WTSGetActiveConsoleSessionId
else
dwSessionId := iSessionID;
// Check running processes and return ID of process in current session
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
if (ProcessIdToSessionId(procEntry.th32ProcessID, winlogonSessId) and (winlogonSessId = dwSessionId)) then begin
Result := procEntry.th32ProcessID;
break;
end;
end;
until (not Process32Next(hSnap, procEntry));
end; { xbwGetProcessID }
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; { GetSpecialPath }
var
strCommonAppData,
strResult : String;
iLoop : Integer;
TS : TStringList;
begin { Main xbwExecProcess }
{ Execute command using lower process executable... }
strCommonAppData := IncludeTrailingBackslash( GetSpecialPath(CSIDL_COMMON_APPDATA)) + 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]);
TS := TStringList.Create;
with TS do
try
Delimiter := '|';
DelimitedText := strConfig;
for iLoop := 0 to Count -1 do
WriteString(STR_PRODUCT, Names[iLoop], Strings[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; { xbwExecProcess }
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
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; { xbwStartProcess }
function GLE: String;
var LE: Integer;
begin
LE := GetlastError;
Result := '(' + InttoStr(LE) + ') ' + SysErrorMessage(LE);
end;
function xbwImpersonateLoggedOnUser: Boolean;
var
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 begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwImpersonateLoggedOnUser InitProcLibs failed');
Exit;
end;
// Get explorer.exe process ID:
strClone := 'explorer.exe';
winlogonPid := xbwGetProcessID(strClone);
Write2Log(AppDirFromReg + 'DoTheWork.log','winLogonPiD: ' + inttostr(winlogonpid));
try
// Get user token for winlogon:
dwSessionId := WTSGetActiveConsoleSessionId;
Write2Log(AppDirFromReg + 'DoTheWork.log','dwSessionId: ' + inttostr(dwSessionId));
// Get the primary access token of the logged-on user specified by the session ID
if not WTSQueryUserToken(dwSessionId, hUserToken) then
begin
Write2Log(AppDirFromReg + 'DoTheWork.log','WTSQueryUserToken error: ' + GLE);
Write2Log(AppDirFromReg + 'DoTheWork.log','hUserToken: ' + inttostr(hUserToken));
// Open the existing local process object identified by winlogonPid
hProcess := OpenProcess(MAXIMUM_ALLOWED, FALSE, winlogonPid);
Write2Log(AppDirFromReg + 'DoTheWork.log','hProcess: ' + inttostr(hProcess));
// Open the access token associated with a process, result is put in hPToken
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
Write2Log(AppDirFromReg + 'DoTheWork.log','OpenProcessToken error: ' + GLE);
// Retrieve the locally unique identifier (LUID) used on a specified system to locally represent the specified privilege name.
// Put it in the TOKEN_PRIVILEGES structure tp
if (not LookupPrivilegeValue(nil, SE_DEBUG_NAME, tp.Privileges[0].Luid)) then
Write2Log(AppDirFromReg + 'DoTheWork.log','LookupPrivilegeValue error: ' + GLE);
bError := True;
end;
// Set other tp params:
tp.PrivilegeCount := 1;
tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
// Create a new access token FhUserTokenDup that duplicates the existing token hPToken
if not DuplicateTokenEx(hPToken,
MAXIMUM_ALLOWED, // Request all access rights that are valid for the caller
Nil, // Token gets a default security descriptor
SecurityIdentification, // Value from the SECURITY_IMPERSONATION_LEVEL enumeration that indicates the impersonation level of the new token
// http://msdn.microsoft.com/en-us/library/aa379572(VS.85).aspx
TokenPrimary, // Value from the TOKEN_TYPE enumeration
FhUserTokenDup) then
begin
Write2Log(AppDirFromReg + 'DoTheWork.log','DuplicateTokenEx error: ' + GLE);
// Overwrite information for the FhUserTokenDup token (Adjust token privilege):
if not SetTokenInformation(FhUserTokenDup,
TokenSessionId, // TOKEN_INFORMATION_CLASS enumeration type contains values that specify the type of information being assigned
// TokenSessionId = You will receive a DWORD value that indicates the Terminal Services session identifier that is associated with the token
@dwSessionId,
sizeof(DWORD)) then
begin
{ ERROR 998 Invalid access to memory location. }
Write2Log(AppDirFromReg + 'DoTheWork.log','SetTokenInformation error: ' + GLE);
// Now adjust the priviliges of the duplicated token with the settings in tp:
if (not AdjustTokenPrivileges(FhUserTokenDup, FALSE, @tp, sizeof(TOKEN_PRIVILEGES), nil, nil)) then
Write2Log(AppDirFromReg + 'DoTheWork.log','AdjustTokenPrivileges error: ' + GLE);
bError := True;
end;
if (GetLastError() = ERROR_NOT_ALL_ASSIGNED) then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwImpersonateLoggedOnUser error ERROR_NOT_ALL_ASSIGNED');
bError := True;
end;
// Finally, do the impersonation:
if not ImpersonateLoggedOnUser(FhUserTokenDup) then
begin
Write2Log(AppDirFromReg + 'DoTheWork.log','ImpersonateLoggedOnUser error: ' + GLE);
bError := True;
end;
{ perform all the close handles tasks... }
try
CloseHandle(hProcess);
CloseHandle(hUserToken);
// CloseHandle(FhUserTokenDup); { This is closed later in xbwRevertToSelf }
CloseHandle(hPToken);
except
on E:Exception do
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwImpersonateLoggedOnUser error (1): ' + E.Message);
end;
except
on E:Exception do begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwImpersonateLoggedOnUser error (2): ' + E.Message);
bError := True;
end;
end;
Result := not bError;
end; { xbwImpersonateLoggedOnUser }
procedure xbwRevertToSelf;
begin
RevertToSelf;
CloseHandle(FhUserTokenDup);
end; { xbwRevertToSelf }
procedure xbwWriteCurrUserRegKey(NameOfKey,StringName: PChar; IntValue: DWORD);
(* We must use RegOpenCurrentUser instead of RegOpenKey(Ex) (TRegistry).
* These functions don't work with impersonation:
* http://msdn.microsoft.com/en-us/library/ms685145(VS.85).aspx *)
var
phkRoot,
phkKey : HKEY;
Res : Integer;
begin
Res := RegOpenCurrentUser(KEY_ALL_ACCESS,phkRoot);
If Res <> ERROR_SUCCESS then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwWriteCurrUserRegKey/RegOpenCurrentUser error: (' + IntToStr(Res) + '): ' + SysErrorMessage(Res));
Exit;
end;
Res := RegOpenKeyEx(phkRoot,NameOfKey,0,KEY_ALL_ACCESS,phkKey);
if Res <> ERROR_SUCCESS then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwWriteCurrUserRegKey/RegOpenKeyEx error: (' + IntToStr(Res) + '): ' + SysErrorMessage(Res));
Exit;
end;
Res := RegSetValueEx(phkKey,StringName,0,REG_DWORD,@IntValue,SizeOf(DWORD));
if Res <> ERROR_SUCCESS then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwWriteCurrUserRegKey/RegSetValueEx error: (' + IntToStr(Res) + '): ' + SysErrorMessage(Res));
Exit;
end;
Res := RegCloseKey(phkKey);
if Res <> ERROR_SUCCESS then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwWriteCurrUserRegKey/RegCloseKey error: (' + IntToStr(Res) + '): ' + SysErrorMessage(Res));
Exit;
end;
end; { xbwWriteCurrUserRegKey }
end.