delphi uac elevation

Delphi: solicite elevación UAC cuando sea necesario



elevation (4)

Necesitamos cambiar algunas configuraciones a HKEY_LOCAL_MACHINE en el tiempo de ejecución.

¿Es posible solicitar elevación de uac si es necesario en tiempo de ejecución, o tengo que iniciar un segundo proceso elevado para hacer ''el trabajo sucio''?


Generalmente, poner el texto "Configurar" o "Instalar" en algún lugar de su nombre EXE es suficiente para hacer que Windows se ejecute con privilegios elevados automáticamente, y vale la pena hacerlo si es una utilidad de configuración que está escribiendo, ya que es muy fácil de hacer.

Ahora estoy teniendo problemas en Windows 7, cuando no estoy conectado como administrador, y tengo que usar el botón derecho como Administrador de ejecución cuando se ejecuta manualmente (ejecutar el programa a través del asistente de instalación de Wise todavía está bien)

Aunque veo que Delphi 10.1 Berlin tiene una nueva opción muy fácil de usar en Opciones de proyecto | Solicitud. Simplemente marque Permitir privilegios de administrador y el manifiesto estará listo para usted, ¡así de fácil!

NÓTESE BIEN. asegúrese de realizar este tipo de cambios solamente a través de un programa de instalación separado, ejecutar su aplicación con privilegios elevados todo el tiempo puede causar problemas con otras cosas, por ejemplo, correo electrónico, donde el perfil de correo predeterminado ya no se recupera.

Edición: enero de 2018: desde que se escribió esta respuesta en agosto de 2017, parece haber salido una gran cantidad de actualizaciones de Windows, que ahora requieren que el usuario haga clic derecho y ejecutar como administrador en casi todo, incluso en el exe de instalación creado con Wise. Incluso Outlook ya no se instala correctamente sin ejecutar como administrador. No hay más elevación automática en todo parece.


No puede "elevar" un proceso existente. Los procesos elevados bajo UAC tienen un token diferente con un LUID diferente, un nivel de integridad obligatorio diferente y una membresía de grupo diferente. Este nivel de cambio no se puede hacer dentro de un proceso en ejecución, y sería un problema de seguridad si eso pudiera suceder.

Debe iniciar un segundo proceso elevado que haría el trabajo o creando un objeto COM que se ejecute en un servidor dllhost elevado.

http://msdn.microsoft.com/en-us/library/bb756922.aspx proporciona una función de ejemplo "RunAsAdmin" y una función "CoCreateInstanceAsAdmin".

EDITAR: Acabo de ver "Delphi" en su título. Todo lo que enumeré es obviamente nativo, pero si Delphi proporciona acceso a la funcionalidad similar a ShellExecute, debería ser capaz de adaptar el código desde el enlace.


Una muestra de código listo para usar :

Ejemplo de uso:

