如何自动提升我的batch file,以便在需要时从UACpipe理员权限请求?

我想我的batch file只运行提升。 如果不boost,请提供一个选项让用户重新启动批次。

我正在写一个batch file来设置系统variables,将两个文件复制到Program Files位置,然后启动一个驱动程序安装程序。 如果Windows 7 / Windows Vista用户( UAC已启用,即使他们是本地pipe理员)在没有右键单击并select“以pipe理员身份运行”的情况下运行它,他们将得到“访问被拒绝”,复制这两个文件并写入系统variables。

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

你可以让脚本自己调用psexec的-h选项来运行提升。

我不知道你将如何检测,如果它已经运行提升或不…也许重新尝试与提升的烫发只有当有一个访问被拒绝的错误?

或者,您可以简单地将xcopyreg.exe的命令始终与psexec -h一起运行,但是如果每次需要input密码,最终用户都会感到烦恼(如果包含密码,则不安全)在脚本)…

有一个简单的方法,而不需要使用外部工具 – 它与Windows 7,8,8.1和10运行良好,并向后兼容(Windows XP没有任何UAC,因此不需要提升 – 在那如果脚本正在进行)。

看看这段代码(我受到NIronwolf的代码的启发,发布在线程batch file – “访问被拒绝”在Windows 7? ),但我已经改进了 – 在我的版本中没有任何目录创build和删除检查pipe理员权限):

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

该脚本利用了NET FILE需要pipe理员权限的事实,如果没有,则返回错误errorlevel 1 。 通过创build一个脚本来重新启动batch file来获取权限来实现高程。 这会导致Windows显示UAC对话框并要求您inputpipe理员帐户和密码。

我已经用Windows 7,8,8.1,10和Windows XPtesting过了 – 它可以正常工作。 优点是,在开始点之后,可以放置任何需要系统pipe理员权限的任何内容,例如,如果打算重新安装并重新运行Windows服务以进行debugging(假定mypackage.msi是服务安装程序包) :

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

如果没有这个特权提升脚本,UAC会问你三次pipe理员用户名和密码 – 现在你只需要在开始时询问一次,只有在需要的时候才会问你。


如果您的脚本只需要显示一条错误消息,并在没有任何pipe理员权限而不是自动提升的情况下退出则更简单:您可以通过在脚本开始处添加以下内容来实现此目的:

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

这样,用户必须右键单击并select“以pipe理员身份运行”。 如果REM语句检测到pipe理员权限,则该脚本将继续执行,否则退出并显示错误。 如果您不需要PAUSE ,只需将其删除。 重要: NET FILE [...] EXIT /D)必须位于同一行。 它在这里显示在多行以更好的可读性!


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

我现在通过剥离path中的引号并稍后重新添加它们来解决此问题,并且在脚本重新启动时使用提升的权限添加了一个额外的参数。

双引号被删除以下(细节在这里 ):

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

您可以通过使用!batchPath!来访问path!batchPath! 。 它不包含任何双引号,所以可以说"!batchPath!"是安全的"!batchPath!" 稍后在脚本中。

该线

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

检查脚本是否已被VBScript脚本调用以提升权限,从而避免无限recursion。 它使用shift移除参数。


更新:

  • 为了避免在Windows 10中注册.vbs扩展名,我已经replace了这一行
    "%temp%\OEgetPrivileges.vbs"
    通过
    "%SystemRoot%\System32\WScript.exe" "%temp%\OEgetPrivileges.vbs"
    在上面的脚本中; 还添加了由Stephen(单独的答案)和TomášZato(评论)build议的cd /d %~dp0来将脚本目录设置为默认值。

  • 现在脚本将传递给它的命令行参数。 感谢jxmallet,TanisDLJ和Peter Mortensen的观察和启发。

  • 根据Artjom B.的提示,我分析了它,并用SHIFT /1代替了SHIFTSHIFT /1保留了%0参数的文件名

  • del "%temp%\OEgetPrivileges_%batchName%.vbs"部分添加del "%temp%\OEgetPrivileges_%batchName%.vbs"以清除(如mltbuild议)。 添加了%batchName%以避免影响,如果你并行运行不同的批次。 请注意,您需要使用for才能够利用高级string函数(如%%~nk ,该函数只提取文件名。

  • 优化的脚本结构,改进(添加variablesvbsGetPrivileges ,现在引用到处都允许轻松地更改文件的path或名称,只需要删除.vbs文件,如果批次需要提升)

  • 在某些情况下,提升需要使用不同的调用语法。 如果脚本不起作用,请检查以下参数:
    set cmdInvoke=0
    set winSysFolder=System32
    请更改第一个参数来set cmdInvoke=1并检查是否已经解决了问题。 它会将cmd.exe添加到执行高程的脚本。
    或者尝试将第二个参数更改为winSysFolder=Sysnative ,这可能有助于(但在大多数情况下不需要)在64位系统上。 (ADBailey已经报道了这一点)。 只有从32位脚本主机(例如Visual Studio构build过程或从另一个32位应用程序调用脚本)启动64位应用程序时才需要“系统性”。

  • 为了更清楚地解释参数是如何解释的,现在我将它显示为P1=value1 P2=value2 ... P9=value9 。 如果您需要用双引号括住path等参数,例如"C:\Program Files" ,这是特别有用的。

正如jcoder和Matt提到的那样,PowerShell使得它变得简单,甚至可以embedded到批处理脚本中,而不需要创build新的脚本。

我修改了马特的剧本:

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

:getPrivileges标签没有任何需要。

我正在使用马特的优秀答案,但在运行提升脚本时,我看到了Windows 7和Windows 8系统之间的区别。

在Windows 8上升级脚本后,当前目录设置为C:\Windows\system32 。 幸运的是,通过将当前目录更改为当前脚本的path有一个简单的解决方法:

 cd /d %~dp0 

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

为了testing这个,你可以将以下内容复制到脚本中。 在任一版本上正常运行以查看相同的结果。 以pipe理员身份运行并查看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 

我这样做:

 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默认命令。 如果你需要重新分配你的batch file,这是非常好的。

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

%~nx0返回当前文件名的扩展名(如果你不包括扩展名,并且在文件夹中有一个同名的exe文件,它会调用exe文件)。

在这篇文章上有这么多的回复,我甚至不知道我的回复是否会被看到。

无论如何,我发现这种方式比其他解决scheme提出的其他解决scheme更简单,我希望它可以帮助别人。

如果不是,则使用PowerShell重新启动脚本。 把这些行放在脚本的最上面。

 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 

我从@ Matt的回答中复制了“网名”方法。 他的回答是更好的logging和错误消息等。 这个有一个优点,就是PowerShell已经安装在Windows 7和Windows 7上。 没有临时的VBScript(* .vbs)文件,而且您不必下载工具。

只要您的PowerShell执行权限未被locking,此方法应该可以在没有任何configuration或设置的情况下运行。

马特有一个很好的答案,但它删除了传递给脚本的任何参数。 这是我的修改,保持论点。 我还在Windows 8中join了Stephen的工作目录问题修复。

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

对于一些程序设置超级密钥__COMPAT_LAYER环境variablesRunAsInvoker 将工作 。检查这一点:

 set "__COMPAT_LAYER=RunAsInvoker" start regedit.exe 

虽然这样会有没有UAC提示用户将继续没有pipe理员权限。

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

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