передача аргументов в кавычках из командного файла в `powershell start` - самоповышение по требованию

Я пишу пакетный файл Windows, который автоматически расширяет себя до административных разрешений, если пользователь нажимает «Да» в появившемся диалоговом окне «Управление доступом пользователя».

Я использую метод, который я изучил здесь, чтобы определить, есть ли у нас уже права администратора, а другой - от здесь для эскалации. Когда это уместно, следующий скрипт, назовем его foo.bat, повторно запускает себя через вызов runas, опосредованный powershell:

@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'). Использование документов для Powershell Start-Process я пробовал всевозможные нелепые комбинации кавычек, знаков вставки, двойных и тройных кавычек, обратных кавычек и запятых, но между цитированием пакетного файла и цитированием powershell происходит какое-то нечестивое взаимодействие, и ничего сработало.

Это вообще возможно?


person jez    schedule 12.02.2019    source источник
comment
Ваш код работает для меня в Windows 10 из командной строки. Какую версию ОС или командной оболочки вы используете?   -  person AdminOfThings    schedule 13.02.2019
comment
@AdminOfThings Но я предполагаю, что вы видите Second argument is "two", а не желаемое Second argument is "two three"   -  person jez    schedule 13.02.2019
comment
При работе в Windows 7 с включенным UAC код открывает окно командной строки со вторым аргументом, равным двум. В моей исходной консоли командной строки после закрытия всплывающей консоли отображается два три. В Windows 10 я получаю только второй аргумент: два, три без дополнительного командного окна. Вероятно, он не выполняется должным образом в моей системе Windows 10.   -  person AdminOfThings    schedule 13.02.2019
comment
@AdminOfThings, если вы видите что-то напечатанное в исходной консоли, ваш goto :eof должен отсутствовать или каким-то образом его игнорируют. Упс, его нет в моем списке в вопросе, поэтому — отредактировано. Важно то, какие аргументы у экземпляра командного файла с повышенными правами, и они должны быть такими же, как и в версии без повышенных прав.   -  person jez    schedule 13.02.2019
comment
Что такое %~dpfx0? Я думаю, вам нужен полный путь, поэтому %~dpnx0 в длинной форме или %~f0 в более компактной...   -  person aschipfl    schedule 13.02.2019


Ответы (3)


  • Вы столкнулись с целой чередой двух адов цитирования (cmd и PowerShell), украшенных ошибка PowerShell (начиная с PowerShell Core 6.2.0).

  • Чтобы обойти эту ошибку, пакетный файл нельзя повторно вызвать напрямую, вместо этого его необходимо повторно вызвать через cmd /c.

  • Полезный ответ LotPings, который принимает это во внимание, обычно работает, но не в следующих пограничных случаях:

    • If the batch file's full path contains spaces (e.g., c:\path\to\my batch file.cmd)
    • Если аргументы содержат любой из следующих cmd метасимволов (даже внутри "..."): & | < > ^; например, one "two & three"
    • Если пакетный файл, повторно вызванный с правами администратора, полагается на выполнение в том же рабочем каталоге, из которого он был первоначально вызван.

Следующее решение касается всех этих крайних случаев. Хотя это далеко не тривиально, его можно использовать повторно как есть:

@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
person mklement0    schedule 15.02.2019
comment
Хотел бы я проголосовать за это 10 раз! Это первый раз, когда я видел фрагмент кода, который действительно работал со специальными символами и пробелами. - person N Jones; 27.11.2019

Это моя партия для этой цели:

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

Если вы хотите, чтобы командная строка с повышенными правами оставалась открытой, используйте -ArgumentList '/k %~f0 %Args:"=\""%

person Community    schedule 12.02.2019
comment
Потрясающе, спасибо. Стоит отметить, что ваше решение также обобщается на случай, когда %* пусто (в моем оригинале этого нет, потому что вы не можете передать пустое -ArgumentList) - person jez; 14.02.2019

Единственный одобренный способ повышения — использовать манифест. Это эмулирует 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 


