使用wmic时转义字符串

时间:2014-05-23 10:46:52

标签: cmd wmic

我正在尝试使用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"'

工作正常 - 除了作为命令对我来说没用。所以问题似乎是我原始命令的嵌套卷曲括号部分。

我尝试了各种不同的逃脱角色......到目前为止还没有成功。任何人都可以建议我哪里出错了?

2 个答案:

答案 0 :(得分:9)

在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必须在创建为一个连续字符串后才能看到整个命令行。 '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解析器(无论是什么)似乎不支持命令行中的逗号:(

我试过在单引号或双引号中引用逗号,但这并没有帮助。我还尝试用@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" 转义逗号。再一次,没有运气。我还搜索了一个解决方案,然后空了。

我担心可能没有解决方案,除非有可用的VLC语法消除逗号,或者是否有办法将复杂的VLC命令行参数放在某种类型的外部脚本文件中。或者也许其他一些聪明的人发现了逃避逗号的方法?

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

@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

更新2014-12-23:已找到解决方案!

在DosTips上,我们一群人开发了various methods for a batch script to determine its own PID。知道后,可以使用WMIC列出父会话的所有子进程。通过仔细记账,可以可靠地确定每个新创建的子进程的PID。但它需要相当多的代码。只有在命令行无法通过WMIC PROCESS CALL CREATE(逗号问题)传递时才需要这样做。

{{1}}

答案 1 :(得分: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)同样可以抵抗逗号问题。这是一个批处理+ 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命令投入生产。


RE:纯批处理,对于该问题中引用的异常复杂的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)

不幸的是,这些纯批处理解决方案都不能解决逗号问题。