2009-05-28 10 views
31

Tenemos que cambiar algunas configuraciones al HKEY_LOCAL_MACHINE en el tiempo de ejecución.Delphi: solicite la elevación del UAC cuando sea necesario

¿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'?

+0

Vi un buen artículo en el famoso [Jedi] (http://blog.delphi-jedi.net/2008/03/18/elevate-application-on-vista-with-jwscl/) Lib too –

+0

The Jedi ejemplo para "elevar partes de una aplicación" depende de un objeto COM y de llamar a eso. La desventaja de usar un objeto COM es que tiene que escribir un objeto COM, y lo que es peor: registrarlo en la computadora del usuario. Es más fácil pasar las instrucciones en la línea de comando, o en la memoria compartida, o a través de un conducto con nombre. –

+0

Usar la línea de comando para pasar instrucciones es bastante problemático si tiene que usar credenciales o una gran cantidad de datos. No se debe usar una tubería con nombre, ya que se puede conectar desde prácticamente cualquier lugar de la computadora. En su lugar, use la manija de la tubería y envíela al nuevo proceso (puede heredar los identificadores mediante CreateProcess). Tenga cuidado con la memoria compartida, ya que puede abrir una vulnerabilidad (principalmente desbordamiento de búfer). El proceso elevado debe verificar cuidadosamente la entrada. – ChristianWimmer

Respuesta

19

No se 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 da un ejemplo de la función "RunAsAdmin" y una función "CoCreateInstanceAsAdmin".

EDIT: 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.

+2

Lo investigaré. Delphi es nativo y proporciona acceso completo a la api win32, incluido ShellExecute(). Tanques. – Vegar

21

me relanzaría a usted mismo como elevado, pasando parámetros de línea de comandos 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; 

El otro Microsoft sugirió solución es crear un objeto COM fuera de proceso (utilizando la función CoCreateInstanceAsAdmin especialmente creado). No me gusta esta idea porque debe escribir y registrar un objeto COM.


Nota: No hay ninguna 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 prefijar una cadena GUID clase con el "Elevación: administrador nueva:" prefijo de código cuando normalmente oculta llama internamente 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?

10

Una muestra de ready-to-use code: ejemplo

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 unidad de apoyo 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. 
1

Por lo general, poner el texto "configuración" o "Instalar" en algún lugar de su El nombre EXE es suficiente para hacer que Windows se ejecute con privelages 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 inicié sesión como administrador, y tengo que utilizar el botón derecho como administrador de ejecución cuando se ejecuta manualmente (ejecutar el programa mediante el asistente de instalación Wise todavía está bien)

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!

Project Options

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

Edit: Jan 2018: desde que se escribió esta respuesta en agosto de 2017, parece que han salido muchas 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 construido con Sabio. Incluso Outlook ya no se instala correctamente sin ejecutar como administrador. No hay más elevación automática en todo parece.

Cuestiones relacionadas