Como ejecutar una aplicación con otro usuario
Como ejecutar una aplicación con permisos de
otro usuario (Emular un "Run As")
Introducción
En ocasiones resulta de utilidad
poder ejecutar una aplicación con permisos de otro usuario (usualmente como
administrador), ya que en una red Windows bien configurada, es decir con
políticas de grupos y usuarios, raramente los usuarios llamados "normales"
poseerán privilegios de administrador de la red.
Esto está muy bien para asegurar
que no se producen "desgracias" por parte de un usuario, ya no de forma
malintencionada sino lo que vulgarmente se conoce como el "yonohesido" :-D, ya
que si un usuario no tiene permiso para borrar una carpeta o cambiar la
configuración del Internet Explorer, seguros que nuestra red vivirá más tiempo
sin sobresaltos.
Ahora bien, también existe la
parte negativa, y es que existen algunas (que digo algunas, un montón)
aplicaciones que deben forzosamente ejecutarse con privilegios de usuario
administrador para funcionar correctamente. Esto habitualmente es fuente de
pesadillas para los administradores de red, que afortunadamente cuentan con la
posibilidad de crear accesos directos a aplicaciones que se ejecutan con
permisos de otro usuario, basta con activar la casilla 'Ejecutar como usuario
diferente" (fig. 1).

(fig. 1)
De esta forma cuando el usuario
quiere ejecutar la aplicación se le presentará una ventana dónde pueda
introducir sus "credenciales", es decir: Usuario, contraseña y dominio en el que
iniciar la sesión (fig. 2). Así solventamos algunas de las situaciones, pero
¿que sucede cuando es una aplicación la que debe llamar a otra y hacer que esta
segunda se ejecute con otros privilegios? Pues la respuesta es que no es tan
sencillo... o no lo sería si no tuviéramos la función "CreateProcessWithLogon"
del API de Windows.

