You cannot directly execute batch-file (cmd
) commands from PowerShell (which speaks a very different language), but
you can pipe a series of batch-file commands to cmd
, the (legacy) Windows command processor, but it has severe limitations:
$commands = @'
@echo off
REM Maintenance Mode on
"D:\vdogServer\VdogMasterService.exe" /at:s /rd:E\vdServerArchive /maintenace :on
if ERRORLEVEL 1 ECHO "NOK" >> d:\MMLOG.txt
if ERRORLEVEL 0 ECHO "OK" >> d:\MMLOG.txt
'@
# !! THIS MAY OR MAY NOT WORK AS INTENDED, DEPENDING ON THE SPECIFIC COMMANDS.
# Simply sends the commands via stdin.
# /q suppresses printing the prompt between commands, and
# /d suppresses autorun entries - see cmd /?
$commands | cmd /q /d
Limitations:
for
loops and escaped %
chars. do not work,
because, cmd.exe
parses commands provided via stdin expecting interactive command-line syntax, not batch-file syntax, which - regrettably, and for historical reasons - differ:
- Batch files must use, e.g.,
%%i
as the iterator variable, whereas interactively you must use %i
(just one %
); e.g., providing a statement such as for /l %%i in (1,1,3) do echo %%i
via stdin is a quiet no-op.
- Batch files allow you to escape
%
signs as %%
(to use them as literals): for instance, you could use %%PATH%%
to produce literal %PATH%
on output; on the command line - and when piping via stdin - this does NOT work: you end up with %<value of variable>%
.
With this invocation style, cmd
will not automatically reflect the last command's exit code in its own exit code, and PowerShell's $LASTEXITCODE
will therefore NOT reflect failure. (Contrast this with invoking a batch file containing the same commands.)
- Make sure that the code exits with an explicit
exit
call to properly set the exit code.
Character-encoding caveat: You need to (temporarily) set $OutputEncoding = [Console]::InputEncoding
so as to ensure that batch commands that contain non-ASCII characters are encoded the way cmd.exe
expects (that is, based on the active OEM code page).
Finally, there is a cosmetic issue, which, however, would also affect processing the output programmatically:
- Even with
@echo off
as the first line, cmd.exe
's copyright message invariably prints first (e.g., Microsoft Windows [Version 10.0.19044.1826]...
), followed by one instance of the prompt string (e.g. C:\>
)
- Either way, each command's source-code line prints before its output.
For these reasons, you're generally better off writing the commands to a (temporary) batch file and invoking that:
Note: You can also use this function to execute the content of a batch file downloaded from the web with Invoke-WebRequest
/ Invoke-RestMethod
, as requested in this related question.
function Invoke-AsBatchFile {
param(
[string] $batchFileContents
)
# Determine a unique file path to serve as a temp. batch file.
$tempBatchFile = "$(Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName())).cmd"
# Write the commands to the batch file.
# Note: -Encoding OEM assumes that the current console window's
# active code page is at its default, the system's active OEM code page.
$batchFileContents | Set-Content -Encoding OEM -LiteralPath $tempBatchFile
# Execute the temp. batch file with pass-through arguments, if any.
# (Reflected in the automatic $args variable.)
& $tempBatchFile $args
# Remove the temp. batch file.
Remove-Item $tempBatchFile
# $LASTEXITCODE now contains the temp. batch file's exit code
# (whereas $? should be ignored).
}
Sample invocation:
$command = @'
@echo off
REM Maintenance Mode on
"D:\vdogServer\VdogMasterService.exe" /at:s /rd:E\vdServerArchive /maintenace :on
if ERRORLEVEL 1 ECHO "NOK" >> d:\MMLOG.txt
if ERRORLEVEL 0 ECHO "OK" >> d:\MMLOG.txt
'@
Invoke-AsBatchFile $command
if ($LASTEXITCODE -ne 0) { Write-Error "Something went wrong." }