Передача аргументов в кавычках из командного файла в `powershell start` - само-повышение по требованию
Я пишу пакетный файл Windows, который автоматически переходит в административные полномочия при условии, что пользователь нажимает "Да" в открывшемся диалоговом окне "Контроль доступа пользователей".
Я использую технику, которую я изучил здесь, чтобы определить, есть ли у нас уже права администратора и еще одна, которая будет расширяться. При необходимости, следующий скрипт, давайте назовем его 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
которые составляют соответственно избежавшую замену %*
, Однако даже на этом статическом примере каждый шаблон escape, который я пробовал до сих пор, либо не удался (я вижу, Second argument is "two"
скорее, чем Second argument is "two three"
) или вызвало ошибку (обычно Start-Process: A positional parameter cannot be found that accepts argument 'two'
). Опираясь на документы для начального процесса PowerShell, я перепробовал все смешные комбинации кавычек, кареток, двойных и тройных кавычек, обратных кавычек и запятых, но между цитированием пакетных файлов и цитированием PowerShell происходит какое-то нечестивое взаимодействие. работал.
Это вообще возможно?
3 ответа
Вы столкнулись с идеальным штормом двух цитирующих адов (
cmd
и PowerShell), дополненный ошибкой PowerShell (начиная с PowerShell Core 6.2.0).Чтобы обойти эту ошибку, пакетный файл не может быть повторно вызван напрямую, а должен быть повторно вызван через
cmd /c
,Полезный ответ LotPings, который учитывает это, обычно работает, но не в следующих крайних случаях:
- Если полный путь пакетного файла содержит пробелы (например,
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
Это моя партия для этой цели:
::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:"=\""%
Единственный одобренный способ поднять это использовать манифест. Это эмулирует SUDO.EXE Unix.
Чтобы выполнить команду и оставаться на возвышении
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
----------------------------------------