Search code examples
powershellbatch-filecmd

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


Solution

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