¿Cómo puedo elevar automáticamente mi archivo por lotes para que solicite los derechos de administrador de UAC si es necesario?

Quiero que mi archivo por lotes solo se ejecute elevado. Si no está elevado, brinde una opción para que el usuario vuelva a lanzar el lote como elevado.

Estoy escribiendo un archivo por lotes para establecer una variable de sistema, copiar dos archivos en una ubicación de Archivos de progtwig 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 seleccionando “Ejecutar como administrador”, obtendrá ‘Acceso denegado’ copiando los dos archivos y escribiendo la variable del sistema .

Me gustaría usar un comando para reiniciar automáticamente el lote como elevado si el usuario es de hecho un administrador. De lo contrario, si no son un 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 manejar posibles máquinas con Windows XP. He encontrado preguntas similares sobre este tema, pero nada que tenga que ver con relanzar un archivo por lotes como elevado.

Puede hacer que el script se llame a sí mismo con la opción -h para ejecutar elevado.

No estoy seguro de cómo detectaría si ya se está ejecutando como elevado o no … tal vez vuelva a intentar con las permanentes permanentes solo si hay un error de Acceso denegado.

O bien, simplemente podría hacer que los comandos para xcopy y reg.exe siempre se ejecuten con psexec -h , pero sería molesto para el usuario final si necesita ingresar su contraseña cada vez (o inseguro si incluyó la contraseña). en el guion) …

Hay una manera fácil sin la necesidad de usar una herramienta externa: funciona bien con Windows 7, 8, 8.1 y 10 y también es compatible con versiones anteriores (Windows XP no tiene ningún UAC, por lo tanto, no se necesita elevación). caso el script simplemente continúa).

Echa un vistazo a este código (me inspiré en el código de NIronwolf publicado en el hilo Batch File – “Acceso denegado” en Windows 7? ), Pero lo he mejorado: en mi versión no hay ningún directorio creado y eliminado para verificar privilegios de administrador):

 :::::::::::::::::::::::::::::::::::::::::::: :: Elevate.cmd - Version 4 :: Automatically check & get admin rights :::::::::::::::::::::::::::::::::::::::::::: @echo off CLS ECHO. ECHO ============================= ECHO Running Admin shell ECHO ============================= :init setlocal DisableDelayedExpansion set cmdInvoke=1 set winSysFolder=System32 set "batchPath=%~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 privilegio de administrador y devuelve errorlevel 1 si no lo tiene. La elevación se logra creando una secuencia de comandos que vuelve a lanzar el archivo por lotes para obtener privilegios. Esto hace que Windows presente el cuadro de diálogo UAC y le pide la cuenta de administrador y la contraseña.

Lo he probado con Windows 7, 8, 8.1, 10 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 tiene la intención de reinstalar y volver a ejecutar un servicio de Windows con fines de depuración (se supone que mypackage.msi es un paquete de instalador de servicios) :

 msiexec /passive /x mypackage.msi msiexec /passive /i mypackage.msi net start myservice 

Sin esta secuencia de comandos de elevación de privilegios, UAC le preguntaría tres veces por su usuario administrador y contraseña: ahora solo se le solicita una vez al principio, y solo si es necesario.


Si su script solo necesita mostrar un mensaje de error y salir si no hay privilegios de administrador en lugar de auto-elevarse, esto es aún más simple: puede lograr esto agregando lo siguiente al comienzo de su script:

 @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 instrucción REM si detecta derechos de administrador; de lo contrario, saldrá con un error. Si no necesita la PAUSE , simplemente elimínela. 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 de ellos se debió a un manejo diferente de las comillas dobles, y el otro problema se debió al hecho de que el UAC estaba deshabilitado (establecido en el nivel más bajo) en una máquina con Windows 7, por lo tanto, el script se llama una y otra vez.

Lo he arreglado ahora eliminando las comillas en la ruta y volviéndolas a agregar más tarde, y he agregado un parámetro adicional que se agrega cuando la secuencia de comandos se vuelve a ejecutar con derechos elevados.

