« Prettifying Monad Code Listings | Main | The Gates Foundation »

October 23, 2005

Get Environment Variables from a .CMD script

One issue that crops up with Monad is that when you execute a .CMD script, it launches a child cmd.exe process to run it--Monad doesn't directly interpret the .CMD language. If the .CMD files just does something, like copy a file, then this is fine, but cmd.exe has this "feature" (which makes some Unix people gag) that any environment variables set inside a script affect the environment table for that process. This lack of scoping is often taken advantage of to write scripts that set up the environment (for example, the command-line environment that the Windows team uses is setup by such a script, called for historical reasons "razzle.cmd").

So people running Monad often hit this problem--they run a .CMD script to set up the environment, it sets up the environment of the child cmd.exe process, and then when that is done it exits so the environment changes are not reflected in the parent. One solution is to run the .CMD program first (with the /K parameter so the cmd.exe window sticks around) and then run msh.exe. This works, but you can't run another .CMD-environment-setter once you are inside msh.exe.

So I wrote this script to grab the settings from a .CMD script--it runs the script also, so any actions performed by the script are also done. Usage is:

scriptname.msh foo.cmd

where scriptname.msh is the name you give the script when you save it, and foo.cmd is the .CMD file you want to run (Note to Andy, the syntax for setting an environment variable in Monad is $env:goo = "Hello"):

# First save the current environment in a hash table

$oldenv = @{}
get-childitem env: | foreach-object { $oldenv[$_.Key] = $_.Value }

# Now create a temp file to hold the new environment and temp .cmd file

$envfile = [System.IO.Path]::GetTempFileName()
$tmpcmdfile = [System.IO.Path]::GetTempFileName()
$cmdfile = $tmpcmdfile + ".cmd"

# Create the temp .cmd file

add-content -Path $cmdfile -Value ("call " + $args[0])
add-content -Path $cmdfile -Value "@echo off"
add-content -Path $cmdfile -Value "set > `"$envfile`""

# Run the temp .cmd file

cmd.exe /c $cmdfile

# Get the environment after

$newenv = @{}
get-content $envfile |
  foreach-object {
    $eq = $_.IndexOf("=")
    $k = $_.Substring(0,$eq)
    $v = $_.Substring($eq+1)
    $newenv[$k] = $v

# Compare the old and new environments

$newenv.Keys |
  foreach-object {
    if (!$oldenv.ContainsKey("$_")) {
        set-item -Path env:$_ -Value $newenv[$_]
    } elseif ($oldenv[$_] -ne $newenv[$_]) {
        set-item -Path env:$_ -Value $newenv[$_]

$oldenv.Keys |
  foreach-object {
    if (!$newenv.ContainsKey("$_")) {
        remove-item -Path env:$_

# Clean up the temp files

remove-item $envfile
remove-item $tmpcmdfile
remove-item $cmdfile

This script is old (I wrote the first version in July of last year) and could probably be optimized. In fact Bruce Payette saw it and immediately whipped up essentially the same thing in one line:

cmd /c “mybatchfile.cmd&set” |
foreach {
  if ($_ -match “=”) {
    $v = $_.split(“=”); set-item -force -path "ENV:\$($v[0])"  -value "$($v[1])"

However my longer one does a bit more (it handles deleted environment variables) and also shows some hash table usage and creation of temp files.

Posted by AdamBa at October 23, 2005 05:35 PM

Trackback Pings

TrackBack URL for this entry:


Hehe...I was wondering why you could get the value of an environment variable, but not set it. :) If nothing else, at least I learned a bit more about Monad from writing my own way of getting and setting environment variables.

Posted by: Andy at October 23, 2005 06:43 PM

Cool I was thinking of writing a similar script. However I was trying to think of a way to retrieve the cmd variables from from the process. I didn't think about just parsing the output from set :), that works pretty well.

Posted by: Wes Haggard at October 25, 2005 08:28 AM