unit Unit1; interface uses Windows{....}; type TForm1 = class(TForm) Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; Button1: TButton; Button2: TButton; procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private procedure StartWait; procedure EndWait; end; var Form1: TForm1; implementation uses RunElevatedSupport; {$R *.dfm} const ArgInstallUpdate = ''/install_update''; ArgRegisterExtension = ''/register_global_file_associations''; procedure TForm1.FormCreate(Sender: TObject); begin Label1.Caption := Format(''IsAdministrator: %s'', [BoolToStr(IsAdministrator, True)]); Label2.Caption := Format(''IsAdministratorAccount: %s'', [BoolToStr(IsAdministratorAccount, True)]); Label3.Caption := Format(''IsUACEnabled: %s'', [BoolToStr(IsUACEnabled, True)]); Label4.Caption := Format(''IsElevated: %s'', [BoolToStr(IsElevated, True)]); Button1.Caption := ''Install updates''; SetButtonElevated(Button1.Handle); Button2.Caption := ''Register file associations for all users''; SetButtonElevated(Button2.Handle); end; procedure TForm1.Button1Click(Sender: TObject); begin StartWait; try SetLastError(RunElevated(ArgInstallUpdate, Handle, Application.ProcessMessages)); if GetLastError <> ERROR_SUCCESS then RaiseLastOSError; finally EndWait; end; end; procedure TForm1.Button2Click(Sender: TObject); begin StartWait; try SetLastError(RunElevated(ArgRegisterExtension, Handle, Application.ProcessMessages)); if GetLastError <> ERROR_SUCCESS then RaiseLastOSError; finally EndWait; end; end; function DoElevatedTask(const AParameters: String): Cardinal; procedure InstallUpdate; var Msg: String; begin Msg := ''Hello from InstallUpdate!'' + sLineBreak + sLineBreak + ''This function is running elevated under full administrator rights.'' + sLineBreak + ''This means that you have write-access to Program Files folder and you''''re able to overwrite files (e.g. install updates).'' + sLineBreak + ''However, note that your executable is still running.'' + sLineBreak + sLineBreak + ''IsAdministrator: '' + BoolToStr(IsAdministrator, True) + sLineBreak + ''IsAdministratorAccount: '' + BoolToStr(IsAdministratorAccount, True) + sLineBreak + ''IsUACEnabled: '' + BoolToStr(IsUACEnabled, True) + sLineBreak + ''IsElevated: '' + BoolToStr(IsElevated, True); MessageBox(0, PChar(Msg), ''Hello from InstallUpdate!'', MB_OK or MB_ICONINFORMATION); end; procedure RegisterExtension; var Msg: String; begin Msg := ''Hello from RegisterExtension!'' + sLineBreak + sLineBreak + ''This function is running elevated under full administrator rights.'' + sLineBreak + ''This means that you have write-access to HKEY_LOCAL_MACHINE key and you''''re able to write keys and values (e.g. register file extensions globally/for all users).'' + sLineBreak + ''However, note that this is usually not a good idea. It is better to register your file extensions under HKEY_CURRENT_USER/Software/Classes.'' + sLineBreak + sLineBreak + ''IsAdministrator: '' + BoolToStr(IsAdministrator, True) + sLineBreak + ''IsAdministratorAccount: '' + BoolToStr(IsAdministratorAccount, True) + sLineBreak + ''IsUACEnabled: '' + BoolToStr(IsUACEnabled, True) + sLineBreak + ''IsElevated: '' + BoolToStr(IsElevated, True); MessageBox(0, PChar(Msg), ''Hello from RegisterExtension!'', MB_OK or MB_ICONINFORMATION); end; begin Result := ERROR_SUCCESS; if AParameters = ArgInstallUpdate then InstallUpdate else if AParameters = ArgRegisterExtension then RegisterExtension else Result := ERROR_GEN_FAILURE; end; procedure TForm1.StartWait; begin Cursor := crHourglass; Screen.Cursor := crHourglass; Button1.Enabled := False; Button2.Enabled := False; Application.ProcessMessages; end; procedure TForm1.EndWait; begin Cursor := crDefault; Screen.Cursor := crDefault; Button1.Enabled := True; Button2.Enabled := True; Application.ProcessMessages; end; initialization OnElevateProc := DoElevatedTask; CheckForElevatedTask; end.

Y la unidad de soporte en sí:

