我希望我的批处理文件只运行提升。如果没有提升,为用户提供一个选项,以提升的方式重新启动批处理。

我正在编写一个批处理文件来设置一个系统变量,将两个文件复制到Program files位置,并启动一个驱动程序安装程序。如果Windows 7/Windows Vista用户(启用了UAC,即使他们是本地管理员)在没有右键单击并选择“以管理员身份运行”的情况下运行它,他们将得到“访问拒绝”,复制这两个文件并写入系统变量。

如果用户实际上是管理员,我想使用一个命令自动重新启动提升的批处理。否则,如果他们不是管理员,我想告诉他们,他们需要管理员权限来运行批处理文件。我使用xcopy复制文件和REG ADD写入系统变量。我正在使用这些命令来处理可能的Windows XP机器。我在这个主题上发现了类似的问题,但没有一个是关于重新启动批处理文件的。


当前回答

一行批处理用户提升(带参数)

以下是我对这个古老的批量用户提升问题的单行版本,这个问题在今天仍然是相关的。 只需将代码添加到批处理脚本的顶部,就可以开始了。

沉默

这个版本不会输出任何东西,也不会在出错时暂停执行。

@setlocal disabledelayedexpansion enableextensions
@echo off

:: Admin check
fltmc >nul 2>nul || set _=^"set _ELEV=1^& cd /d """%cd%"""^& "%~f0" %* ^"&&((if "%_ELEV%"=="" ((powershell -nop -c start cmd -args '/d/x/s/v:off/r',$env:_ -verb runas >nul 2>nul) || (mshta vbscript:execute^("createobject(""shell.application"").shellexecute(""cmd"",""/d/x/s/v:off/r ""&createobject(""WScript.Shell"").Environment(""PROCESS"")(""_""),,""runas"",1)(window.close)"^) >nul 2>nul)))& exit /b)

详细的

详细版本,告诉用户正在请求管理权限,并在退出前暂停错误。

@setlocal disabledelayedexpansion enableextensions
@echo off

:: Admin check
fltmc >nul 2>nul || set _=^"set _ELEV=1^& cd /d """%cd%"""^& "%~f0" %* ^"&&((if "%_ELEV%"=="" (echo Requesting administrator privileges...&((powershell -nop -c start cmd -args '/d/x/s/v:off/r',$env:_ -verb runas >nul 2>nul) || (mshta vbscript:execute^("createobject(""shell.application"").shellexecute(""cmd"",""/d/x/s/v:off/r ""&createobject(""WScript.Shell"").Environment(""PROCESS"")(""_""),,""runas"",1)(window.close)"^) >nul 2>nul))) else (echo This script requires administrator privileges.& pause))& exit /b)

echo Has admin permissions
echo Working dir: "%cd%"
echo Script dir: "%~dp0"
echo Script path: "%~f0"
echo Args: %*

pause

操作方法

Uses fltmc to check for administrator privileges. (system component, included in Windows 2000+) If user already has administrator privileges, continues operation normally. If not, spawns an elevated version of itself using either: powershell (optional Windows feature, included in Windows 7+ by default, can be uninstalled/otherwise not available, can be installed on Windows XP/Vista) mshta (system component, included in Windows 2000+) If fails to acquire elevation, stops execution (instead of looping endlessly).

这个解决方案与其他解决方案的区别是什么?

实际上,解决这个问题的方法有数百种,但我迄今为止发现的所有方法都有其缺点,这是解决其中大多数问题的尝试。

Compatibility. Using fltmc as the means of checking for privileges and either powershell or mshta for elevation works with every Windows version since 2000 and should cover most system configurations. Does not write any extra files. Preserves current working directory. Most of the solutions found conflate "script directory" with "working directory" which are totally different concepts. If you want to use "script directory" instead, replace %cd% with %~dp0. Some people advocate using pushd "%~dp0" instead so paths inside networked UNC paths like "\\SOMEONES-PC\share" will work but that will also automagically map that location to a drive letter (like Y:) which might or might not be what you want. Stops if unable to acquire elevation. This can happen because of several reasons, like user clicking "No" on the UAC prompt, UAC being disabled, group policy settings, etc. Many other solutions enter an endless loop on this point, spawning millions of command prompts until the heat death of the universe. Supports (most of) command-line arguments and weird paths. Stuff like ampersands &, percent signs %, carets ^ and mismatching amount of quotes """'. You still definitely CAN break this by passing a sufficiently weird combinations of those, but that is an inherent flaw of Windows' batch processing and cannot really be worked around to always work with any combination. Most typical use-cases should be covered though and arguments work as they would without the elevation script.

已知的问题

