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

passing quoted arguments from batch file to `powershell start` - self-elevation on demand

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

我正在使用我学到的 here to detect whether we already have admin rights and another from 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,上面的代码工作正常,但如果其中一个参数包含 space,例如 foo.bat one "two three"(第二个参数应该是两个话,"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 引用之间存在一些不正常的交互,但没有任何效果.

这可能吗?

这是我的批次:

::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:"=\""%

唯一获得批准的提升方式是使用清单。这模拟了 Unix 的 SUDO.EXE.

到运行一个命令并保持高位

RunAsAdminconsole <Command to run>

提升当前 cmd window 或创建一个新的提升的 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 


----------------------------------------
  • 你 运行 陷入了 两个 引用地狱的完美风暴 (cmd PowerShell),装饰有 PowerShell bug(从 PowerShell Core 6.2.0 开始)。

  • 要解决此错误,无法直接重新调用批处理文件,而必须通过 cmd /c.

    重新调用
  • ,考虑到这一点,通常有效,但在下面边缘情况:

    • 如果批处理文件的完整路径包含空格(例如,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