I want to run a cmd file from powershell and retreive one set variable by that cmd file
my script returns a null/empty variable
Start-Process -FilePath "cmd.exe" -ArgumentList "/c 'my.cmd'" -Wait
$envVariable = [System.Environment]::GetEnvironmentVariable("my-variable", [System.EnvironmentVariableTarget]::Process)
Write-Host "Environment Variable Value: $envVariable"
the .cmd file contains lines like this
set my-variable=2.6.4.6
how can I get that value from powershell ?
thanks for your help
The problem:
Use of [System.Environment]::GetEnvironmentVariable() would only work if your batch created persistent environment variables, i.e. those stored in the registry, e.g. via setx.exe.
By contrast, cmd.exe's internal SET command creates process-scoped environment variables, i.e. they are defined for the current process only.
Because PowerShell of necessity runs a batch file (*.cmd, *.bat) in a child process (implicitly via cmd.exe), it has no way of querying the process-level environment variables created by a batch file: by the time the call returns, the batch-file process has exited, and its process-specific environment variables no longer exist.
A potential solution:
Assuming that your batch file does not use SETLOCAL before SETting its environment variables - which would be the case if its purpose is to set environment variables for the hosting cmd.exe session - you can use the following approach:
Call the batch file via the cmd.exe CLI (cmd /c) and follow the invocation with a SET command in isolation, which lists all process-level environment variables after execution of the batch file.
You can capture and compare this list to the process-level environment variables that PowerShell sees, which tells you which variables were added or updated, or even removed.
The following supports not just added and updated variables, but also propagates removal of environment variables performed by the batch file - hence the need for a Compare-Object-based solution.
If supporting removal isn't necessary, a simpler solution is possible - see this answer.
Either way, due to line-by-line processing of SET's output, only environment variables with single-line values are supported, i.e. values that do not contain newlines (values with newlines would be unusual, however).
# Get the current list of env. vars. and their values, in the same
# format that cmd.exe's SET command produces.
$envVarsBefore =
Get-ChildItem Env: | ForEach-Object { '{0}={1}' -f $_.Name, $_.Value }
# Synchronously execute the batch file and call SET from the same cmd.exe
# session to list all process-level env. vars.
# NOTE:
# * For simplicity, the batch file's own output is *discarded* (>NUL)
# See the notes below this code if you want to pass it through.
# * "PROMPT" is excluded, as it is only relevant to cmd.exe
$envVarsAfter =
cmd /c 'my.cmd >NUL & SET' | Where-Object { $_ -notmatch '^PROMPT=' }
# Now compare the two lists and define the variables in PowerShell
# that were added or updated by the batch file, and undefine (remove)
# those that were removed.
$htNamesAfter = @{}
Compare-Object -PassThru $envVarsBefore $envVarsAfter |
ForEach-Object {
# Variable was added or updated
if ($_.SideIndicator -eq '=>') {
# Split the "<name>=<value>" line into the variable's name and value.
$name, $value = $_ -split '=', 2
$htNamesAfter[$name] = $true # Record the name of the added/updated var.
# Define it as a process-level environment variable in PowerShell.
Set-Content Env:$name $value
}
# Variable was potentially removed.
else { # $_.SideIndicator -eq '<='
$name = ($_ -split '=', 2)[0]
# If the variable isn't among the updated ones, remove it.
if (-not $htNamesAfter[$name]) {
Remove-Item Env:$name
}
}
}
Note:
The above discards the batch file's own (stdout) output, under the assumption that it isn't of interest, which also simplifies processing. If you want that output to be passed through (or, if you adapt the code below accordingly to capture it separately), use the following snippet in lieu of the single $envVarsAfter = ... statement above:
# Synchronously execute the batch file and call SET from the same cmd.exe
# session to list all process-level env. vars.
# For robustness, a GUID is used to separate the batch file's own output from
# that of the SET command.
# NOTE: "PROMPT" is excluded, as it is only relevant to cmd.exe
$envVarsAfter = [System.Collections.Generic.List[string]] @()
$guid = (New-Guid).Guid; $guidFound = $false
cmd /c "my.cmd & echo $guid& SET" | # Note: NO SPACE after $guid.
Where-Object {
if ($guidFound) {
# A line resulting from the SET command, add it to the list.
if ($_ -match '^PROMPT=') { return } # Ignore the "PROMPT" variable
$envVarsAfter.Add($_)
} else {
if ($_ -eq $guid) { $guidFound = $true; return } # The separator line.
# A line output by the batch file itself.
$_ # Pass it through.
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With