Reputation: 32684
I'm replacing parts of a .bat script with PowerShell. Configuration for the batch files is done via files that set
appropriate environment variables. I'm looking for a way to load those variable values into the .ps1
script, without modifying the .bat
files (as they are also used in other places.
An example .bat
looks as follows:
set VAR_ONE=some_value
set VAR_TWO=/other-value
In a batch script, I'd just CALL
the configuration file and the variables would be available. I've tried both dot-sourcing (. filename.bat
) and calling (& filename.bat
) the configuration files from PowerShell, neither of those makes the variables visible. Tried accessing them with both with $VAR_ONE
and $env:VAR_ONE
syntax.
What would be a good way to load such configuration file without modifying it's format on disk?
Upvotes: 26
Views: 18160
Reputation: 438018
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.
Whatever environment variables are set in a child process (batch file) are not seen by the calling process (PowerShell).
Therefore, a workaround is required:
cmd /c 'sample.cmd >NUL & SET' |
Where-Object { $_ -notmatch '^PROMPT=' } |
ForEach-Object {
# Split the "<name>=<value>" line into the variable's name and value.
$name, $value = $_ -split '=', 2
# Define it as a process-level environment variable in PowerShell.
Set-Content ENV:$name $value
}
Note:
This recreates the batch-file environment variables as environment variables in PowerShell too, using the Env:
PowerShell drive and Set-Content
, thereby ensuring that external programs launched from your PowerShell session see them too.
Set-Variable
were used, it is PowerShell-only variables that would get created, which would only be seen by the current PowerShell session, not also by child processes (which external programs invariably run in) called from it.Executing SET
(unconditionally after the batch-file call, via &
) prints all then-current environment-variable definitions as <name>=<value>
pairs.
cmd.exe
also defines a PROMPT
environment variable, which isn't relevant to PowerShell or other processes, so it is filtered out above.Via the ForEach-Object
call, $name, $value = $_ -split '=', 2
then splits each <name>=<value>
pair into its constituent parts.
Assumptions:
A fundamental prerequisite is that the target batch file must not use setlocal
(as that would limit the scope of the variables to that batch file and the subsequent SET
command wouldn't see them); that said, batch files whose express purpose is to set environment variables for the caller by design do not.
The assumption is that sole intent of invoking the batch file is to capture the environment variables that are set by the batch file; therefore, the latter's output - if any - is discarded (>NUL
), which simplifies parsing of the output.
Also, it is assumed that it isn't necessary to propagate removal of environment variables by the batch file.
If either or both of the latter two assumptions do not hold, you can find solutions in this answer.
Upvotes: 1
Reputation: 200293
The preferred option would be to change the configuration to a .ps1
file and change the variable definitions to PowerShell syntax:
$VAR_ONE = 'some_value'
$VAR_TWO = '/other-value'
Then you'll be able to dot-source the file:
. filename.ps1
If you want to stick with the format you currently have, you'll have to parse the values, e.g. like this:
Select-String '^set ([^=]*)=(.*)' .\filename.bat | ForEach-Object {
Set-Variable $_.Matches.Groups[1].Value $_.Matches.Groups[2].Value
}
Note: The above won't work in PowerShell versions prior to v3. A v2-compatible version would look like this:
Select-String '^set ([^=]*)=(.*)' .\filename.bat | ForEach-Object {
$_.Matches
} | ForEach-Object {
Set-Variable $_.Groups[1].Value $_.Groups[2].Value
}
Upvotes: 7
Reputation: 201672
If you are using the PowerShell Community Extensions, it has a Invoke-BatchFile
that does this. I use with the Visual Studio vcvarsall.bat file to configure my PowerShell session to use the Visual Studio tools.
Upvotes: 24
Reputation: 67216
You can do that via a Batch file that first call
the configuration file and then execute the PowerShell script.
Upvotes: 3
Reputation: 4818
Assuming the .bat is called test.bat
, define testps.ps1
:
$lines = cat "test.bat"
$output = @{};
foreach ($line in $lines) {
$bits = $line.Split("=");
$name = $bits[0].Split(" ")[1];
$val = $bits[1];
$output[$name] = $val
}
return $output
Then the result is something like:
C:\temp> .\testps.ps1
Name Value
---- -----
VAR_TWO /other-value
VAR_ONE some_value
C:\temp> $x = .\testps.ps1
C:\temp> $x
Name Value
---- -----
VAR_TWO /other-value
VAR_ONE some_value
C:\temp> $x["VAR_ONE"]
some_value
There is probably a nicer way of doing the splits (will edit if I find it)
Upvotes: 1
Reputation: 67090
I'd parse them (just skip all lines that don't start with set
and split them with first =
character. You can do it from o small C# cmdlet or directly with a small PowerShell script:
CMD /c "batchFile.bat && set" | .{process{
if ($_ -match '^([^=]+)=(.*)') {
Set-Variable $matches[1] $matches[2]
}
}}
I have this code and I'm sure it comes from somewhere but credits have been lost, I suppose it comes from Power Shell Community Extensions for an Invoke-Batch
script.
Upvotes: 8