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

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

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


当前回答

使用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"'"

其他回答

使用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"'"

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

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

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

有一种简单的方法,不需要使用外部工具——它在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

有用的链接:

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

我使用了Matt的精彩答案,但我发现在运行提升脚本时,Windows 7和Windows 8系统之间存在差异。

在Windows 8上提升脚本后,当前目录将被设置为c:\Windows\system32。幸运的是,有一个简单的解决方法,将当前目录更改为当前脚本的路径:

cd /d %~dp0

注意:使用cd /d确保驱动器号也被更改。

要测试这一点,可以将以下内容复制到脚本中。在两个版本上正常运行可以看到相同的结果。以Admin身份运行,看看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

我最近需要一个用户友好的方法,我根据这里和其他地方的贡献者的宝贵见解想出了这个方法。只需将这一行放在.bat脚本的顶部。欢迎您的反馈。

@pushd %~dp0 & fltmc | find "." && (powershell start '%~f0' ' %*' -verb runas 2>nul && exit /b)

无畏:

@pushd %~dp0 ensures a consistant working directory relative to this batch file; supports UNC paths fltmc a native windows command that outputs an error if run unelevated | find "." makes the error prettier, and causes nothing to output when elevated && ( if we successfully got an error because we're not elevated, do this... powershell start invoke PowerShell and call the Start-Process cmdlet (start is an alias) '%~f0' pass in the full path and name of this .bat file. Single quotes allow spaces in the path/file name ' %*' pass in any and all arguments to this .bat file. Funky quoting and escape sequences probably won't work, but simple quoted strings should. The leading space is needed to prevent breaking things if no arguments are present -verb runas don't just start this process...RunAs Administrator! 2>nul discard PowerShell's unsightly error output if the UAC prompt is canceled/ignored. && if we successfully invoked ourself with PowerShell, then... NOTE: in the event we don't obtain elevation (user cancels UAC), then && allows the .bat to continue running without elevation, such that any commands that require it will fail but others will work just fine. If you want the script to simply exit instead of running unelevated, make this a single ampersand: & exit /b) exits the initial .bat processing, because we don't need it anymore; we have a new elevated process currently running our .bat. Adding /b allows cmd.exe to remain open if the .bat was started from the command line...it has no effect if the .bat was double-clicked