(fig. 2)
¿CreateProcessWithLogon? Examinemos
atentamente esta función:
Private Declare
Function CreateProcessWithLogon
Lib _
"Advapi32" Alias "CreateProcessWithLogonW" _
(ByVal
lpUsername As Long,
ByVal lpDomain As
Long, ByVal _
lpPassword As Long,
ByVal dwLogonFlags As
Long, ByVal _
lpApplicationName As
Long, ByVal lpCommandLine
As Long, _
ByVal dwCreationFlags
As Long, ByVal
lpEnvironment As Long,
_
ByVal lpCurrentDirectory
As Long,
ByVal lpStartupInfo As
STARTUPINFO, _
ByVal lpProcessInfo As
PROCESS_INFORMATION) As
Long
Básicamente crea un nuevo
proceso con su thread primario, dentro del cual ejecuta la aplicación solicitada
en el contexto de seguridad especificado (usuario, contraseña y dominio) que es
suministrado a la función en forma de parámetros, y aunque nos suene extraño las
cadenas que contienen el usuario, contraseña y dominio no se pasan como String,
sino como puntero a un String usando la función StrPtr de VB
lpUsername
Puntero a un String que contiene el nombre del usuario que ejecutará la
aplicación. Si usamos la convención UPN (usuario@dominio), el argumento
lpDomain deberá ser NULL. Se conceden permisos a todos los usuarios en
todos los clientes y servidores, pero sólo a los administradores en los
controladores de dominio.
lpDomain
Puntero a un String que contiene el dominio en el que nos autentificaremos, en
Windows XP si el valor es "." se asume que estamos usando una cuenta local.
lpPassword
Puntero a un String que contiene la contraseña del usuario que ejecutará la
aplicación.
dwLogonFlags
Define las opciones de la autentificación, sus valores pueden ser:
- LOGON_WITH_PROFILE: Carga el perfil del usuario, es la mejor opción
siempre que tengamos acceso al mismo, aunque consume un poco más de tiempo.
- LOGON_NETCREDENTIALS_ONLY: El nuevo proceso usará el token de seguridad
que el proceso llamante, pero el sistema creará una nueva sesión mediante LSA,
se acostumbra a usar en entornos multidominio en los cuales no esté definida
relación de confianza.
lpApplicationName
Puntero a un String que contiene la ruta del ejecutable. Si únicamente se
suministra el nombre, por defecto se buscará en la carpeta actual.
lpCommandLine
Puntero a un String que contiene los argumentos de línea de comandos
suministrado a la aplicación. En caso que la combinación de nombre de aplicación
más los argumentos suministrados permitan diferentes interpretaciones el orden
de búsqueda será:
- El directorio de la aplicación
que deseamos ejecutar.
- El directorio actual del proceso llamador.
- El directorio %System (independientemente de si estamos en un sistema 16 o 32
bits).
- El directorio Windows.
- Los directorios listados en la variable de entorno PATH.
dwCreationFlags
Permite especificar "como" se va a crear el nuevo proceso, por defecto los flags
CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, y CREATE_NEW_PROCESS_GROUP están
activados, y admite los siguientes valores:
- CREATE_DEFAULT_ERROR_MODE: El nuevo proceso no heredará el modo de errores del
proceso "llamador".
- CREATE_NEW_CONSOLE: El nuevo proceso creará su propia consola en lugar de
heredar la consola del proceso "llamador".
- CREATE_NEW_PROCESS_GROUP: Permite la creación de un proceso "raíz" de un grupo
de procesos, el identificador de este nuevo proceso será devuelto en el valor
del parámetro lpProcessInfo.
- CREATE_SEPARATE_WOW_VDM: Válido sólo en entornos 16 bits.
- CREATE_SUSPENDED: Crea el proceso pero establece el estado del thread
principal a suspendido, y sólo se activará si llamamos a la función
ResumeThread.
- CREATE_UNICODE_ENVIRONMENT: Permite el uso de carácteres Unicode en el entorno
lpEnvironment.
Si además deseamos establecer la
prioridad del nuevo proceso podemos usar las constantes habituales, enumeradas
de menor a mayor prioridad (por defecto NORMAL_PRIORITY_CLASS ):
- IDLE_PRIORITY_CLASS
- BELOW_NORMAL_PRIORITY_CLASS
- NORMAL_PRIORITY_CLASS
- ABOVE_NORMAL_PRIORITY_CLASS
- HIGH_PRIORITY_CLASS
- REALTIME_PRIORITY_CLASS
lpEnvironment
Puntero al entorno del nuevo proceso. Si el valor es NULL se usará el entorno
del proceso "llamador".
lpCurrentDirectory
Puntero a una cadena que contiene la unidad y directorio del nuevo proceso. Si
el valor es NULL se usarán los valores del proceso "llamador".
lpStartupInfo
Puntero a una estructura STARTUPINFO que permite especificar la apariencia de la
ventana principal del nuevo proceso.
lpProcessInfo
Parámetro de salida. Puntero a una estructura PROCESS_INFORMATION que recibe
información de identificación del nuevo proceso, incluyendo el handle del nuevo
proceso.
Ejemplo de implementación (código
VB 6.0):
Una vez conocemos cómo funciona
vamos a ver un ejemplo de cómo implementarla en una aplicación VB 6.0. Esta
aplicación recibirá tres parámetros que son: el path de la aplicación,
usuario@dominio (observar que usaremos convención UPN), y la contraseña del
usuario.
El código
está completo e incluye las declaraciones necesarias para la llamada, además de
las funciones "GetLastError" y "FormatMessage" para los posibles
valores de retorno en caso de error. Sólo es necesario un modulo *.BAS con el
siguiente código y establecer "Sub Main" en el modo de inicio en las propiedades
del proyecto:
Option Explicit
' ///
' /// Ejecutar una aplicación con permisos de otro
usuario ("Run As")
' /// Lluís Franco i Montanyés (MVP-MCP-VB)
07/06/2004
' /// Ejemplo: fRunAsEx "NOTEPAD.EXE",
"<usuario>@<dominio>", "<password>"
' ///
Private Const
LOGON_WITH_PROFILE = &H1&
Private Const
CREATE_DEFAULT_ERROR_MODE = &H4000000
Private Const
CREATE_NEW_CONSOLE = &H10&
Private Const
CREATE_NEW_PROCESS_GROUP = &H200&
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Private Const LANG_NEUTRAL = &H0
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Private Type STARTUPINFO
cb As Long
lpReserved As Long
lpDesktop As Long
lpTitle As Long
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Byte
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Declare
Function CreateProcessWithLogon
Lib _
"Advapi32" Alias "CreateProcessWithLogonW" _
(ByVal
lpUsername As Long,
ByVal lpDomain As
Long, ByVal _
lpPassword As Long,
ByVal dwLogonFlags As
Long, ByVal _
lpApplicationName As
Long, ByVal lpCommandLine
As Long, _
ByVal dwCreationFlags
As Long, ByVal
lpEnvironment As Long,
_
ByVal lpCurrentDirectory
As Long,
ByVal lpStartupInfo As
STARTUPINFO, _
ByVal lpProcessInfo As
PROCESS_INFORMATION) As
Long
Private Declare
Function CloseHandle
Lib "kernel32" _
(ByVal
hObject As Long)
As Long
Private Declare
Function FormatMessage
Lib "kernel32" Alias "FormatMessageA" _
(ByVal
dwFlags As Long,
ByVal lpSource As
Any, ByVal dwMessageId
As Long, _
ByVal dwLanguageId As
Long, ByVal
lpBuffer As String,
ByVal nSize As
Long, _
ByVal Arguments As
Long) As
Long
Private Declare
Function GetLastError
Lib "kernel32" () As
Long
Public sError As
String
Public Sub
Main()
Dim sParams() As
String, sParam, i As
Long, fReturn As
Long
Dim sApp As
String, sUser As
String, sPwd As
String
'Verificación de argumentos correctos (/path aplicación /usuario@dominio
/contraseña)
If Trim(Command$) = ""
Then
MsgBox("Error! No se han suministrado argumentos para ejecutar RunAs:" & _
vbNewLine & vbNewLine & _
"Parametros=
/<path aplicación> /<usuario> /<password>" & _
vbNewLine & vbNewLine & _
"Ejemplo= /NOTEPAD.EXE /administrador@dominio.com /mypassword", vbExclamation)
Exit
Sub
End If
sParams = Split(Command$, "/")
For Each sParam
In sParams
If Trim(sParam) <> ""
Then
i = i + 1
If i = 1
Then sApp = Trim(sParam)
If i = 2
Then sUser = Trim(sParam)
If i = 3
Then sPwd = Trim(sParam)
End If
Next
fReturn = fRunAsEx(sApp, sUser, sPwd)
If fReturn = 0 Then
MsgBox(sError, vbExclamation)
End Sub
Public Function
fRunAsEx(ByVal sFileName
As String,
_
ByVal sUserName As
String, ByVal
sUserPwd As String)
As Long
'Declaració vars
Dim lpBuffer As
String * 200
Dim fReturn As
Long
Dim lpUsername As
String, lpDomain As
String
Dim lpPassword As
String, lpApplicationName
As String
Dim lpCommandLine As
String, lpCurrentDirectory
As String
Dim StartInfo As
STARTUPINFO, ProcessInfo As PROCESS_INFORMATION
lpApplicationName = sFileName
lpUsername = sUserName
lpPassword = sUserPwd
lpCommandLine = vbNullString
lpCurrentDirectory = vbNullString
StartInfo.cb = LenB(StartInfo)
StartInfo.dwFlags = 0&
fReturn = CreateProcessWithLogon(StrPtr(lpUsername), StrPtr(lpDomain), _
StrPtr(lpPassword), LOGON_WITH_PROFILE, StrPtr(lpApplicationName), _
StrPtr(lpCommandLine), CREATE_DEFAULT_ERROR_MODE Or
CREATE_NEW_CONSOLE _
Or CREATE_NEW_PROCESS_GROUP,
ByVal 0&, StrPtr(lpCurrentDirectory), _
StartInfo, ProcessInfo)
If fReturn = 0 Then
If GetLastError = 0
Then
sError = "La
operación no se ha podido completar con éxito."
Else
FormatMessage FORMAT_MESSAGE_FROM_SYSTEM,
ByVal 0&, _
GetLastError, LANG_NEUTRAL, lpBuffer, 200,
ByVal 0&
sError = lpBuffer
End If
End If
fRunAsEx = fReturn
CloseHandle(ProcessInfo.hThread)
CloseHandle(ProcessInfo.hProcess)
End Function
Ejecutarlo en un caso real
Una vez compilado el código obtenemos un ejecutable
de Windows (RunAsEx.exe) que para más comodidad copiaremos en el directorio
%system (así no tenemos que especificar el path completo cada vez que hagamos
una llamada), y a continuación podemos elegir ejecutarlo desde la línea de
comandos (fig. 3), o bien crear otra aplicación VB muy sencilla que realice la
llamada a través del comando "Shell" (fig. 4):

(fig. 3)

(fig. 4)
Bueno, pues ya hemos terminado con esto. Si quieres
puedes descargarte el proyecto de ejemplo
y ya me contarás...
Un
saludo y hasta pronto!
Lluís Franco
Principat d'Andorra, May 2005
:-)