Friday Fun: Expand Environmental Variables in PowerShell Strings

This week I was working on a project that involved using the %PATH% environmental variable. The challenge was that I have some entries that look like this: %SystemRoot%\system32\WindowsPowerShell\v1.0\. When I try to use that path in PowerShell, it complains because it doesn’t expand %SystemRoot%. What I needed was a way to replace it with the actual value, which I can find in the ENV: PSdrive, or reference as $env:systemroot. This seems reasonable enough. Take a string, use a regular expression to find the environmental variable, find the variable in ENV:, do a replacement, write the revised string back to the pipeline. So here I have Resolve-EnvVariable.

Function Resolve-EnvVariable {

[cmdletbinding()]
Param(
[Parameter(Position=0,ValueFromPipeline=$True,Mandatory=$True,
HelpMessage="Enter a string that contains an environmental variable like %WINDIR%")]
[ValidateNotNullOrEmpty()]
[string]$String
)

Begin {
    Write-Verbose "Starting $($myinvocation.mycommand)"
} #Begin

Process {  
    #if string contains a % then process it
    if ($string -match "%\S+%") {
        Write-Verbose "Resolving environmental variables in $String"
        #split string into an array of values
        $values=$string.split("%") | Where {$_}
        foreach ($text in $values) {
            #find the corresponding value in ENV:
            Write-Verbose "Looking for $text"
            [string]$replace=(Get-Item env:$text -erroraction "SilentlyContinue").Value
            if ($replace) {
                #if found append it to the new string
                Write-Verbose "Found $replace"
                $newstring+=$replace
            }
            else {
                #otherwise append the original text
                $newstring+=$text
            }
           
        } #foreach value

        Write-Verbose "Writing revised string to the pipeline"
        #write the string back to the pipeline
        Write-Output $NewString
    } #if
    else {
        #skip the string and write it back to the pipeline
        Write-Output $String
    }
} #Process

End {
    Write-Verbose "Ending $($myinvocation.mycommand)"
} #End
} #end Resolve-EnvVariable

The function takes a string as a parameter, or you can pipe into the function. The function looks to see if there is something that might be an environmental variable using a regular expression match.

if ($string -match "%\S+%") {

If there is an extra % character in the string, this won’t work so I’m assuming you have some control over what you provide as input. Now I need to get the match value. At first I tried using the Regex object. But when faced with a string like this “I am %username% and working on %computername%” it also tried to turn % and working on% as an environmental variable. I’m sure there’s a regex pattern that will work but I found it just as easy to split the string on the % character and trim off the extra space.

$values=$string.split("%") | Where {$_}

Now, I can go through each value and see if there is a corresponding environmental variable.

foreach ($text in $values) {
   #find the corresponding value in ENV:
    Write-Verbose "Looking for $text"
   [string]$replace=(Get-Item env:$text -erroraction "SilentlyContinue").Value

I turned off the error pipeline to suppress errors about unfound entries. If something was found then I do a simple replace, otherwise, I re-use the original text.

if ($replace) {
   #if found append it to the new string
   Write-Verbose "Found $replace"
   $newstring+=$replace
}
else {
   #otherwise append the original text
   $newstring+=$text
}

In essence I am building a new string adding the replacement values or original text. When finished I can write the new string, which has the variable replacements back to the pipeline.

Write-Verbose "Writing revised string to the pipeline"
#write the string back to the pipeline
Write-Output $NewString

Finally, I can pass strings that contain environmental variables to the function.

PS C:\> "I am %username% and working on %computername%" | resolve-envvariable
I am Jeff and working on SERENITY

This isn’t perfect. Look what happens if there is an undefined variable:

PS C:\> "I am %username% and working on %computername% with a %bogus% variable." | resolve-envvariable
I am Jeff and working on SERENITY with a bogus variable.

But as long as you are confident that variables are defined, then you can do things like this:

PS C:\> $env:path.split(";") | Resolve-EnvVariable | foreach { if (-Not (Test-Path $_)) {$_}}
c:\foo

Download Resolve-EnvVariable and let me know what you think. The download version includes comment based help.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati


About the Author

PowerShell.org Announcer

This is the official account for PowerShell.org and sponsor announcements.