Windows 7, Vista, Server 2008, UAC активирован
Для выполнения некоторых действий по установке необходимо указать права администратора. После этого я хочу, чтобы моя программа продолжала работать с правами, отличными от admin.
Как перезапустить его с правами администратора?
P.S.
Моя программа переустанавливается. Я не хочу распространять для него дополнительные программы. Итак, мои шаги:
Thanx to Кейт Грегори для справки.
В Delphi есть рабочий код:
function RunAsUser(CommandLine, WorkDirectory: string; Wait: Boolean): Boolean;
const
TOKEN_ADJUST_SESSIONID = $0100;
dwTokenRights = TOKEN_QUERY or TOKEN_ASSIGN_PRIMARY or TOKEN_DUPLICATE or TOKEN_ADJUST_DEFAULT or TOKEN_ADJUST_SESSIONID;
var
WExe, WCmdLine, wCurrDir: WideString;
hProcessToken, dwLastErr, retLength, hwnd, dwPID, hShellProcess, hShellProcessToken, hPrimaryToken: Cardinal;
tkp: TOKEN_PRIVILEGES;
PI: TProcessInformation;
SI: TStartupInfoW;
begin
Result:= False;
hShellProcessToken:= 0;
hPrimaryToken:= 0;
hShellProcess:= 0;
if WorkDirectory = '' then WorkDirectory:= GetCurrentDir;
Wexe:= SeparateText(CommandLine, ' ');
WCmdLine:= CommandLine;
wCurrDir:= WorkDirectory;
// Enable SeIncreaseQuotaPrivilege in this process. (This won't work if current process is not elevated.)
if not OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hProcessToken) then Exit;
tkp.PrivilegeCount:= 1;
LookupPrivilegeValueW(nil, SE_INCREASE_QUOTA_NAME, tkp.Privileges[0].Luid);
tkp.Privileges[0].Attributes:= SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hProcessToken, FALSE, tkp, 0, nil, retLength);
dwLastErr:= GetLastError();
CloseHandle(hProcessToken);
if (dwLastErr <> ERROR_SUCCESS) then Exit;
// Get an HWND representing the desktop shell.
// CAVEATS: This will fail if the shell is not running (crashed or terminated), or the default shell has been
// replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and
// restarted elevated.
hwnd:= GetShellWindow();
if hwnd = 0 then Exit;
// Get the PID of the desktop shell process.
GetWindowThreadProcessId(hwnd, dwPID);
if dwPID = 0 then Exit;
// Open the desktop shell process in order to query it (get the token)
hShellProcess:= OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID);
if hShellProcess = 0 then Exit;
// From this point down, we have handles to close, so make sure to clean up.
try
// Get the process token of the desktop shell.
if not OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, hShellProcessToken) then Exit;
// Duplicate the shell process token to get a primary token.
// Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation).
if not DuplicateTokenEx(hShellProcessToken, dwTokenRights, nil, SecurityImpersonation, TokenPrimary, hPrimaryToken) then Exit;
SI.cb:= SizeOf(SI);
FillChar(SI, SI.cb, 0);
SI.wShowWindow:= SW_SHOWNORMAL;
SI.dwFlags:= STARTF_USESHOWWINDOW;
// Start the target process with the new token.
Result:= CreateProcessWithTokenW(
hPrimaryToken,
0,
PWideChar(WExe),
PWideChar(wCmdLine),
0,
nil,
PWideChar(wCurrDir),
@si,
@pi);
if not Result then Exit;
if Wait then
while MsgWaitForMultipleObjects(1, PI.hProcess, False, INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0 do
ProcessMessages;
CloseHandle(PI.hProcess);
finally
// Clean up resources
CloseHandle(hShellProcessToken);
CloseHandle(hPrimaryToken);
CloseHandle(hShellProcess);
end;
end;
В UAC выполнение чего-либо "при первом запуске" теперь сильно обескуражено. Кроме того, программы, которые обновляют себя, используя технику "roll-your-own", усложнят работу. Вы говорите, что не хотите распространять дополнительные программы, но в UAC у вас действительно мало выбора. Либо все ваше приложение запускается каждый раз при повышении (раздражает пользователя) в случае, если вам нужно сделать что-то административное, или вы разделите его на две части, и запустите один повышающийся время от времени, а другой - не повышающийся все время.
Один из способов разделить его - написать установщик, который поднимает, и обычное приложение, а это не так. Это работает для людей, которые устанавливают один раз, делают некоторые вещи при первом запуске (вы переносите эти вещи в установщик), а затем выполняются. Вы говорите, что ваше приложение обновляется. Поэтому вам нужно переместить этот код в отдельный exe и поместить манифест на этот exe, у которого есть requireAdministrator. Затем ваше основное приложение запустит (используя ShellExecute) обновление exe, когда появится новое обновление.
Я думаю, что вы ошибетесь в этом. На мой взгляд, вы должны сделать одно из следующих действий:
или
Изменить: Таким образом, шаги:
Для запроса высоты нет необходимости перезапуска. Возможно, вы захотите использовать этот способ при работе в средах до Vista.
Здесь используется простой метод перезапуска;
procedure Restart(RunAs: Boolean);
var
i: Integer;
Params: string;
begin
// Close handle to Mutex or any such thing if only one inst. is allowed
// Prepare to re-pass parameters if the application uses them
Params := '';
for i := 1 to ParamCount do
Params := Params + ' "' + ParamStr(i) + '"';
Application.MainForm.Close;
Application.ProcessMessages;
if RunAs then
ShellExecute(0, 'runas', PChar(ParamStr(0)), PChar(Params), '', SW_SHOW)
else
ShellExecute(0, 'open', PChar(ParamStr(0)), PChar(Params), '', SW_SHOW);
end;