unit RunElevatedSupport; {$WARN SYMBOL_PLATFORM OFF} {$R+} interface uses Windows; type TElevatedProc = function(const AParameters: String): Cardinal; TProcessMessagesMeth = procedure of object; var // Warning: this function will be executed in external process. // Do not use any global variables inside this routine! // Use only supplied AParameters. OnElevateProc: TElevatedProc; // Call this routine after you have assigned OnElevateProc procedure CheckForElevatedTask; // Runs OnElevateProc under full administrator rights function RunElevated(const AParameters: String; const AWnd: HWND = 0; const AProcessMessages: TProcessMessagesMeth = nil): Cardinal; overload; function IsAdministrator: Boolean; function IsAdministratorAccount: Boolean; function IsUACEnabled: Boolean; function IsElevated: Boolean; procedure SetButtonElevated(const AButtonHandle: THandle); implementation uses SysUtils, Registry, ShellAPI, ComObj; const RunElevatedTaskSwitch = ''0CC5C50CB7D643B68CB900BF000FFFD5''; // some unique value, just a GUID with removed ''['', '']'', and ''-'' function CheckTokenMembership(TokenHandle: THANDLE; SidToCheck: Pointer; var IsMember: BOOL): BOOL; stdcall; external advapi32 name ''CheckTokenMembership''; function RunElevated(const AParameters: String; const AWnd: HWND = 0; const AProcessMessages: TProcessMessagesMeth = nil): Cardinal; overload; var SEI: TShellExecuteInfo; Host: String; Args: String; begin Assert(Assigned(OnElevateProc), ''OnElevateProc must be assigned before calling RunElevated''); if IsElevated then begin if Assigned(OnElevateProc) then Result := OnElevateProc(AParameters) else Result := ERROR_PROC_NOT_FOUND; Exit; end; Host := ParamStr(0); Args := Format(''/%s %s'', [RunElevatedTaskSwitch, AParameters]); FillChar(SEI, SizeOf(SEI), 0); SEI.cbSize := SizeOf(SEI); SEI.fMask := SEE_MASK_NOCLOSEPROCESS; {$IFDEF UNICODE} SEI.fMask := SEI.fMask or SEE_MASK_UNICODE; {$ENDIF} SEI.Wnd := AWnd; SEI.lpVerb := ''runas''; SEI.lpFile := PChar(Host); SEI.lpParameters := PChar(Args); SEI.nShow := SW_NORMAL; if not ShellExecuteEx(@SEI) then RaiseLastOSError; try Result := ERROR_GEN_FAILURE; if Assigned(AProcessMessages) then begin repeat if not GetExitCodeProcess(SEI.hProcess, Result) then Result := ERROR_GEN_FAILURE; AProcessMessages; until Result <> STILL_ACTIVE; end else begin if WaitForSingleObject(SEI.hProcess, INFINITE) <> WAIT_OBJECT_0 then if not GetExitCodeProcess(SEI.hProcess, Result) then Result := ERROR_GEN_FAILURE; end; finally CloseHandle(SEI.hProcess); end; end; function IsAdministrator: Boolean; var psidAdmin: Pointer; B: BOOL; const SECURITY_NT_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5)); SECURITY_BUILTIN_DOMAIN_RID = $00000020; DOMAIN_ALIAS_RID_ADMINS = $00000220; SE_GROUP_USE_FOR_DENY_ONLY = $00000010; begin psidAdmin := nil; try // Создаём SID группы админов для проверки Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, psidAdmin)); // Проверяем, входим ли мы в группу админов (с учётов всех проверок на disabled SID) if CheckTokenMembership(0, psidAdmin, B) then Result := B else Result := False; finally if psidAdmin <> nil then FreeSid(psidAdmin); end; end; {$R-} function IsAdministratorAccount: Boolean; var psidAdmin: Pointer; Token: THandle; Count: DWORD; TokenInfo: PTokenGroups; HaveToken: Boolean; I: Integer; const SECURITY_NT_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5)); SECURITY_BUILTIN_DOMAIN_RID = $00000020; DOMAIN_ALIAS_RID_ADMINS = $00000220; SE_GROUP_USE_FOR_DENY_ONLY = $00000010; begin Result := Win32Platform <> VER_PLATFORM_WIN32_NT; if Result then Exit; psidAdmin := nil; TokenInfo := nil; HaveToken := False; try Token := 0; HaveToken := OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, Token); if (not HaveToken) and (GetLastError = ERROR_NO_TOKEN) then HaveToken := OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, Token); if HaveToken then begin Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, psidAdmin)); if GetTokenInformation(Token, TokenGroups, nil, 0, Count) or (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then RaiseLastOSError; TokenInfo := PTokenGroups(AllocMem(Count)); Win32Check(GetTokenInformation(Token, TokenGroups, TokenInfo, Count, Count)); for I := 0 to TokenInfo^.GroupCount - 1 do begin Result := EqualSid(psidAdmin, TokenInfo^.Groups[I].Sid); if Result then Break; end; end; finally if TokenInfo <> nil then FreeMem(TokenInfo); if HaveToken then CloseHandle(Token); if psidAdmin <> nil then FreeSid(psidAdmin); end; end; {$R+} function IsUACEnabled: Boolean; var Reg: TRegistry; begin Result := CheckWin32Version(6, 0); if Result then begin Reg := TRegistry.Create(KEY_READ); try Reg.RootKey := HKEY_LOCAL_MACHINE; if Reg.OpenKey(''/Software/Microsoft/Windows/CurrentVersion/Policies/System'', False) then if Reg.ValueExists(''EnableLUA'') then Result := (Reg.ReadInteger(''EnableLUA'') <> 0) else Result := False else Result := False; finally FreeAndNil(Reg); end; end; end; function IsElevated: Boolean; const TokenElevation = TTokenInformationClass(20); type TOKEN_ELEVATION = record TokenIsElevated: DWORD; end; var TokenHandle: THandle; ResultLength: Cardinal; ATokenElevation: TOKEN_ELEVATION; HaveToken: Boolean; begin if CheckWin32Version(6, 0) then begin TokenHandle := 0; HaveToken := OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, TokenHandle); if (not HaveToken) and (GetLastError = ERROR_NO_TOKEN) then HaveToken := OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, TokenHandle); if HaveToken then begin try ResultLength := 0; if GetTokenInformation(TokenHandle, TokenElevation, @ATokenElevation, SizeOf(ATokenElevation), ResultLength) then Result := ATokenElevation.TokenIsElevated <> 0 else Result := False; finally CloseHandle(TokenHandle); end; end else Result := False; end else Result := IsAdministrator; end; procedure SetButtonElevated(const AButtonHandle: THandle); const BCM_SETSHIELD = $160C; var Required: BOOL; begin if not CheckWin32Version(6, 0) then Exit; if IsElevated then Exit; Required := True; SendMessage(AButtonHandle, BCM_SETSHIELD, 0, LPARAM(Required)); end; procedure CheckForElevatedTask; function GetArgsForElevatedTask: String; function PrepareParam(const ParamNo: Integer): String; begin Result := ParamStr(ParamNo); if Pos('' '', Result) > 0 then Result := AnsiQuotedStr(Result, ''"''); end; var X: Integer; begin Result := ''''; for X := 1 to ParamCount do begin if (AnsiUpperCase(ParamStr(X)) = (''/'' + RunElevatedTaskSwitch)) or (AnsiUpperCase(ParamStr(X)) = (''-'' + RunElevatedTaskSwitch)) then Continue; Result := Result + PrepareParam(X) + '' ''; end; Result := Trim(Result); end; var ExitCode: Cardinal; begin if not FindCmdLineSwitch(RunElevatedTaskSwitch) then Exit; ExitCode := ERROR_GEN_FAILURE; try if not IsElevated then ExitCode := ERROR_ACCESS_DENIED else if Assigned(OnElevateProc) then ExitCode := OnElevateProc(GetArgsForElevatedTask) else ExitCode := ERROR_PROC_NOT_FOUND; except on E: Exception do begin if E is EAbort then ExitCode := ERROR_CANCELLED else if E is EOleSysError then ExitCode := Cardinal(EOleSysError(E).ErrorCode) else if E is EOSError then else ExitCode := ERROR_GEN_FAILURE; end; end; if ExitCode = STILL_ACTIVE then ExitCode := ERROR_GEN_FAILURE; TerminateProcess(GetCurrentProcess, ExitCode); end; end.


