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.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
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.
Jeff- dont look now but ther eis a net lcasds staic method that does exactly that. YOU can also use the shell method but it takes two lines.
shell.ExpandEnvironmentStrings()
If I remember correctly “ExpandEnvironmentStrings” is a Win32 API call.
Fine. Rain on my parade!! But remember my Friday Fun articles are as much about concepts and techniques as practical solutions.
09:15 PS>$sh = new-object -com WScript.Shell
09:15 PS>$sh.ExpandEnvironmentStrings(“\\test\%computername%\%username%\myfile.txt”)
\\test\OMEGA2\jvierra\myfile.txt
Sure, but who wants to use a COM object?! You can tell I’ve been away from VBScript for a long time.
Sorry – How about a NEt Framework object exposed in POwerSHell as a type accellerator? Hmm?
09:17 PS>[environment]::ExpandEnvironmentVariables(‘I am %username% and working on %computername%’)
I am jvierra and working on OMEGA2
Sorry – I just stumbled on that myself a couple of days ago. It took me qa few minutes to figure out my memory of it. I tried [env] and [context] etc then searched and found it in a second.
Nice little bit. It is the foundation for the “env:” provider.
Now that is helpful.
Here’s a short function using this technique:
Function Resolve-EnvVar {
[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True)]
[string]$string
)
Process {
[environment]::ExpandEnvironmentVariables($string)
}
}
I guess because I’m not a developer I don’t think to look in the framework for a solution. I figure stuff out from what I can see in the shell. But this is nice.
PS S:\> "%systemroot%\foo" | resolve-envvar | test-path
False
And I say stay away from VBScript – wish I could.
I didn;t mean to rain on your “parade’ and the echniques aer useful and good. I shows how easy it is in POwerSHell to quickly provide mazing functionality with little effort. Very Good. Keep up the blogging.
So here’s a quick and dirty function using the COM object.
Function Resolve-EnvVar {
[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True)]
[string]$string
)
Begin {
$shell=new-object -com wscript.shell
}
Process {
$shell.ExpandEnvironmentStrings($string)
}
End {}
}
This is better at handling undefined variables.
PS S:\> "I am %username% on %computername% with a %bogus% variable" | resolve-envvar
I am Jeff on SERENITY with a %bogus% variable
WHy is it better? They should both be the same API call.
I don’t care about API calls. What matters is how easy it is to get at it. Some of my comments here are not chronological. The COM and .NET approach both do the same thing. but I’d say the latter is a bit more “economical”.
[environment]::ExpandEnvironmentVariables(‘I am %username% and working on %systemroot%’)
That is economical.
Nice little exercise. Thanks for sharing. Good to know that there are API calls for this but ofcourse it takes away the fun. I figured it would be possible to write the same thing using regular expressions;
Function Resolve-EnvVariable($expr)
{
$eval = [System.Text.RegularExpressions.MatchEvaluator]{
param($m)
$v = [Environment]::GetEnvironmentVariable($m.ToString().Trim("%"))
if ($v) { $v } else { $m };
}
[regex]::Replace($expr, "%.+?%", $eval)
}
That’s a cool way too, although regex always requires beer.
Now Jeff wwent and said the magic word and reminded me that I am out. No more typing today until we remedy the refrigerator situation.
Good weekend all.
just for fun:
Function Resolve-EnvVar {
[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True)]
[string]$string
)
iex "$env:ComSpec /c echo $string"
}
I’m just a little confused and having trouble with determining a use case. Why not just use
"I am $env:username working on $env:computername"
? We are using PowerShell after all. ;-)
The issue isn’t expanding PowerShell environmental variables but rather strings that might contain DOS-type environmental variables like %WINDIR%.
I am facing a very strange issue. I set an environment variable from Powershell and add it in the path as below
$mypath=[environment]::GetEnvironmentVariable(“PATH”,,”User”)
[environment]::SetEnvironmentVariable(“TEST”,”MyTestVariable”,”User”)
[environment]::SetEnvironmentVariable(“PATH”,$mypath+”;%TEST%”,”User”)
When I want to see the path in windows command shell I see following, which is very strange for me
c:\>echo %TEST%
MyTestVariable
c:\>path
;%TEST%;
in path, the TEST variable is not expanding and i am not able to use this feature. For echo it works fine though. I am very confused.
You told PowerShell to add a literal string %TEST% to your path and it did. PowerShell can’t expand it like that. What you did was the same as this, which is much easier.
[environment]::SetEnvironmentVariable(“Path”,”$env:path;%TEST%”,”user”)
The CMD shell can’t expand that in the path. Why not just do this:
[environment]::SetEnvironmentVariable(“Path”,”$env:path;c:\work”,”user”)
If you need more help, I suggest using the forums at PowerShell.org.