0

How do I have to change PowerShell code so that I can run it via CMD?

I came up with the following code:

$text_auslesen = Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt
$text_auslesen.Replace("Count    :","") > $env:APPDATA\BIOS-Benchmark\Count_only.txt
$text_auslesen = Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt
$text_auslesen.Replace("Average  :","") > $env:APPDATA\BIOS-Benchmark\Durchschnitt_only.txt

If I copy and paste it completely into a powershell, it can run. But now I have to put the code next to other code in a batch file. How do I have to adjust the code so that the cmd.exe executes the whole thing?

I suspect setting the variables via Powershell code is problematic here.

Unfortunately, a PS1 file is out of the question for my project.

Compo
  • 36,585
  • 5
  • 27
  • 39

4 Answers4

1
  • To execute PowerShell commands from a batch file / cmd.exe, you need to create a PowerShell child process, using the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7+) and pass the command(s) to the -Command (-c) parameter.

  • However, batch-file syntax does not support multi-line strings, so you have two options (the examples use two simple sample commands):

    • Pass all commands as a double-quoted, single-line string:

      powershell.exe -Command "Get-Date; Write-Output hello > test.txt"
      
    • Do not use quoting, which allows you to use cmd.exe's line continuations, by placing ^ at the end of each line.

      powershell.exe -Command Get-Date;^
                              Write-Output hello ^> test.txt
      

Note:

  • In both cases multiple statements must be separated with ;, because ^ at the end of a batch-file line continues the string on the next line without a newline.

  • Especially with the unquoted solution, you need to carefully ^-escape individual characters that cmd.exe would otherwise interpret itself, such as & and >

  • See this answer for detailed guidance.

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

Powershell -c executes PowerShell commands. You can do this from cmd, however, it looks like it needs to be run as administrator.

PowerShell -c "$text_auslesen = Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt;
$text_auslesen.Replace('Count    :','') > $env:APPDATA\BIOS-Benchmark\Count_only.txt;
$text_auslesen = Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt;
$text_auslesen.Replace('Average  :','') > $env:APPDATA\BIOS-Benchmark\Durchschnitt_only.txt"
dem0ur3r
  • 1
  • 2
0

It is possible to execute the PowerShell code in a batch file, but technically what you are doing is pulling a copy of it out and executing it someplace else. Here are 3 methods that I know of.

  1. mklement0's answer addresses executing a copy of it that is passed as a parameter to PowerShell.
  2. You could build a ps1 file from CMD, and then execute that ps1 file by passing it as a parameter to PowerShell.
  3. And the method I've worked with the most is to pass specially designed PowerShell code to PowerShell that, when it runs, will load all, or part, of the current CMD file into memory and execute it there as a ScriptBlock. I have tried loading parts of the current CMD file, but my experience has been that this gets too complicated and I just stick with loading the entire current CMD file.

That last method is what I'm presenting here. The trick is to make the batch/CMD portion of the script look like a comment that is ignored by PowerShell, but still runs without throwing error messages in CMD. I'm not sure where I first found this trick, but it goes like this:

  1. First, place <# : at the start of script. PowerShell sees this as the start of a comment, but CMD seems to ignore this line. I think CMD is trying to redirect < the contents of a non-existing file : to a non-existing command. But what does CMD do with the #? It works, and that's the important thing.
  2. Place your batch code in lines following the <# :.
  3. You end the batch/CMD part with a GOTO :EOF.
  4. You then end the PowerShell comment with #>, but visually I find it easier to find <#~#>, which does the same job.
  5. The rest of the file is your PowerShell code.

This version treats the PowerShell code as a function with defined parameters. The batch part builds %ARGS% and passes, with double quotes intact, to a PowerShell ScriptBlock that in turn is wrapped in another ScriptBlock. The PowerShell function is called twice with the same SourceFile parameter, but different DestinationFile and TextToRemove parameters. Perhaps there is a simpler way to reliably pass double quotes " in arguments passed to a ScriptBlock from batch, but this is the method I got working.

<# :
@ECHO OFF
    SET f0=%~f0
    SET SourceFile=%APPDATA%\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt

    SET ARGS="%SourceFile%" "%APPDATA%\BIOS-Benchmark\Count_only.txt" "Count    :"
    PowerShell -NoProfile -Command ".([scriptblock]::Create('.([scriptblock]::Create((get-content -raw $Env:f0))) ' + $Env:ARGS))"

    SET ARGS="%SourceFile%" "%APPDATA%\BIOS-Benchmark\Durchschnitt_only.txt" "Average  :"
    PowerShell -NoProfile -Command ".([scriptblock]::Create('.([scriptblock]::Create((get-content -raw $Env:f0))) ' + $Env:ARGS))"
GOTO :EOF
<#~#>
param (
    [Parameter(Mandatory = $true, Position = 0)]
    [string]$SourceFile,
    [Parameter(Mandatory = $true, Position = 1)]
    [string]$DestinationFile,
    [Parameter(Mandatory = $true, Position = 2)]
    [string]$TextToRemove
)
(Get-Content $SourceFile).Replace($TextToRemove, '') > $DestinationFile

This script passes a single parameter that, in PowerShell, is used by the Switch command to decide which section of PowerShell you intend on executing. Since we are not passing double quotes " in the args, the PowerShell lines can be greatly simplified. Information could still be passed to PowerShell by defining environmental variables in batch and reading them in PowerShell.

<# :
@ECHO OFF
    SET f0=%~f0

    PowerShell -NoProfile -Command .([scriptblock]::Create((get-content -raw $Env:f0))) Script1
    PowerShell -NoProfile -Command .([scriptblock]::Create((get-content -raw $Env:f0))) Script2

GOTO :EOF
<#~#>
switch ($args[0]) {
    'Script1' {
        (Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt).Replace("Count    :", '') > $env:APPDATA\BIOS-Benchmark\Count_only.txt
        break
    }
    'Script2' {
        (Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt).Replace("Average  :", '') > $env:APPDATA\BIOS-Benchmark\Durchschnitt_only.txt
        break
    }
    default {}
}
Darin
  • 1,423
  • 1
  • 10
  • 12
0

The -c parameter is intended to solve this scenario.

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pwsh?view=powershell-7.2#-command---c

If possible, it would be more efficient to invoke PowerShell\Pwsh directly rather than using a cmd wrapper.