Las comillas dobles se eliminan por lo siguiente (los detalles están aquí ):

 setlocal DisableDelayedExpansion set "batchPath=%~0" setlocal EnableDelayedExpansion 

A continuación, puede acceder a la ruta utilizando !batchPath! . No contiene comillas dobles, por lo que es seguro decir "!batchPath!" más adelante en el guión.

La línea

 if '%1'=='ELEV' (shift & goto gotPrivileges) 

comprueba si la secuencia de comandos de VBScript ya ha llamado al guion para elevar los derechos, evitando recurrencias interminables. Elimina el parámetro usando shift .


Actualizar:

  • Para evitar tener que registrar la extensión .vbs en Windows 10 , he reemplazado la línea
    "%temp%\OEgetPrivileges.vbs"
    por
    "%SystemRoot%\System32\WScript.exe" "%temp%\OEgetPrivileges.vbs"
    en el script de arriba; también se agregó cd /d %~dp0 según lo sugerido por Stephen (respuesta separada) y por Tomáš Zato (comentario) para establecer el directorio de scripts como predeterminado.

  • Ahora la secuencia de comandos respeta los parámetros de 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 reemplacé SHIFT por SHIFT /1 , que conserva el nombre de archivo para el parámetro %0

  • Se agregó del "%temp%\OEgetPrivileges_%batchName%.vbs" a la sección :gotPrivileges para limpiar (como se sugirió mlt ). Se agregó %batchName% para evitar el impacto si ejecuta lotes diferentes en paralelo. Tenga en cuenta que debe usar for para poder aprovechar las funciones de cadena avanzadas, como %%~nk , que extrae solo el nombre del archivo.

  • Estructura de scripts optimizada, mejoras (se agregó la variable vbsGetPrivileges que se hace referencia en todas partes, lo que permite cambiar fácilmente la ruta o el nombre del archivo, solo elimina el archivo .vbs si el lote debe elevarse)

  • En algunos casos, se requirió una syntax 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 para set cmdInvoke=1 y verifique si eso ya solucionó el problema. Agregará cmd.exe al script que realiza la elevación.
    O intente cambiar el 2º parámetro a winSysFolder=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” solo es necesario para iniciar aplicaciones de 64 bits desde un host de script de 32 bits (por ejemplo, un proceso de comstackción de Visual Studio o una invocación de script desde otra aplicación de 32 bits).

  • Para que quede más claro cómo se interpretan los parámetros, ahora lo estoy mostrando como P1=value1 P2=value2 ... P9=value9 . Esto es especialmente útil si necesita incluir parámetros como las rutas entre comillas dobles, por ejemplo, "C:\Program Files" .

Como mencionaron jcoder y Matt, PowerShell lo hizo fácil, e incluso podría incluirse en el script por lotes sin crear un nuevo script.

Modifiqué el guión de Matt:

 :checkPrivileges NET FILE 1>NUL 2>NUL if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( powershell "saps -filepath %0 -verb runas" >nul 2>&1) exit /b 

No hay ninguna necesidad de la etiqueta :getPrivileges .

Estoy usando la excelente respuesta de Matt, pero veo una diferencia entre mis sistemas Windows 7 y Windows 8 cuando ejecuto scripts elevados.

Una vez que la secuencia de comandos se eleva en Windows 8, el directorio actual se establece en C:\Windows\system32 . Afortunadamente, hay una solución fácil al cambiar el directorio actual a la ruta del script actual:

 cd /d %~dp0 

Nota: Use cd /d para asegurarse de que la letra de la unidad también se modifique.

Para probar esto, puede copiar lo siguiente a un script. Ejecutar normalmente en cualquiera de las versiones para ver el mismo resultado. Ejecutar como administrador y ver la diferencia en Windows 8:

 @echo off echo Current path is %cd% echo Changing directory to the path of the current script cd %~dp0 echo Current path is %cd% pause 

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 comandos predeterminados de Windows. Es genial si necesitas redistribuir tu 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 esté el archivo, gracias a la opción /d ).

%~nx0 Devuelve el nombre de 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, me parece más simple que las otras soluciones propuestas en las otras respuestas, espero que ayude a alguien.

