将引用的参数从批处理文件传递到`powershell start` - 按需自我提升

Posted

技术标签:

【中文标题】将引用的参数从批处理文件传递到`powershell start` - 按需自我提升【英文标题】:passing quoted arguments from batch file to `powershell start` - self-elevation on demand 【发布时间】:2019-07-06 14:06:43 【问题描述】:

我正在编写一个 Windows 批处理文件,如果用户在出现的“用户访问控制”对话框中单击“是”,它会自动升级为管理权限。

我正在使用我从here 学到的一种技术来检测我们是否已经拥有管理员权限,并使用来自here 的另一个技术来升级。在适当的时候,下面的脚本,我们称之为foo.bat,通过powershell 介导的runas 调用重新启动自己:

@echo off
net session >NUL 2>NUL
if %ERRORLEVEL% NEQ 0 (
powershell start -wait -verb runas "%~dpfx0" -ArgumentList '%*'
goto :eof
)

echo Now we are running with admin rights
echo First argument is "%~1"
echo Second argument is "%~2"
pause

我的问题是在-ArgumentList 中转义引号。如果我从命令提示符调用foo.bat one two,上面的代码可以正常工作,但如果其中一个参数包含空格,例如foo.bat one "two three"(其中第二个参数应该是两个单词,“两个三”),则不能正常工作。

如果我用静态参数替换 %* 时甚至能得到适当的行为:

powershell start -wait -verb runas "%~dpfx0" -ArgumentList 'one "two three"'

然后我可以在foo.bat 中添加一些行来构成%* 的适当转义替代。然而,即使在那个静态示例中,到目前为止我尝试过的每个转义模式要么失败(我看到Second argument is "two" 而不是Second argument is "two three"),要么导致错误(通常是Start-Process: A positional parameter cannot be found that accepts argument 'two')。借鉴the docs for powershell's Start-Process,我尝试了各种荒谬的引号、插入符号、双引号和三倍引号、反引号和逗号组合,但是批处理文件引用和powershell引用之间发生了一些邪恶的交互,但没有任何效果。

这可能吗?

【问题讨论】:

您的代码适用于我在 Windows 10 上的命令提示符。您正在运行什么操作系统或命令 shell 版本? @AdminOfThings 但我猜你看到的是Second argument is "two",而不是想要的Second argument is "two three" 在打开 UAC 的 Windows 7 上运行时,代码会打开一个命令提示符窗口,其中第二个参数是“两个”。在我原来的命令提示符控制台中,关闭弹出控制台后显示“二三”。在 Windows 10 上,我只收到第二个参数是“二三”,没有额外的命令窗口。它可能没有在我的 Windows 10 系统上正常执行。 @AdminOfThings 如果您在原始控制台中看到打印的内容,则您的 goto :eof 一定是丢失了,否则它会以某种方式被忽略。糟糕,我的问题列表中缺少它,这就是编辑的原因。重要的是批处理文件的提升实例的参数,它们应该与未提升版本中的相同。 %~dpfx0 是什么?我猜你想要完整的路径,所以%~dpnx0 是长格式,%~f0 是更紧凑的... 【参考方案1】:

您遇到了两个引用地狱(cmd PowerShell)的完美风暴,并带有PowerShell bug(从PowerShell开始)核心 6.2.0)。

要解决该错误,批处理文件不能直接重新调用,而必须通过cmd /c 重新调用。

LotPings' helpful answer,考虑到这一点,通常有效,但在以下极端情况下:

如果批处理文件的完整路径包含空格(例如,c:\path\to\my batch file.cmd) 如果参数碰巧包含以下任何cmd 元字符(即使在"..." 内部):& | < > ^;例如,one "two & three" 如果 reinvoked-with-admin-privileges 批处理文件依赖于在最初调用它的同一工作目录中执行。

以下解决方案解决了所有这些边缘情况。虽然它远非微不足道,但它应该可以按原样重复使用:

@echo off
setlocal

:: Test whether this invocation is elevated (`net session` only works with elevation).
:: If already running elevated (as admin), continue below.
net session >NUL 2>NUL && goto :elevated

:: If not, reinvoke with elevation.
set args=%*
if defined args set args=%args:^=^^%
if defined args set args=%args:<=^<%
if defined args set args=%args:>=^>%
if defined args set args=%args:&=^&%
if defined args set args=%args:|=^|%
if defined args set "args=%args:"=\"\"%"
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  " Start-Process -Wait -Verb RunAs -FilePath cmd -ArgumentList \"/c \"\" cd /d \"\"%CD%\"\" ^&^& \"\"%~f0\"\" %args% \"\" \" "
exit /b

