JaneksCraft
JaneksCraft

Reputation: 83

Run CMD Code in Powershell with ECHO

how can I execute the following example in a PowershellScript?

 @echo off
 REM Maintenance Mode on
 "C:\ProgramFiles\vdogServer\VDogMasterService.exe" /at:s /rd:C:\vdServerArchive /maintenance:on
 if ERRORLEVEL 1 ECHO "versiondog Server wurde nicht ordnungsgemäß in den Wartungsmodus versetzt." >> d:\log.txt
 if ERRORLEVEL 0 ECHO "versiondog Server wurde ordnungsgemäß in den Wartungsmodus versetzt." >> d:\log.txt

I tried that without success:

$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-Expression -Command:$command

Im a Beginner in Powershell yet, would be nice if someone has a solution for that, BR

Upvotes: 1

Views: 4515

Answers (2)

mklement0
mklement0

Reputation: 437109

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." }

Upvotes: 3

BenH
BenH

Reputation: 10034

Edited to test exit code per comment:

#Maintenance Mode on 
& "C:\ProgramFiles\vdogServer\VDogMasterService.exe" /at:s /rd:C:\vdServerArchive /maintenance:on
if ($LASTEXITCODE -eq 0) {
    "versiondog Server wurde ordnungsgemäß in den Wartungsmodus versetzt." | out-file d:\log.txt -append
} else {
    "versiondog Server wurde nicht ordnungsgemäß in den Wartungsmodus versetzt." | out-file d:\log.txt -append
}

Upvotes: 1

Related Questions