Search code examples
powershellbatch-filecmd

Returning a variable value from a batch script to powershell


I have a batch script that is not able to be changed as it is owned by another application. I want to call that batch script from Powershell and access the variable values. This looks to be a scope issue and maybe I am not calling the batch script correctly. I have tried "&", "invoke-expression", "cmd /c" and "start-process" but the result is the same. Here is the simplified problem.

I have a batch script called batch.bat containing two lines:

set /p variable=what is the fruit:
echo %variable%    (from batch)

I have a powershell script containing:

$fp=resolve-path(".\batch.bat")
Invoke-Expression -Command "$fp"
write-host "$env:variable    (from PS)"

from cmd prompt:

set variable=banana
powershell .\script.ps1
what is the fruit: orange
orange    (from batch)
banana    (from PS)

I want orange to be available to PS. If I hadn't preset the variable to banana, then PS would not display anything so it has nothing to do with presetting the value. I considered that I could write another wrapper command script that executes the original batch script, then writes out all the variables (in PS format) to a temp .ps1 file and then execute (dot source) that but it seems clunky.


Solution

  • Calling a batch file from PowerShell of necessity executes the batch file in a child process, given that a cmd.exe child process must be launched to execute it.

    Child processes fundamentally cannot modify their caller's environment variables.

    Your best bet is to call the batch file explicitly via cmd.exe /v /c and then echo the values of the environment variables of interest, which allows PowerShell to capture them:

    $variableValue = cmd /v /c '.\batch.bat >&2 & echo !variable!'
    "Value of %variable%: $variableValue"
    
    • /v is needed to enable delayed variable expansion, which allows the echo command that is part of the same statement that invokes .\batch.bat to dynamically report values that were set by the latter, using !...! rather than %...% to enclose the variable reference.

      • Note that this can affect the execution of .\batch.bat, in that ! characters then become metacharacters, which - unless they deliberately enclose a variable reference - are quietly removed.
      • See the next section for an alternative solution that avoids this problem.
    • >&2 is used to redirect .\batch.bat's stdout output to stderr (stream 2) so that it still displays, but doesn't interfere with what the cmd /v c outputs to stdout, which is what PowerShell captures.


    If you want to echo all variables that exist after the batch file has exited, as <name>=<value> pairs, you use the following, which parses the name-value pairs into a single hasthable, via ConvertFrom-StringData:

    # Assign to a variable (e.g, $hash = ...) as needed.
    # You can then use $hash.variable to get the value of %variable%, for instance.
    (cmd /c '.\batch.bat >&2 & set' | Out-String) -replace '\\', '\\' |
      ConvertFrom-StringData
    

    set without an argument outputs all variables as name-value paris, but you can more selective, by using set <variableNamePrefix> and/or multiple, &-chained set statements with exact variable names (e.g. set variable).

    Note: -replace '\\', '\\' looks like a no-op, but it replaces \ instances with \\, because a single \ is interpreted as an escape character by ConvertFrom-StringData.