:elevated
:: =====================================================
:: Now we are running elevated, in the same working dir., with args passed through.
:: YOUR CODE GOES HERE.

echo First argument is "%~1"
echo Second argument is "%~2"

pause

【讨论】:

我希望我能投票 10 倍!这是我第一次看到真正适用于特殊字符和空格的 sn-p。【参考方案2】:

这是我的批次:

::ElevateMe.cmd::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
@echo off & setlocal EnableExtensions DisableDelayedExpansion
Set "Args=%*"
net file 1>nul 2>&1 || (powershell -ex unrestricted -Command ^
  Start-Process -Verb RunAs -FilePath '%comspec%' -ArgumentList '/c %~f0 %Args:"=\""%'
  goto :eof)
:: Put code here that needs elevation
Echo:%*
Echo:%1
Echo:%2
Pause

样本输出:

one "two three"
one
"two three"
Drücken Sie eine beliebige Taste . . .

如果您希望提升的 cmd 保持打开状态,请使用 -ArgumentList '/k %~f0 %Args:"=\""%

【讨论】:

太棒了,谢谢。值得注意的是,您的解决方案也可以推广到%* 为空的情况(我原来的没有,因为您不能传递一个空的-ArgumentList【参考方案3】:

唯一认可的提升方式是使用清单。这模拟了 Unix 的 SUDO.EXE

运行命令并保持提升

RunAsAdminconsole <Command to run>

提升当前的 cmd 窗口或创建一个新的提升的窗口

RunAsAdminconsole 

来自https://pastebin.com/KYUgEKQv


REM Three files follow
REM RunAsAdminConsole.bat
REM This file compiles RunAsAdminconsole.vb to RunAsAdminconsole.exe using the system VB.NET compiler.
REM Runs a command elevated using a manifest
C:\Windows\Microsoft.NET\Framework\v4.0.30319\vbc "%~dp0\RunAsAdminconsole.vb" /win32manifest:"%~dp0\RunAsAdmin.manifest" /out:"%~dp0\RunAsAdminConsole.exe" /target:exe
REM To use
rem RunAsAdminconsole <Command to run>
pause

RunAsAdmin.manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="*"
    name="Color Management"
    type="win32"
/>
<description>Serenity's Editor</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> 
<security> 
    <requestedPrivileges> 
        <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/> 
    </requestedPrivileges> 
</security> 
</trustInfo> 
</assembly>

'RunAsAdminConsole.vb
'Change cmd /k to cmd /c to elevate and run command then exit elevation
imports System.Runtime.InteropServices 
Public Module MyApplication  

    Public Sub Main ()
        Dim wshshell as object
        WshShell = CreateObject("WScript.Shell")
        Shell("cmd /k " & Command())
    End Sub 

End Module 


----------------------------------------

【讨论】:

它应该是%~dp0RunAsAdmin...,因为%~dp0 已经以反斜杠结尾了! 没什么区别。 VBC 不介意双反斜杠。有一天,我将用三个反斜杠进行一项实验,以检验我的假设,即它将它作为转义的反斜杠。它使批处理文件路径更加明显。 我并没有说它有什么不同,但仅仅因为在这种情况下它没有改变并不足以让它显示为不正确。 我没有看到您的其余代码,只是包含了不必要的额外反斜杠! 它是许多程序的模板。我使用长程序名称。这使得编辑更短的名称变得容易。 @Compo 的观点是有效的,顺便说一句,即使它没有实际区别。重申您对唯一批准的方式的主张:Start-Process -Verb RunAs应该适用于按需提升并且通常可以,但是有一个关于传递 双引号 参数的错误。要求使用两个帮助文件进行按需编译(至少使用硬编码路径)并不是一个实际的替代方案。

以上是关于将引用的参数从批处理文件传递到`powershell start` - 按需自我提升的主要内容,如果未能解决你的问题,请参考以下文章

将参数从批处理文件传递到 PowerShell 脚本

将输入变量从PowerShell表单传递到cmdlet参数会导致空白数据

powershell v2.0 从批处理 cmd 文件传递​​变量计算机名参数

我如何使用需要从命令提示符/批处理文件引用的参数调用PowerShell Start-Process命令?

在 powershell 中传递命令行 $args,从函数到函数

将变量从 Powershell 脚本传递到 SQLPlus