I have following loop:
for /f "tokens=1-3 delims=- " %%a in ('
powershell.exe -Command "Get-EventLog -ErrorAction SilentlyContinue -Newest 1 -LogName System -EntryType Error -Source Tcpip | ForEach-Object { \"$(Get-Date $_.TimeGenerated) - $($_.ReplacementStrings -join '#')\" }; Write-output ^$error[0]"
') do (
echo %%a %%b %%c
)
But got this error message:
- was unexpected at this time.
On the command line, the powershell command works fine.
I have tried to escape dashes and semicolons, but have had no luck.
What could be the cause of this error?
The
)characters were the problem, as Compo points out: Becausecmd.exesees what is inside the\"...\"string embedded in your overall"..."string as unquoted,[1] you would have to^-escape allcmd.exemetacharacters there, which in this case - due to the command executing inside afor /floop - 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 makescmd.exesee the content of the embedded string as quoted and therefore generally avoids the need for metacharacter-individual^-escaping.Note:
If you were to use
pwsh.exe, the PowerShell (Core) CLI rather thanpowershell.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
\""...\""withpowershell.exe(which equally applies to embedded\"...\"is that whitespace normalization could occur.[3]A few changes were made to your original command as well:
-NoProfilewas added to suppress profile loading, which makes for a more predictable execution environment and can speed up the command.The redundant
Get-Datewas removed fromGet-Date $_.TimeGeneratedWrite-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$nullcauses 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.exefor Windows PowerShell,pwshfor 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-Commandargument(s).To
pwsh.exe/pwsh(PowerShell (Core) 7+),-Fileis 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]"..."enclosure,\""...\""prevents this normalization, with both CLIs, when calling fromcmd.exe...is again considered unquoted, necessitating character-individual^-escaping ofcmd.exemetacharacters.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.Rundialog (Win+R), but outside overall"..."enclosure is again subject to whitespace normalization.Only inside overall
"..."enclosure (or individually"..."-enclosed arguments):"""works withpowershell.exe(Windows PowerShell)""works withpwsh.exe(PowerShell (Core) 7+)The implications for calling from
cmd.exe(batch files):If you're calling
pwsh.exe(PowerShell (Core) 7+):"..."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 ofcmd.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, anycmd.exemetacharacters (outside embedded double-quoted string literals, ifcmd.exehappens to see them as quoted too) require character-individual^-escaping; a simple example:Note that it is use of
\"..."\for the embedded PowerShell string literal that prevents...from being seen as unquoted bycmd.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:[1]
cmd.exehas no concept of escaped"characters, so that"and\"are equally seen as quoting characters with syntactic function. Thus,cmd.exesees something like" \"A|B\" "as three tokens: double-quoted token" \", followed by unquotedA|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 ofcmd.exe's many parsing "quirks". A minimal example:for /f %%a in (' echo (hi) ') do echo %%abreaks,for /f %%a in (' echo (hi^) ') do echo %%ais sufficient,for /f %%a in (' echo ^(hi^) ') do echo %%aworks 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 verbatimI 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) outsidefor /floops, and an even uglier workaround insidefor /floops, detailed in this answer.