Matt tiene una gran respuesta, pero elimina cualquier argumento pasado al guión. Aquí está mi modificación que guarda argumentos. También incorporé la corrección de Stephen para el problema del directorio de trabajo en Windows 8.

 @ECHO OFF setlocal EnableDelayedExpansion NET FILE 1>NUL 2>NUL if '%errorlevel%' == '0' ( goto START ) else ( goto getPrivileges ) :getPrivileges if '%1'=='ELEV' ( goto START ) set "batchPath=%~f0" set "batchArgs=ELEV" ::Add quotes to the batch path, if needed set "script=%0" set script=%script:"=% IF '%0'=='!script!' ( GOTO PathQuotesDone ) set "batchPath=""%batchPath%""" :PathQuotesDone ::Add quotes to the arguments, if needed. :ArgLoop IF '%1'=='' ( GOTO EndArgLoop ) else ( GOTO AddArg ) :AddArg set "arg=%1" set arg=%arg:"=% IF '%1'=='!arg!' ( GOTO NoQuotes ) set "batchArgs=%batchArgs% "%1"" GOTO QuotesDone :NoQuotes set "batchArgs=%batchArgs% %1" :QuotesDone shift GOTO ArgLoop :EndArgLoop ::Create and run the vb script to elevate the batch file ECHO Set UAC = CreateObject^("Shell.Application"^) > "%temp%\OEgetPrivileges.vbs" ECHO UAC.ShellExecute "cmd", "/c ""!batchPath! !batchArgs!""", "", "runas", 1 >> "%temp%\OEgetPrivileges.vbs" "%temp%\OEgetPrivileges.vbs" exit /B :START ::Remove the elevation tag and set the correct working directory IF '%1'=='ELEV' ( shift /1 ) cd /d %~dp0 ::Do your adminy thing here... 

Uso PowerShell para volver a ejecutar el script elevado si no es así. Pon estas líneas en la parte superior de tu script.

 net file 1>nul 2>nul && goto :run || powershell -ex unrestricted -Command "Start-Process -Verb RunAs -FilePath '%comspec%' -ArgumentList '/c %~fnx0 %*'" goto :eof :run :: TODO: Put code here that needs elevation 

Copié el método de ‘nombre de red’ de la respuesta de @ Matt. Su respuesta está mucho mejor documentada y tiene mensajes de error y cosas así. Éste tiene la ventaja de que PowerShell ya está instalado y disponible en Windows 7 en adelante. No hay archivos temporales de VBScript (* .vbs), y no tiene que descargar herramientas.

Este método debería funcionar sin ninguna configuración o configuración, siempre que sus permisos de ejecución de PowerShell no estén bloqueados.

Para algunos progtwigs, configurar la súper secreta variable de entorno __COMPAT_LAYER para RunAsInvoker funcionará. Verifique esto:

 set "__COMPAT_LAYER=RunAsInvoker" start regedit.exe 

Aunque así no habrá ninguna indicación de UAC que el usuario continuará sin permisos de administrador.

Pegué esto al comienzo del guión:

 :: BatchGotAdmin :------------------------------------- REM --> Check for permissions >nul 2>&1 "%SYSTEMROOT%\system32\icacls.exe" "%SYSTEMROOT%\system32\config\system" REM --> If error flag set, we do not have admin. if '%errorlevel%' NEQ '0' ( echo Requesting administrative privileges... goto UACPrompt ) else ( goto gotAdmin ) :UACPrompt echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" echo args = "" >> "%temp%\getadmin.vbs" echo For Each strArg in WScript.Arguments >> "%temp%\getadmin.vbs" echo args = args ^& strArg ^& " " >> "%temp%\getadmin.vbs" echo Next >> "%temp%\getadmin.vbs" echo UAC.ShellExecute "%~s0", args, "", "runas", 1 >> "%temp%\getadmin.vbs" "%temp%\getadmin.vbs" %* exit /B :gotAdmin if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" ) pushd "%CD%" CD /D "%~dp0" :--------------------------------------