----------------------------------------
person Noodles    schedule 13.02.2019
comment
Это должно быть %~dp0RunAsAdmin..., так как %~dp0 уже расширяется обратной косой чертой! - person Compo; 14.02.2019
comment
Это не имеет значения. VBC не возражает против двойной обратной косой черты. Однажды я проведу эксперимент с тремя обратными косыми чертами, чтобы проверить свою гипотезу о том, что он воспринимает их как экранированную обратную косую черту. Это делает пути пакетных файлов более очевидными. - person Noodles; 14.02.2019
comment
Я не сказал, что это имело значение, но только потому, что это не так, в данном случае это не достаточная причина, чтобы оставить его неверным. Я не вижу остальной части вашего кода, просто включая ненужные дополнительные обратные слэши! - person Compo; 14.02.2019
comment
Это шаблон для многих программ. Я использую длинные имена программ. Это упрощает редактирование более коротких имен. - person Noodles; 14.02.2019
comment
Кстати, точка зрения @ Compo верна, даже если это не имеет практического значения. Что касается вашего заявления о единственном утвержденном способе: Start-Process -Verb RunAs должен работать для повышения прав по запросу и обычно работает, но есть ошибка< /i> относительно передачи аргументов в двойных кавычках. Требование компиляции по требованию с двумя вспомогательными файлами — с использованием жестко закодированных путей — не является практичной альтернативой. - person mklement0; 14.02.2019
comment
Существует также проблема, заключающаяся в том, что он работает только в том случае, если пользователь не изменил этот раздел реестра. Поэтому не может быть никаких гарантий, что это сработает. Опять же, это мышление пользователя, а не программиста. Программисты не притворяются пользователями. - person Noodles; 14.02.2019
comment
@ mklement0 Этот путь будет работать на всех версиях Windows от XP до Windows 10. Он находится в известном месте. Все приложения, совместимые с UAC, должны иметь запрошенный уровень выполнения, добавленный в манифест приложения, чтобы скрипты не могли этого сделать. Нет надежных способов обойти UAC, кроме как в Windows. И вы создаете его ОДИН РАЗ и используете повторно. - person Noodles; 14.02.2019
comment
@ mklement0 Даже не ошибка, а просто случай крайней синтаксической неловкости, о чем свидетельствует тот факт, что ответ LotPings успешно проходит через нее. - person jez; 14.02.2019
comment
@Noodles Мне было бы интересно узнать больше об этой проблеме: когда вы говорите, что это работает, только если пользователь не изменил этот раздел реестра можно уточнить? О чьем решении вы говорите и о каком ключе реестра? - person jez; 14.02.2019
comment
@jez HKEY_LOCAL_MACHINE\SOFTWARE\Classes\exefile\shell\runas добавляет команду «Запуск от имени администратора» в EXE-файлы. А вот некоторые люди с проблемой answers.microsoft.com/en-us/windows/forum/all/ и neowin.net/forum/topic/ - person Noodles; 14.02.2019
comment
Итак, вы говорите, что если этот ключ реестра был изменен (или поврежден, как в этом потоке), тогда подход powershell -verb runas не удастся, тогда как ваш подход vbc + manifest все еще будет работать? - person jez; 14.02.2019
comment
И вирусы тоже делают это bleepingcomputer.com/ форумы/t/483782/ и - person Noodles; 14.02.2019
comment
@jez Да, это то, что я говорю. И вирус, не требуя прав администратора, может переопределить этот параметр, создав HKEY_CURRENT_USER\SOFTWARE\Classes\exefile\shell\runas (поскольку пользовательские ключи переопределяют системные ключи). И я повторяю свою точку зрения - есть только ОДИН правильный способ подъема. - person Noodles; 14.02.2019
comment
Это объясняет, как это работает docs.microsoft.com/en-us/windows/security/identity-protection/ - person Noodles; 14.02.2019
comment
Также не существует НЕТ правильного способа поднять уровень. Некоторые люди пытаются имитировать отмену повышения, лишая токен привилегий, но это приводит к странным результатам, когда пользователь звонит администратору, чтобы ввести имя и пароль администратора. Так что это то, что вы не пытаетесь - вы структурируете свою программу так, чтобы она работала так, как хочет Windows. - person Noodles; 14.02.2019
comment
Хорошо знать. Я попробовал ваш код. Он скомпилировался нормально, но затем при вызове RunAsAdminConsole.exe whatever.bat я получил двухсекундную паузу, отсутствие запроса согласия и «доступ запрещен» на выходе консоли. - person jez; 14.02.2019
comment
Вы не проходите к нему путь. После повышения текущий каталог меняется на C:\windows\system32. Это решение Microsoft сделать это. - person Noodles; 14.02.2019
comment
@jez: это это ошибка, характерная для объединения -Verb RunAs со встроенными аргументами в двойных кавычках внутри одного аргумента -ArgumentList — см. github.com/PowerShell/PowerShell/issues/8898. Действительно, ответ LotPings обходит ошибку способом, который обычно работает (как это неявно подтверждается вашим принятием этого ответа); однако есть крайние случаи, которые он не обрабатывает, что и мое решение (надеюсь). - person mklement0; 15.02.2019
comment
@Noodles: Приятно знать, что статический путь работает от W7 до W10 (будет ли он там в будущих версиях?), но важнее то, что есть способ решить проблему Джеза, используя < i>встроенная функциональность, без необходимости прибегать к созданию вспомогательного исполняемого файла, который (а) далеко не прост и требует от вас либо (б) встраивания кода для создания этого исполняемого файла по запросу в ваш скрипт ( еще дальше от прямолинейности) или (c) для развертывания этого исполняемого файла на целевых машинах заранее. - person mklement0; 15.02.2019
comment
Windows поставляется с версиями 2, 3.5 и 4 компиляторов x32 и x64. - person Noodles; 15.02.2019