批处理文件中的Powershell - 如何转义元字符?

时间:2017-08-14 02:27:42

标签: powershell batch-file parameter-passing powershell-v2.0 quoting

运行Windows 7,当我将文件复制到外部磁盘时,在例行文件备份期间,我使用Powershell v2(从批处理文件运行) )在复制文件上重新创建原始文件的所有时间戳。

以下代码在大多数情况下都能成功运行,但并非总是如此: -

SET file=%1
SET dest=E:\

COPY /V /Y  %file% "%dest%"

SetLocal EnableDelayedExpansion
FOR /F "usebackq delims==" %%A IN ('%file%') DO (
      SET fpath=%%~dpA
      SET fname=%%~nxA
)

PowerShell.exe (Get-Item \"%dest%\%fname%\").CreationTime=$(Get-Item \"%fpath%%fname%\" ^| Select-Object -ExpandProperty CreationTime ^| Get-Date -f \"MM-dd-yyyy HH:mm:ss\")

当我将源文件拖放到我的批处理文件中时,上面的代码会复制文件,然后将复制(目标)文件的创建日期/时间设置为源文件的创建日期/时间。

但是在某些情况下代码会失败。如果文件名包含“毒药”。字符,例如(例如)方括号 [...],它会给出错误" Property' CreationTime'无法在此对象上找到"。在“毒药”中解析文件名明显失败了。字符。

代码会出现& 等符号错误。

我尝试了使用单引号和双引号转义Powershell命令的大量变体,但没有成功。请有人告诉我如何逃避Powershell反对的那些角色。

这只是一个更长的批处理例程的一小部分,我依赖它进行常规系统备份。我没有选择切换到.ps1文件,所以我需要一个在批处理文件中工作的解决方案,而不是.ps1文件。

感谢所有建议。

ADDENDUM :我找到了一个解决方案,采用了mklement0提供的一个建议。通过将以下命令替换为我原来的Powershell命令 -

,可以克服方括号的问题
PowerShell.exe (Get-Item -LiteralPath \"%dest%\%fname%\").CreationTime=$(Get-Item -LiteralPath \"%fpath%%fname%\" ^| Select-Object -ExpandProperty CreationTime ^| Get-Date -f \"MM-dd-yyyy HH:mm:ss\")

为了将来参考,请注意(在 Windows 7 上):

  1. 使用此修订版命令可成功保留任何多余的空格字符。 必须包含一对额外的双引号字符才能实现这一目标。

    • 编者注:这是一个边缘案例,但值得指出:没有额外的双引号,任何多个空格的运行都会折叠成一个空间;例如,
      powershell.exe -command echo \"a b\"会产生a b 将整个命令括在"..."中有助于原则上 -
      powershell.exe -command "echo \"a b\"" - 但由于cmd.exe然后没有将整个字符串识别为单个双引号字符串,元字符可以中断命令;例如,
      powershell.exe -command "echo \"a & b\""
  2. 文件路径可以包含任何" (双引号)字符,因此不需要代码来转义该字符。双引号字符是FAT和NTFS文件系统中的非法字符,因此永远不会在文件的路径中遇到。

  3. 原则上使用' (单引号)在Powershell命令中,因为该字符在NTFS文件系统中不是非法的,所以可以在文件的实际路径中找到。必须首选使用双引号,因为双引号字符是非法的,在实际的NTFS路径中永远不会遇到。

  4. 使用ROBOCOPY,以下通配符解决方案成功甚至大多数毒性字符 - 除了之外(即它可以应对 =&` ^ )。这个命令非常强大,如果有更多而不是一个毒药角色(虽然不是万无一失):

    ROBOCOPY "%fpath% " "%dest%" "*%name%*%ext%*" /B /COPY:DAT /XJ /SL /R:0 /W:0 /V

    一个。 "%fpath%"中的空格这是必不可少的,这不是错误。

    湾在所有情况下唯一致命的毒药是EXCLAMATION MARK(!)。

    ℃。毒性字符似乎只是FILENAME中的问题,而不是路径中的问题。

3 个答案:

答案 0 :(得分:2)

首先要做的事情:

在Windows 7中,您可以使用robocopy.exe复制文件,默认情况下会保留时间戳(并且可以选择让您详细控制要复制的属性):

@echo off
:: Do NOT use setlocal ENABLEDELAYEDEXPANSION, because it would cause
:: misinterpretation of  "!" chars. in filenames.    
setlocal

:: Parse the file path given as %1 (the first argument) into its folder path and filename.
:: Be sure to pass the %1 argument *double-quoted* to prevent up-front interpretation 
:: by cmd.exe; e.g.:
::   someBatchFile "c:\tmp\foo.txt" or someBatchFile "%file%"
:: Note that %~dp1 always returns a path with a trailing "\".
set "fpath=%~dp1"
set "fname=%~nx1"

:: Determine the destination folder
set "dest=E:\"

:: Use robocopy to copy the file to the destination dir. with timestamps preserved.
:: Syntax is: <source-dir> <dest-dir> <filename-or-wildcard> ...
:: IMPORTANT: To avoid problems with paths that end in "\", always follow
::            a variable reference inside "..." with a *space* (a trick discovered by
::            Ed999 himself).
robocopy "%fpath% " "%dest% " "%fname%"

注意:

  • 虽然robocopy主要用于复制整个目录,但 允许您通过从指定的通配符表达式复制单个文件第三个​​位置参数,如上面的"%fname%" 鉴于robocopy - 与PowerShell不同 - 不考虑[]通配符metacharacters,这种方法应该有效(如果您的文件名包含<,则只会出现问题< em>嵌入式 *?字符,这不太可能。)

  • 尾随空间技巧(例如"%fpath% ")是必要的,因为robocopy - 正如大多数命令行实用程序所做的那样 - 在结束时处理\"参数为转义 ",它打破了命令。严格地说,正确的解决方法是双重尾随\(例如,"E:\\"),但你可以通过附加空间来逃脱,因为路径中的任何尾随空格都被忽略。因此,使这些调用健壮的一种简单方法是在传递文件夹路径时始终使用"%var% "(在结束双引号之前的尾随空格)。

  • robocopy没有类似于/V的开关,导致它验证文件是否已正确复制,但是 - 至少根据this blog post - 正在运行verify on } beforehand应该具有相同的效果。

如果您仍需要通过PowerShell复制创建时间戳:

powershell -command "(Get-Item -LiteralPath '%dest%%fname%').CreationTime=(Get-Item -LiteralPath '%fpath%%fname%').CreationTime"

警告:如果您的文件名可能嵌入了'个字符。 (单引号/撇号),你必须首先转义它们,通过加倍它们(例如,%fname:'=''%返回%fname%的全部值'个实例翻倍):

powershell -command "(Get-Item -LiteralPath '%dest:'=''%%fname:'=''%').CreationTime=(Get-Item -LiteralPath '%fpath:'=''%%fname:'=''%').CreationTime"
  • 请注意,整个命令都包含在"..."中,以防止可能包含在变量值中的cmd.exe元字符(例如&)打破命令。 [1]

  • 命令字符串中,'...'用于确保PowerShell将变量值视为 literals (如果您使用了{{例如,1}}和包含"..."的文件名,结果将是意外的。)

  • $确保-LiteralPath将文件路径解释为文字,而它是 传递路径位置时隐含的Get-Item参数,以及传递给
    的路径 -Path被解释为通配符表达式,这可能会导致PowerShell通配符元字符出现问题,例如-Path[

  • 无需先将源文件的]属性值转换为日期+时间字符串;您只需将其直接分配给目标文件的.CreationTime属性,该属性的类型为.CreationTime

[1] 引用头疼

  • [System.DateTime]中包含变量引用 - 转义双引号,如问题(\),不是因为这样做会将值赋予空格规范化,这意味着多个空格的运行被规范化为单个空间。

  • 虽然另外\"%dest%\%fname%\"中附加整个的命令原则上有帮助,但"..."却无法识别整体string作为单个双引号字符串,在这种情况下,变量值中的cmd.exe等元字符可以中断命令;例如,
    &工作得很好,忠实地保留了空白,但是 powershell.exe -command "echo \"a b\""由于powershell.exe -command "echo \"a & b\""而导致&休息。

    • 使用\""代替\"解决元字符问题,但重新引入空格规范化:
      powershell.exe -command "echo \""a & b\"""收益a & b
  • 因此,在整个'...'字符串中使用"..." PowerShell字符串是使命令健壮的最简单方法:您只需要在变量值中转义'个实例

答案 1 :(得分:0)

归根结底,这是我在批处理文件中提出的最佳解决方案。

通过在Windows命令shell中使用REN和SET命令,它可以100%可靠地工作,它们没有ROBOCOPY或POWERSHELL的缺点。

@echo off

::  **  INPUT File **
    SET file=%1

::  **  Destination Directory **
    SET dest=C:\Users\%Username%\Desktop\test

::  **  Copy using Command Shell **
    COPY /V /Y  %file% "%dest%"

::  **  Location of PowerShell **
    SET PowerShell=C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -NoProfile

::  ** Store PATH & NAME of source file (WITHOUT quotation marks) **
    FOR /F "usebackq delims==" %%A IN ('%file%') DO (
          SET fpath=%%~dpA
          SET fname=%%~nxA
    )

::  ** Rename the Files **
    REN "%fpath%%fname%" temp1
    REN "%dest%\%fname%" temp2

::  ** Set CREATED date of output file **
::  NB: Will fail if MONTHS (MM) is identical to MINUTES (mm)
    %PowerShell% (Get-Item \""%dest%\temp2\"").CreationTime=$(Get-Item \""%fpath%temp1\"" ^| Select-Object -ExpandProperty CreationTime ^| Get-Date -f \"MM-dd-yyyy HH:mm:ss\")

::  ** Set MODIFIED date of output file **
::  NB: Will fail if MONTHS (MM) is identical to MINUTES (mm)
    %PowerShell% (Get-Item \""%dest%\temp2\"").LastWriteTime=$(Get-Item \""%fpath%temp1\"" ^| Select-Object -ExpandProperty LastWriteTime ^| Get-Date -f \"MM-dd-yyyy HH:mm:ss\")

::  ** Wait **
    ::  No, I have no idea why it doesn't work without this...
    echo. & echo Wait 15 Seconds ... & echo.
    @CHOICE /T 15 /C yn /D y > NUL

::  ** Restore ORIGINAL filenames **
    REN "%fpath%temp1" "%fname%"
    REN "%dest%\temp2" "%fname%"

答案 2 :(得分:0)

不是通过篡改以前的答案来淹没水域,而是在这里找到我最新的&#39;&#39;关于这个问题。

一个月的额外摆弄导致我放弃了我以前的解决方案(效果很好!),其中需要重命名文件(尽管我做了找到一个真正的整洁的想法:如果您采用我的简单解决方案,通过将文件重命名为任何无害的临时名称,无法运行任何有毒的元字符运行Powershell或Robocopy)。

但下面是一个从上述想法中剔除的解决方案,经过一个月的测试,它还没有引发任何失败。并且它没有采取任何&#34;未经授权的&#34;通过重命名文件的快捷方式!

以下批处理文件现在位于我的Windows 7发送到文件夹中,因此始终可以从Windows资源管理器右键单击菜单访问。

Windows 7&#34; send-to&#34;文件夹:

<强> C:\用户\%USERNAME%\应用程序数据\漫游\微软\的Windows \的SendTo

@echo off

::  *** Copy file including its CREATED date & MODIFIED date ***

::  File : Drag-and-Drop
    SET file=%1

::  Destination Directory
    SET dest=E:\

::  ** Safety Checks **
    ATTRIB -R -A -S -H  %file%

::  ** Store PATH & NAME of file (WITHOUT quotation marks) **
    FOR /F "usebackq delims==" %%A IN ('%file%') DO (
      SET fpath=%%~dpA
      SET fname=%%~nxA
      SET  name=%%~nA
      SET   ext=%%~xA
    )

    ::  *** POWERSHELL : File only ***

    ::  ** Copy File **
    COPY /V /Y  %file% "%dest%"

    ::  ** Location of PowerShell **
    SET PowerShell=C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -NoProfile

    ::  ** Set CREATED date of copy **
    %PowerShell% -command "(Get-Item -LiteralPath '%dest:'=''%\%fname:'=''%').CreationTime=(Get-Item -LiteralPath '%fpath:'=''%%fname:'=''%').CreationTime"

    ::  ** Set MODIFIED date of copy **
    %PowerShell% -command "(Get-Item -LiteralPath '%dest:'=''%\%fname:'=''%').LastWriteTime=(Get-Item -LiteralPath '%fpath:'=''%%fname:'=''%').LastWriteTime"

    ::  ** Set ACCESSED date of copy **
    %PowerShell% -command "(Get-Item -LiteralPath '%dest:'=''%\%fname:'=''%').LastAccessTime=(Get-Item -LiteralPath '%fpath:'=''%%fname:'=''%').LastAccessTime"

    ::  Open Destination Directory
    C:\Windows\Explorer.exe "%dest%"