使用wmic时转义字符串

4

我尝试使用wmic启动VLC实例。

我这样做主要是因为我想捕获创建进程的pid。

我知道下面的命令在命令行中可以正常工作:

C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe rtsp://abcd --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show"

(其中 rtsp://abcd 可以是此示例中的任何输入文件)

尝试通过 wmic 运行它,使用各种不同的转义序列尝试(以下是其中之一):

wmic process create 'C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe rtsp://abcd --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show" '

始终给我相同的错误:

Invalid format.
Hint: <assignlist> = <propertyname>=<propertyvalue> [, <assignlist>].

然而以下内容:
wmic process create 'C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe rtsp://abcd --sout="#duplicate{dst=display} --rtsp-caching=120 --no-video-title-show"'

工作正常 - 但对于我来说,作为命令是无用的。因此,问题似乎出在原始命令的嵌套花括号部分。

我尝试了各种不同的转义字符...到目前为止还没有成功。有谁能建议我哪里出了问题?


请更新您的问题,以显示不使用WMIC即可正常工作的命令。我认为我可以给您提供一个解决方案,但我不确定如何解释您现有发布的代码的意图。 - dbenham
嗨@dbenham-感谢您详细的回复。如发布的命令行中所示,它可以正常工作。它正在加载vlc,然后复制流。一份副本显示在屏幕上,另一份副本显示在“video.mpg”上。“rtsp://abcd”可以是任何测试源。当按建议将其传递到wmic时,仍会出现上述错误。问题似乎出在括号部分。将其修改为删除第二组括号:C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe rtsp://abcd --sout="#duplicate{dst=display} --rtsp-caching=120 --no-video-title-show"可以工作。但这样做我已经停止了转储到文件。 - user1447903
我还不清楚 - 哪个命令有效?请编辑您的问题(在标签下面有一个编辑链接),并发布给出所需结果的确切工作命令(不使用WMIC)。 - dbenham
感谢 @dbenham - 我已经更新了问题,希望能让事情更清晰。 - user1447903
坏消息 - 可能没有解决方案。请查看我的更新答案。 - dbenham
2个回答

13
引用和转义规则在CMD中已经足够复杂了,当你添加WMIC的复杂性后,它们甚至变得更加疯狂。
WMIC通常可以使用单引号'或双引号"作为引号字符。在双引号内部的双引号可以使用\"进行转义。在单引号内部的单引号也可以使用\'进行转义,但反斜杠似乎没有被消耗,所以它似乎是无用的。
CMD只使用双引号",而且没有办法在带引号的字符串内转义"。不在引号内的毒瘤字符,例如&|<等,必须像^&这样进行转义。
尝试合并WMIC和CMD的引用和转义规则是棘手的。请记住,CMD的引用和转义是在WMIC看到命令行之前发生的。
此外,您可以使用PROCESS CALL CREATE而不是PROCESS CREATE。我想用PROCESS CREATE也可能做到,但我从未见过如何实现。

我不确定您的实际命令行是否可以在不使用WMIC的情况下运行。基于您的代码,我认为以下命令可以工作:

C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe rtsp://abcd --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show"

如果是这样,我相信下面的WMIC命令将起作用:
wmic process call create 'C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe rtsp://abcd --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show"'

WMIC必须将create后的整个命令行视为一个连续字符串。由于内容中没有空格,'vlc_117/video.mpg' 中的单引号不会造成问题。添加空格将破坏此解决方案,需要另一种策略。
如果在exe路径周围使用双引号,则应该能够使用长路径而不是短路径:
wmic process call create '"C:\Program Files\VideoLAN\VLC_117\vlc.exe" rtsp://abcd --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show"'

你可能想在批处理文件中将PID捕获到一个变量中,这时需要使用FOR /F命令。这会增加更多的复杂性。未引用的CMD标记分隔符(如=,等)必须进行转义,因为FOR /F引入了额外的解析层。未引用的毒瘤字符也必须进行转义,因为有额外的解析层。
for /f "tokens=2 delims=;= " %%N in (
  'wmic process call create '"C:\Program Files\VideoLAN\VLC_117\vlc.exe" rtsp://abcd --sout^="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show"' ^| find "ProcessId"'
) do set "pid=%%N"

在FOR /F IN()子句中的包含在单引号中的内容不会导致问题,因为它们在WMIC看到命令之前就被剥离了。但是,在--sout=...中的未引用的=必须进行转义。
我无法测试以上内容中的任何部分,因此可能没有一个可行的解决方案。然而,我所讨论的概念应该仍然是有效的。
由于引用和转义的多个层面和复杂性,对命令行的看似微小的更改可能会对解决方案产生重大影响。如果命令不是我所理解的那样,请通过编辑您的问题告诉我正确的命令,我可以尝试适应答案。

更新于2014-05-27

很不幸,PROCESS CALL CREATE选项期望第一个字符串参数是完整的命令行,可选地跟随第二个参数——工作目录。坏消息是,这些参数由逗号分隔。你的命令行也有逗号,而PROCESS CALL CREATE解析器(无论它是什么)似乎不支持命令行中的逗号 :(

我尝试过用单引号或双引号引用逗号,但这并没有帮助。我还尝试过用\转义逗号。同样,没有成功。我还谷歌了一下解决方案,但没有结果。

恐怕除非有消除逗号的VLC语法可用,或者有一种将复杂的VLC命令行参数放入某种外部脚本文件的方法,否则可能没有解决方案。或者也许有其他聪明人发现了逃避逗号的方法?

以下是一些示例,展示了我尝试过的内容。我没有VLC,所以我只用CMD /C替换了ECHO语句。每次尝试ECHO逗号都失败了。

@echo off
:: All of these commands without commas work just fine
wmic process call create 'cmd /c echo hello world^&pause'
wmic process call create 'cmd /c "echo hello world&pause"'
wmic process call create "cmd /c echo hello world&pause"

:: But none of these commands with commas work
wmic process call create 'cmd /c echo hello,goodbye^&pause'
wmic process call create 'cmd /c "echo hello,goodbye&pause"'
wmic process call create "cmd /c echo hello,goodbye&pause"
wmic process call create 'cmd /c echo hello\,goodbye^&pause'
wmic process call create 'cmd /c "echo hello\,goodbye&pause"'
wmic process call create "cmd /c echo hello\,goodbye&pause"

更新于2014年12月23日:已经找到解决方案!

在DosTips网站上,我们开发了各种方法来确定批处理脚本自身的PID。一旦知道了这一点,就可以使用WMIC列出父进程会话的所有子进程。通过仔细的记账,可以可靠地确定每个新创建的子进程的PID。但是需要编写大量代码。只有当您的命令行无法通过WMIC PROCESS CALL CREATE(逗号问题)传递时,才需要使用此方法。

@echo off
setlocal enableDelayedExpansion

:: Get the PID and UID for this batch process
call :getMyPID

:: Initialize an existing PIDs list
set "PIDs= "

:: Get a list of all existing child processes, except for the
:: child CMD.EXE process that was created by this FOR /F loop.
:: This is necessary because the console session that this script
:: is running in might have previously created a child process.
for /f %%A in (
  '2^>nul wmic process where "ParentProcessID=%myPID% and not CommandLine like '%%<%UID%>%%'" get ProcessID'
) do for %%B in (%%A) do set "PIDs=!PIDs!%%B "

:: Create your new process as you normally would.
:: For this demonstration, I will simply create a new CMD.EXE session 
start

:: Get the PID of the newly created child process by getting all child PIDs,
:: except for the child CMD.EXE process that was created by this FOR /F loop.
:: Ignore any PID that already exists in the %PIDs% list. The only PID left
:: is your new process.
for /f %%A in (
  '2^>nul wmic process where "ParentProcessID=%myPID% and not CommandLine like '%%<%UID%>%%'" get ProcessID'
) do for %%B in (%%A) do if "!PIDs: %%B =!" equ "!PIDs!" set "PID=%%B"

:: At this point you could append the new PID to the PIDs list using
::   set "PIDs=!PIDs!%PID% "
:: Then you can repeat the steps of creating a new proces and getting the new PID
::
:: But instead, I will simply show the result and exit
echo new PID=%PID%

exit /b


:getMyPID 
setlocal disableDelayedExpansion

:getLock

:: Establish a nearly unique temp file name using %time%
set "lock=%temp%\%~nx0.%time::=.%.lock"

:: Transform the full path into a UID that can be used in a WMIC query
set "uid=%lock:\=:b%"
set "uid=%uid:,=:c%"
set "uid=%uid:'=:q%"
set "uid=%uid:_=:u%"
setlocal enableDelayedExpansion
set "uid=!uid:%%=:p!"
endlocal & set "uid=%uid%"


:: Establish an exclusive lock on the temp file
:: If this fails, then loop back and try again until success
:: This guaranees no two process will ever have the same UID
2>nul ( 9>"%lock%" (

  %= The FOR /F loops creates a child CMD.EXE process which in turn invokes WMIC.         =%
  %= The child CMD.EXE process contains the WMIC query with the UID in its command line.  =%
  %= The WMIC query identifies the CMD.EXE process with the UID in the command line,      =%
  %= and returns the parent process ID, which happens to be the PID for this batch script =%
  for /f "skip=1" %%A in (
    'wmic process where "name='cmd.exe' and CommandLine like '%%<%uid%>%%'" get ParentProcessID'
  ) do for %%B in (%%A) do set "PID=%%B"
  (call ) %= Guarantee success so we don't accidently loop again =%

))||goto :getLock

:: Delete the temp file
del "%lock%" 2>nul

:: Return the result
( endlocal
  set "myPID=%PID%"
  set "UID=%uid%"
)
exit /b

噢,好吧 - 非常感谢您付出了如此多的努力来寻找解决方案,即使最终没有找到。听起来您可能尝试了许多与我相同的转义序列!是时候考虑其他选项了... - user1447903
3
我已经找到了逗号问题的解决方案。虽然不太美观,但是它很有效! - dbenham
我发现在类似的wmic情况下,我可以使用^来转义逗号,因此逗号变成了^。 - morechilli
@morechilli - 我在原始答案的第10段中将逗号 , 转义为 ^, 进行了处理。但是当尝试使用 WMIC PROCESS CALL CREATE 时,它并不起作用。因此,如果 ^, 对您有用,则我不会称之为类似的情况。 - dbenham
我的目标是鼓励其他人首先尝试它,以防他们有幸它能够起作用。我是在for循环内使用WMIC PROCESS CALL CREATE的上下文中使用它的 - 所以我认为我的情况是类似的,但并不完全相同。在这些情况下,不被注意的微小差异可能会导致最终行为上的重大差异。 - morechilli

0

我想在这里再发表一些观察。当使用WMI创建进程时,PowerShell似乎更能抵抗标记和符号混淆。只需使用反引号转义嵌套的引号即可。例如:

$addr = "rtsp://abcd"
$cmd = "cmd /k echo C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe $addr --sout=`"#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show`""
$proc = ([wmiclass]'win32_process').create($cmd)

$proc.ProcessId

Windows脚本宿主(JScript和VBScript)同样对逗号问题具有抵抗力。这里是一个Batch + JScript混合脚本(以.bat扩展名保存),以进行演示:

@if (@CodeSection == @Batch) @then
@echo off & setlocal

set "addr=rtsp://abcd"
for /f %%I in ('cscript /nologo /e:JScript "%~f0" "%addr%"') do set "PID=%%I"
echo Process: %PID%
goto :EOF

@end // end Batch / begin JScript hybrid code
var wmi = GetObject("winmgmts:root\\cimv2"),
    proc = wmi.Get("Win32_Process").Methods_("Create").InParameters.SpawnInstance_();

proc.CommandLine = 'cmd /k echo C:\\PROGRA~1\\VideoLAN\\VLC_117\\vlc.exe '
    + WSH.Arguments(0)
    + ' --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst=\'vlc_117/video.mpg\'}}} --rtsp-caching=120 --no-video-title-show"';

var exec = wmi.ExecMethod('Win32_Process', 'Create', proc);
WSH.Echo(exec.ProcessId);

对于PowerShell和JScript解决方案,都需要删除cmd /k echo,以便将您的vlc命令投入生产。

关于纯批处理,对于问题中提到的不常见且复杂的vlc命令,我无法提供太多帮助,但这可能有助于其他人。对于需要嵌入引号但不必处理逗号的较简单命令,您可以通过使用反斜杠进行转义,在wmic命令中嵌入引号。

wmic process call create "powershell \"test-connection localhost -count 1\""

如果这在您的情况下不起作用,您还可以尝试将整个值放入使用延迟扩展检索的变量中。例如:
set "proc=powershell \"test-connection localhost -count 1\""
setlocal enabledelayedexpansion
for /f "tokens=2 delims==;" %%I in (
    '2^>NUL wmic process call create "!proc!" ^| find "ProcessId"'
) do endlocal & set /a PID = %%I
echo Proc: %PID%

或者为了方便重用,你可以将它放入子程序中:
@echo off & setlocal

set "pc=localhost"
call :run PID "powershell -window normal \"test-connection %pc% -count 1\""
echo Proc: %PID%
goto :EOF

:run <varname_to_return_PID> <command>
setlocal
set proc=%*
call set proc=%%proc:%1 =%%
setlocal enabledelayedexpansion
for /f "tokens=2 delims==;" %%I in (
    '2^>NUL wmic process call create !proc! ^| find "ProcessId"'
) do (endlocal & endlocal & set /a %~1 = %%I && goto :EOF)

不幸的是,这两种纯批处理解决方案都无法解决逗号问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接