¿Cómo puedo elevar automáticamente mi archivo por lotes para que solicite derechos de administrador de UAC si es necesario?
Quiero que mi archivo por lotes solo se ejecute elevado. Si no está elevado, proporcione una opción para que el usuario reinicie el lote como elevado.
Estoy escribiendo un archivo por lotes para configurar una variable del sistema, copiar dos archivos a una ubicación de Archivos de programa e iniciar un instalador de controladores. Si un usuario de Windows 7/Windows Vista ( UAC habilitado e incluso si es un administrador local) lo ejecuta sin hacer clic derecho y seleccionar "Ejecutar como administrador", obtendrá "Acceso denegado" al copiar los dos archivos y escribir la variable del sistema. .
Me gustaría usar un comando para reiniciar automáticamente el lote como elevado si el usuario es en realidad administrador. De lo contrario, si no es administrador, quiero decirles que necesitan privilegios de administrador para ejecutar el archivo por lotes. Estoy usando xcopy para copiar los archivos y REG ADD para escribir la variable del sistema. Estoy usando esos comandos para tratar con posibles máquinas con Windows XP. Encontré preguntas similares sobre este tema, pero nada que tenga que ver con reiniciar un archivo por lotes como elevado.
Existe una manera fácil sin la necesidad de utilizar una herramienta externa: funciona bien con Windows 7, 8, 8.1, 10 y 11 y también es compatible con versiones anteriores (Windows XP no tiene UAC, por lo que no es necesaria la elevación). en ese caso el guión simplemente continúa).
Consulte este código (me inspiré en el código de NIronwolf publicado en el hilo Archivo por lotes - "Acceso denegado" en Windows 7? 1) , pero lo he mejorado: en mi versión no se crea ni se elimina ningún directorio. para comprobar los privilegios de administrador):
::::::::::::::::::::::::::::::::::::::::::::
:: Elevate.cmd - Version 4
:: Automatically check & get admin rights
:: see "https://stackoverflow.com/a/12264592/1016343" for description
::::::::::::::::::::::::::::::::::::::::::::
@echo off
CLS
ECHO.
ECHO =============================
ECHO Running Admin shell
ECHO =============================
:init
setlocal DisableDelayedExpansion
set cmdInvoke=1
set winSysFolder=System32
set "batchPath=%~dpnx0"
rem this works also from cmd shell, other than %~0
for %%k in (%0) do set batchName=%%~nk
set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs"
setlocal EnableDelayedExpansion
:checkPrivileges
NET FILE 1>NUL 2>NUL
if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges )
:getPrivileges
if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges)
ECHO.
ECHO **************************************
ECHO Invoking UAC for Privilege Escalation
ECHO **************************************
ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
ECHO args = "ELEV " >> "%vbsGetPrivileges%"
ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
ECHO Next >> "%vbsGetPrivileges%"
if '%cmdInvoke%'=='1' goto InvokeCmd
ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%"
goto ExecElevation
:InvokeCmd
ECHO args = "/c """ + "!batchPath!" + """ " + args >> "%vbsGetPrivileges%"
ECHO UAC.ShellExecute "%SystemRoot%\%winSysFolder%\cmd.exe", args, "", "runas", 1 >> "%vbsGetPrivileges%"
:ExecElevation
"%SystemRoot%\%winSysFolder%\WScript.exe" "%vbsGetPrivileges%" %*
exit /B
:gotPrivileges
setlocal & cd /d %~dp0
if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul & shift /1)
::::::::::::::::::::::::::::
::START
::::::::::::::::::::::::::::
REM Run shell as admin (example) - put here code as you like
ECHO %batchName% Arguments: P1=%1 P2=%2 P3=%3 P4=%4 P5=%5 P6=%6 P7=%7 P8=%8 P9=%9
cmd /k
El script aprovecha el hecho de que NET FILE
requiere privilegios de administrador y regresa errorlevel 1
si no los tiene. La elevación se logra creando un script que reinicia el archivo por lotes para obtener privilegios. Esto hace que Windows presente el cuadro de diálogo UAC y le solicite la cuenta y la contraseña del administrador.
Lo probé con Windows 7, 8, 8.1, 10, 11 y con Windows XP; funciona bien para todos. La ventaja es que, después del punto de inicio, puede colocar cualquier cosa que requiera privilegios de administrador del sistema, por ejemplo, si desea volver a instalar y ejecutar un servicio de Windows con fines de depuración (suponiendo que mypackage.msi es un paquete de instalación de servicios). :
msiexec /passive /x mypackage.msi
msiexec /passive /i mypackage.msi
net start myservice
Sin este script de elevación de privilegios, UAC le pediría tres veces su usuario y contraseña de administrador; ahora se le pedirá sólo una vez al principio, y sólo si es necesario.
Si su secuencia de comandos solo necesita mostrar un mensaje de error y salir si no hay privilegios de administrador en lugar de elevarse automáticamente, esto es aún más simple: puede lograrlo agregando lo siguiente al comienzo de su secuencia de comandos:
@ECHO OFF & CLS & ECHO.
NET FILE 1>NUL 2>NUL & IF ERRORLEVEL 1 (ECHO You must right-click and select &
ECHO "RUN AS ADMINISTRATOR" to run this batch. Exiting... & ECHO. &
PAUSE & EXIT /D)
REM ... proceed here with admin rights ...
De esta forma, el usuario debe hacer clic derecho y seleccionar "Ejecutar como administrador" . El script continuará después de la REM
declaración si detecta derechos de administrador; de lo contrario, saldrá con un error. Si no lo necesita PAUSE
, simplemente elimínelo.
Importante: NET FILE [...] EXIT /D)
debe estar en la misma línea. ¡Se muestra aquí en varias líneas para una mejor legibilidad!
En algunas máquinas, he encontrado problemas que ya están resueltos en la nueva versión anterior. Uno se debió a un manejo diferente de las comillas dobles y el otro problema se debió al hecho de que UAC estaba deshabilitado (establecido en el nivel más bajo) en una máquina con Windows 7, por lo que el script se llama a sí mismo una y otra vez.
Solucioné esto ahora eliminando las comillas en la ruta y volviéndolas a agregar más tarde, y agregué un parámetro adicional que se agrega cuando el script se reinicia con derechos elevados.
Las comillas dobles se eliminan con lo siguiente (los detalles están aquí ):
setlocal DisableDelayedExpansion
set "batchPath=%~0"
setlocal EnableDelayedExpansion
Luego puede acceder a la ruta usando !batchPath!
. No contiene comillas dobles, por lo que es seguro decirlo "!batchPath!"
más adelante en el guión.
La línea
if '%1'=='ELEV' (shift & goto gotPrivileges)
comprueba si el script VBScript ya ha llamado al script para elevar los derechos, evitando así recursiones interminables. Elimina el parámetro usando shift
.
Actualizar:
Para evitar tener que registrar la
.vbs
extensión en Windows 10 , reemplacé la línea
"%temp%\OEgetPrivileges.vbs"
por
"%SystemRoot%\System32\WScript.exe" "%temp%\OEgetPrivileges.vbs"
en el script anterior; También se agregócd /d %~dp0
según lo sugerido por Stephen (respuesta separada) y Tomáš Zato (comentario) para configurar el directorio de secuencias de comandos como predeterminado.Ahora el script respeta los parámetros de la línea de comando que se le pasan. Gracias a jxmallet, TanisDLJ y Peter Mortensen por sus observaciones e inspiraciones.
De acuerdo con la sugerencia de Artjom B., lo analicé y lo reemplacé
SHIFT
porSHIFT /1
, que conserva el nombre del archivo para el%0
parámetro.Agregado
del "%temp%\OEgetPrivileges_%batchName%.vbs"
a la:gotPrivileges
sección para limpiar (como sugirió mlt ). Se agregó%batchName%
para evitar el impacto si ejecuta diferentes lotes en paralelo. Tenga en cuenta que debe utilizarfor
para poder aprovechar las funciones de cadena avanzadas, como%%~nk
, que extrae solo el nombre del archivo.Estructura de script optimizada, mejoras (variable agregada
vbsGetPrivileges
a la que ahora se hace referencia en todas partes, lo que permite cambiar la ruta o el nombre del archivo fácilmente, solo elimine.vbs
el archivo si es necesario elevar el lote)En algunos casos, se requirió una sintaxis de llamada diferente para la elevación. Si el script no funciona, verifique los siguientes parámetros:
set cmdInvoke=0
set winSysFolder=System32
Cambie el primer parámetro aset cmdInvoke=1
y verifique si eso ya soluciona el problema. Se agregarácmd.exe
al script realizando la elevación.
O intente cambiar el segundo parámetro awinSysFolder=Sysnative
, esto podría ayudar (pero en la mayoría de los casos no es necesario) en sistemas de 64 bits. (ADBailey ha informado esto). "Sysnative" sólo es necesario para iniciar aplicaciones de 64 bits desde un host de script de 32 bits (por ejemplo, un proceso de compilación de Visual Studio o la invocación de un script desde otra aplicación de 32 bits).Para que quede más claro cómo se interpretan los parámetros, ahora lo muestro como
P1=value1 P2=value2 ... P9=value9
. Esto es especialmente útil si necesita encerrar parámetros como rutas entre comillas dobles, por ejemplo"C:\Program Files"
.Si desea depurar el script VBS, puede agregar el
//X
parámetro a WScript.exe como primer parámetro, como se sugiere aquí (se describe para CScript.exe, pero también funciona para WScript.exe).Corrección de error proporcionada por MiguelAngelo : BatchPath ahora se devuelve correctamente en cmd Shell. Este pequeño script
test.cmd
muestra la diferencia, para aquellos interesados en los detalles (ejecútelo en cmd.exe, luego ejecútelo haciendo doble clic desde el Explorador de Windows):@echo off setlocal set a="%~0" set b="%~dpnx0" if %a% EQU %b% echo running shell execute if not %a% EQU %b% echo running cmd shell echo a=%a%, b=%b% pause
Enlaces útiles:
- Significado de los caracteres especiales en el archivo por lotes:
Comillas (") , Bang (!) , Caret (^) , Ampersand (&) , Otros caracteres especiales
1) Tenga en cuenta que este enlace ya no existe; ahora muestra un error 404.
Como mencionaron jcoder y Matt, PowerShell lo hizo fácil e incluso podría incrustarse en el script por lotes sin crear un script nuevo.
Modifiqué el guión de Matt:
:: Check privileges
net file 1>NUL 2>NUL
if not '%errorlevel%' == '0' (
powershell Start-Process -FilePath "%0" -ArgumentList "%cd%" -verb runas >NUL 2>&1
exit /b
)
:: Change directory with passed argument. Processes started with
:: "runas" start with forced C:\Windows\System32 workdir
cd /d %1
:: Actual work
Yo lo hago de esta manera:
NET SESSION
IF %ERRORLEVEL% NEQ 0 GOTO ELEVATE
GOTO ADMINTASKS
:ELEVATE
CD /d %~dp0
MSHTA "javascript: var shell = new ActiveXObject('shell.application'); shell.ShellExecute('%~nx0', '', '', 'runas', 1);close();"
EXIT
:ADMINTASKS
(Do whatever you need to do here)
EXIT
De esta manera es simple y usa solo los comandos predeterminados de Windows. Es genial si necesita redistribuir su archivo por lotes.
CD /d %~dp0
Establece el directorio actual en el directorio actual del archivo (si aún no lo está, independientemente de la unidad en la que se encuentre el archivo, gracias a la /d
opción).
%~nx0
Devuelve el nombre del archivo actual con la extensión (si no incluye la extensión y hay un exe con el mismo nombre en la carpeta, llamará al exe).
Hay tantas respuestas en esta publicación que ni siquiera sé si se verá mi respuesta.
De todos modos, esta forma me parece más sencilla que las otras soluciones propuestas en las otras respuestas, espero que ayude a alguien.