If you enter a command-line argument that has a mismatched amount of double-quotes (i.e. not divisible by 2), an extra space and a caret ^ will be added as a last argument. For example "arg1" arg2" """" "arg3" will become "arg1" arg2" """" "arg3" ^. If that matters for your script, you can add logic to fix it, f.ex. check if _ELEV=1 (meaning that elevation was required) and then check if the last character of argument list is ^ and/or amount of quotes is mismatched and remove the misbehaving caret.

日志输出到文件的示例脚本

您不能轻易地使用>进行标准输出日志记录,因为在提升时将生成一个新的cmd窗口并切换执行上下文。

你可以通过传递越来越奇怪的转义字符组合来实现它,比如raise .bat testarg ^^^> test.txt,但你需要让它总是生成新的cmd窗口,或者添加逻辑来去掉插入符号,所有这些都增加了复杂性,在许多情况下它仍然会中断。

最好和最简单的方法是在批处理脚本中添加日志记录,而不是尝试从命令行重定向。这样你就不用再头疼了。

这里有一个例子,你可以很容易地实现你的脚本日志:

@setlocal disabledelayedexpansion enableextensions
@echo off

:: Admin check
fltmc >nul 2>nul || set _=^"set _ELEV=1^& cd /d """%cd%"""^& "%~f0" %* ^"&&((if "%_ELEV%"=="" (echo Requesting administrator privileges...&((powershell -nop -c start cmd -args '/d/x/s/v:off/r',$env:_ -verb runas >nul 2>nul) || (mshta vbscript:execute^("createobject(""shell.application"").shellexecute(""cmd"",""/d/x/s/v:off/r ""&createobject(""WScript.Shell"").Environment(""PROCESS"")(""_""),,""runas"",1)(window.close)"^) >nul 2>nul))) else (echo This script requires administrator privileges.& pause))& exit /b)

set _log=
set _args=%*
if not defined _args goto :noargs
set _args=%_args:"=%
set _args=%_args:(=%
set _args=%_args:)=%
for %%A in (%_args%) do (if /i "%%A"=="-log" (set "_log=>> %~n0.log"))
:noargs

if defined _log (echo Logging to file %~n0.log) else (echo Logging to stdout)
echo Has admin permissions %_log%
echo Working dir: "%cd%" %_log%
echo Script dir: "%~dp0" %_log%
echo Script path: "%~f0" %_log%
echo Args: %* %_log%

echo Hello World! %_log%

pause

执行:logtest.bat -log 通过添加参数-log,输出将记录到文件中,而不是记录到stdout。

封闭的思想

让我困惑的是,即使在UAC存在了15年之后,一个简单的“ELEVATE”指令还没有被引入到批处理中。也许有一天微软会振作起来。在那之前,我们只能求助于这些黑客。

其他回答

您可以使用psexec的-h选项让脚本调用自身以提升运行。

我不确定你如何检测到它是否已经处于升高状态……也许只有在出现“访问被拒绝”错误时才会重新尝试提升烫发?

或者,你可以简单地让xcopy和reg.exe命令总是使用psexec -h运行,但是如果最终用户每次都需要输入密码(或者如果你在脚本中包含密码则不安全),这对他们来说会很烦人……

使用powershell。

如果cmd文件很长,我使用第一个需要提升,然后调用一个做实际工作。

如果脚本是一个简单的命令,那么所有内容都可以放在一个cmd文件中。不要忘记在脚本文件中包含路径。

模板:

@echo off
powershell -Command "Start-Process 'cmd' -Verb RunAs -ArgumentList '/c " comands or another script.cmd go here "'"

示例1:

@echo off
powershell -Command "Start-Process 'cmd' -Verb RunAs -ArgumentList '/c "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\BIN\x.ps1"'"

示例2:

@echo off
powershell -Command "Start-Process 'cmd' -Verb RunAs -ArgumentList '/c "c:\bin\myScript.cmd"'"

我把这个粘贴在脚本的开头:

:: 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"
:--------------------------------------

有一种简单的方法,不需要使用外部工具——它在Windows 7、8、8.1、10和11上运行良好,并且向后兼容(Windows XP没有任何UAC,因此不需要提升——在这种情况下,脚本就会继续)。

看看这段代码(我受到了NIronwolf在线程批处理文件中发布的代码的启发-在Windows 7上“访问被拒绝”?),但我已经改进了它-在我的版本中,没有任何目录创建和删除来检查管理员权限):

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

该脚本利用了NET FILE需要管理员权限这一事实,如果没有管理员权限,则返回errorlevel 1。提升是通过创建一个脚本来实现的,该脚本重新启动批处理文件以获得特权。这将导致Windows显示UAC对话框,并要求您输入管理员帐户和密码。

我已经在Windows 7、8、8.1、10、11和Windows XP上测试过了,在所有操作系统上都运行良好。 这样做的好处是,在起始点之后,您可以放置任何需要系统管理员权限的东西,例如,如果您打算为了调试目的重新安装和重新运行Windows服务(假设mypackage。Msi是一个服务安装包):

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

如果没有这个特权提升脚本,UAC会三次询问您的管理员用户名和密码——现在您只会在开始时被询问一次,而且是在需要的时候。


如果你的脚本只需要显示一个错误消息,并在没有任何管理员权限时退出,而不是自动提升,这甚至更简单:你可以通过在脚本开头添加以下内容来实现:

@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 ...

这样,用户必须右键单击并选择“以管理员身份运行”。如果检测到管理员权限,脚本将在REM语句之后继续执行,否则将报错退出。如果您不需要PAUSE,只需删除它。 重要:NET文件[…]EXIT /D)必须在同一行。为了更好的可读性,它在这里以多行方式显示!


在一些机器上,我遇到了一些问题,这些问题在上面的新版本中已经解决了。一个是由于不同的双引号处理,另一个问题是由于UAC在Windows 7机器上被禁用(设置为最低级别),因此脚本一次又一次地调用自己。

我现在已经通过剥离路径中的引号并在以后重新添加它们来修复这个问题,并且我还添加了一个额外的参数,当脚本以提升的权限重新启动时添加。

双引号被以下方法删除(详细信息在这里):

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

然后可以使用!batchPath!访问该路径。它不包含任何双引号,因此在脚本后面使用“!batchPath!”是安全的。

这条线

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

检查脚本是否已经被VBScript脚本调用以提升权限,从而避免无休止的递归。它使用shift删除参数。


更新:

To avoid having to register the .vbs extension in Windows 10, I have replaced the line "%temp%\OEgetPrivileges.vbs" by "%SystemRoot%\System32\WScript.exe" "%temp%\OEgetPrivileges.vbs" in the script above; also added cd /d %~dp0 as suggested by Stephen (separate answer) and by Tomáš Zato (comment) to set script directory as default. Now the script honors command line parameters being passed to it. Thanks to jxmallet, TanisDLJ and Peter Mortensen for observations and inspirations. According to Artjom B.'s hint, I analyzed it and have replaced SHIFT by SHIFT /1, which preserves the file name for the %0 parameter Added del "%temp%\OEgetPrivileges_%batchName%.vbs" to the :gotPrivileges section to clean up (as mlt suggested). Added %batchName% to avoid impact if you run different batches in parallel. Note that you need to use for to be able to take advantage of the advanced string functions, such as %%~nk, which extracts just the filename. Optimized script structure, improvements (added variable vbsGetPrivileges which is now referenced everywhere allowing to change the path or name of the file easily, only delete .vbs file if batch needed to be elevated) In some cases, a different calling syntax was required for elevation. If the script does not work, check the following parameters: set cmdInvoke=0 set winSysFolder=System32 Either change the 1st parameter to set cmdInvoke=1 and check if that already fixes the issue. It will add cmd.exe to the script performing the elevation. Or try to change the 2nd parameter to winSysFolder=Sysnative, this might help (but is in most cases not required) on 64 bit systems. (ADBailey has reported this). "Sysnative" is only required for launching 64-bit applications from a 32-bit script host (e.g. a Visual Studio build process, or script invocation from another 32-bit application). To make it more clear how the parameters are interpreted, I am displaying it now like P1=value1 P2=value2 ... P9=value9. This is especially useful if you need to enclose parameters like paths in double quotes, e.g. "C:\Program Files". If you want to debug the VBS script, you can add the //X parameter to WScript.exe as first parameter, as suggested here (it is described for CScript.exe, but works for WScript.exe too). Bugfix provided by MiguelAngelo: batchPath is now returned correctly on cmd shell. This little script test.cmd shows the difference, for those interested in the details (run it in cmd.exe, then run it via double click from Windows Explorer): @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

有用的链接:

批处理文件中特殊字符的含义:引号(")、空格(!)、插入号(^)、&号(&)、其他特殊字符

我是这样做的:

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

这样做很简单,只使用windows默认命令。 如果你需要重新分发你的批处理文件,这是很好的。

CD /d %~dp0将当前目录设置为文件的当前目录(如果还不是,则与文件所在的驱动器无关,这要归功于/d选项)。

返回当前带扩展名的文件名(如果你不包含扩展名,并且文件夹中有一个同名的exe,它将调用exe)。

这个帖子有这么多回复,我甚至不知道我的回复是否会被看到。

无论如何,我发现这种方法比其他答案上提出的其他解决方案更简单,我希望它能帮助到一些人。