me relanzaría como parámetros de línea de comando elevados y pasados ​​que indican qué cosa elevada desea hacer. A continuación, puede ir directamente a la forma adecuada, o simplemente guardar sus cosas HKLM.

function RunAsAdmin(hWnd: HWND; filename: string; Parameters: string): Boolean; { See Step 3: Redesign for UAC Compatibility (UAC) http://msdn.microsoft.com/en-us/library/bb756922.aspx This code is released into the public domain. No attribution required. } var sei: TShellExecuteInfo; begin ZeroMemory(@sei, SizeOf(sei)); sei.cbSize := SizeOf(TShellExecuteInfo); sei.Wnd := hwnd; sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI; sei.lpVerb := PChar(''runas''); sei.lpFile := PChar(Filename); // PAnsiChar; if parameters <> '''' then sei.lpParameters := PChar(parameters); // PAnsiChar; sei.nShow := SW_SHOWNORMAL; //Integer; Result := ShellExecuteEx(@sei); end;

La otra solución sugerida por Microsoft es crear un objeto COM fuera de proceso (usando la función CoCreateInstanceAsAdmin especialmente creada). No me gusta esta idea porque debe escribir y registrar un objeto COM.

Nota: No hay llamada a la API "CoCreateInstanceAsAdmin". Es solo un código flotando alrededor. Aquí está la versión de Dephi con la que tropecé. Al parecer, se basa en el truco de anteponer una cadena guid de clase con el prefijo " Elevation: Administrator! New: " cuando el código normalmente oculto internamente llama a CoGetObject :

function CoGetObject(pszName: PWideChar; pBindOptions: PBindOpts3; const iid: TIID; ppv: PPointer): HResult; stdcall; external ''ole32.dll''; procedure CoCreateInstanceAsAdmin(const Handle: HWND; const ClassID, IID: TGuid; PInterface: PPointer); var BindOpts: TBindOpts3; MonikerName: WideString; Res: HRESULT; begin //This code is released into the public domain. No attribution required. ZeroMemory(@BindOpts, Sizeof(TBindOpts3)); BindOpts.cbStruct := Sizeof(TBindOpts3); BindOpts.hwnd := Handle; BindOpts.dwClassContext := CLSCTX_LOCAL_SERVER; MonikerName := ''Elevation:Administrator!new:'' + GUIDToString(ClassID); Res := CoGetObject(PWideChar(MonikerName), @BindOpts, IID, PInterface); if Failed(Res) then raise Exception.Create(SysErrorMessage(Res)); end;

Otra pregunta: ¿cómo maneja a alguien corriendo como usuario estándar en Windows XP?