The )
characters were the problem, as Compo points out: Because cmd.exe
sees what is inside the \"...\"
string embedded in your overall "..."
string as unquoted,[1] you would have to ^
-escape all cmd.exe
metacharacters there, which in this case - due to the command executing inside a for /f
loop - additionally means all )
instances:[2]
Use \""...\""
in lieu of \"...\"
for your embedded PowerShell string-literal, inside the overall "..."
enclosure (whose use is advisable - see the next section) - this makes cmd.exe
see the content of the embedded string as quoted and therefore generally avoids the need for metacharacter-individual ^
-escaping.
:: Note the use of \""...\"" inside the overall "..." -Command argument.
for /f "tokens=1-3 delims=- " %%a in ('
powershell.exe -NoProfile -Command "Get-EventLog -ErrorAction SilentlyContinue -Newest 1 -LogName System -EntryType Error -Source Tcpip | ForEach-Object { \""$($_.TimeGenerated) - $($_.ReplacementStrings -join '#')\"" }; \""$($error[0])\"""
') do (
echo %%a %%b %%c
)
Note:
If you were to use pwsh.exe
, the PowerShell (Core) CLI rather than powershell.exe
, the Windows PowerShell CLI, use ""...""
rather than \""...""
, inside overall "..."
, which is fully robust (but doesn't work in Windows PowerShell).
The only - largely hypothetical - concern with embedded \""...\""
with powershell.exe
(which equally applies to embedded \"...\"
is that whitespace normalization could occur.[3]
A few changes were made to your original command as well:
-NoProfile
was added to suppress profile loading, which makes for a more predictable execution environment and can speed up the command.
The redundant Get-Date
was removed from Get-Date $_.TimeGenerated
Write-Output ^$error[0]
was replaced with \""$($error[0])\""
(the escaped equivalent of "$($error[0])"
, utilizing implicit output) to print the error message only, without the stray ^
, which isn't necessary and gets included in the output (it is for that reason that it caused implicit stringification of the error-record object stored in $error[0]
, so that ^
followed by the error message text only was output).
(It is tempting to try the syntactically simpler $error[0].ToString()
, but that would fail if no errors had occurred, because attempting to call a method on $null
causes an error).
Optional reading: Guidance on whether to enclose the PowerShell commands in "..."
overall or not and how to escape embedded (pass-through) "
chars.:
The PowerShell CLI (powershell.exe
for Windows PowerShell, pwsh
for PowerShell (Core) 7+) allows you to pass commands to execute either as a single argument - which on Windows requires overall enclosure in "..."
- or as multiple arguments that are later joined together with a single space as the separator to form a single string that is then interpreted as PowerShell code.
To powershell.exe
(Windows PowerShell), -Command
(-c
) is the default parameter, so you technically do not need to specify it - all arguments following the first non-CLI parameter, if any, are considered the -Command
argument(s).
To pwsh.exe
/ pwsh
(PowerShell (Core) 7+), -File
is the default parameter, so in order to pass commands you must use -Command
(-c
).
Note: It follows that, without overall "..."
enclosure, embedded string literals (from PowerShell's perspective) - whether embedded with '...'
or with \"...\"
- are subject to whitespace normalization.[3]
- While, without overall
"..."
enclosure, \""...\""
prevents this normalization, with both CLIs, when calling from cmd.exe
...
is again considered unquoted, necessitating character-individual ^
-escaping of cmd.exe
metacharacters.
To PowerShell, any unescaped "
characters - whether around the PowerShell commands overall, or around individual arguments - are considered to have purely syntactical function on the command line, and are stripped during PowerShell's initial command-line parsing. "
chars. that should be retained as part of the PowerShell command(s) to execute must be escaped, but what forms of escaping are accepted depends on whether overall "..."
enclosure is present:
\"
always works, in both PowerShell editions, from PowerShell's perspective.
- It works robustly if you call from a no-shell environment, such as Task Scheduler or the Windows
Run
dialog (Win+R), but outside overall "..."
enclosure is again subject to whitespace normalization.
Only inside overall "..."
enclosure (or individually "..."
-enclosed arguments):
"""
works with powershell.exe
(Windows PowerShell)
""
works with pwsh.exe
(PowerShell (Core) 7+)
- Both forms avoid whitespace normalization.
The implications for calling from cmd.exe
(batch files):
If you're calling pwsh.exe
(PowerShell (Core) 7+):
- Use overall
"..."
enclosure and ""...""
for embedded strings, for a fully robust solution.
If you're calling powershell.exe
(Windows PowerShell):
Use overall "..."
enclosure and \""...\""
for embedded strings, because both \"
and """
can run afoul of cmd.exe
's parsing, as in the case at hand and as explained in footnote [1] (for \"
, but it applies analogously to """
).
This makes the embedded string subject to whitespace normalization, which is rarely a problem, however; see footnote [3].
With both CLIs, if you omit an overall "..."
enclosure, any cmd.exe
metacharacters (outside embedded double-quoted string literals, if cmd.exe
happens to see them as quoted too) require character-individual ^
-escaping; a simple example:
:: Due to no overall "..." enclosure, | must be ^-escaped.
powershell.exe -Command 1..3 ^| ForEach-Object { \"number|$_\" }
Note that it is use of \"..."\
for the embedded PowerShell string literal that prevents ...
from being seen as unquoted by cmd.exe
.
Contrast this with a solution with overall "..."
enclosure, in combination with using \""...""\
for the embedded string literal, in which case no ^
-escaping is needed:
:: Due to "..." overall and embedded \"...\", no ^-escaping needed.
powershell.exe -Command " 1..3 | ForEach-Object { \""number^|$_\"" } "
[1] cmd.exe
has no concept of escaped "
characters, so that "
and \"
are equally seen as quoting characters with syntactic function. Thus, cmd.exe
sees something like " \"A|B\" "
as three tokens: double-quoted token " \"
, followed by unquoted A|B\
, followed by double-quoted " "
. The |
in the unquoted part is then considered the usual pipe operator, and breaks a command using such a string - unless |
is ^
-escaped.
Try echo " \"A|B\" "
vs. echo " \"A^|B\" "
[2] You wouldn't expect )
characters to be to be a problem, given the overall ('...')
enclosure, but that is just one of cmd.exe
's many parsing "quirks".
A minimal example:
for /f %%a in (' echo (hi) ') do echo %%a
breaks,
for /f %%a in (' echo (hi^) ') do echo %%a
is sufficient,
for /f %%a in (' echo ^(hi^) ') do echo %%a
works too - escaping of ( as well, but not strictly needed
[3] This means that if runs of multiple spaces (whitespace characters) are embedded in what are string literals from PowerShell's perspective - whether embedded with '...'
or with \""...\""
or \"...\"
- they become a single space each, due to how PowerShell's parsing of the process command line partitions it into separate arguments that are later joined with a single space to form the PowerShell code to execute.
A minimal example:
powershell -nop -c " 'I watched the \""King & I\""' "
prints verbatim I watched the "King & I"
, i.e. the runs of multiple spaces were folded into one each.
By contrast, pwsh.exe
's support for embedded ""
does not suffer this problem, so the following preserves the original whitespace:
pwsh -nop -c " 'I watched the ""King & I""' "
In the (rare) cases where this normalization must be avoided with powershell.exe
, you can use "^""
(sic) outside for /f
loops, and an even uglier workaround inside for /f
loops, detailed in this answer.