1

I have a NuGet package that sets up some PowerShell cmdlets in its Init.ps1 file, and one of the things I'd like them to be able to do is set environment variables that are passed to a build in Visual Studio.

In my Init.ps1 script I use the line:

[Environment]::SetEnvironmentVariable("MyVariable", $someValue, "User") 

...to set a 'User' level environment variable, figuring that a regular 'Process' level variable won't work since Package Manager Console is in a different process than MSBuild. Also, manually setting $env:MyVariable = "foo" in Package Manager Console does not pass its value to MSBuild.

  • In MSBuild, a regular $(MyVariable) is not populated with 'foo' as desired.
  • If I use [Environment]::GetEnvironmentVariable('MyVariable'), the overload that normally lets me target EnvironmentVariableTarget.User is not available.

The goal is to be able to drop to Package Manager Console, run an arbitrary cmdlet and have the changes persisted in properties during build. Answers that require reboot, restart or reloading a solution aren't what I'm looking for.

  • Am I missing something about environment variables?
  • Is there another simple way to set build properties from Package Manager Console that I've overlooked (short of using EnvDTE or Microsoft.Build to manually edit each project's csproj file?

Update - some further discoveries:

  • The environment variables are set correctly, and I can echo them back from command prompt too.
  • If I restart Visual Studio completely then the variable finally reaches MSBUILD, but then subsequent changes to the variable aren't picked up.

Seems like Visual Studio is caching the environment variable. Is there a way to 'refresh' a process' environment variables?

Sebastian Nemeth
  • 5,505
  • 4
  • 26
  • 36
  • After the SetEnvironmentVariable in init.ps1, does the environment effectively contain that variable (check with system->advanced->environment variables or registry)? I assume everything runs under the same user account? – stijn Jun 18 '14 at 14:59
  • Yeah, all under my own account... MSBuild receiving the variable after restarting Visual Studio at least tells us that the variable CAN reach it. Updated the question with more info. – Sebastian Nemeth Jun 18 '14 at 15:55

2 Answers2

3
  • If I use [Environment]::GetEnvironmentVariable('MyVariable'), the overload that normally lets me target EnvironmentVariableTarget.User is not available.

Are you sure?

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="Foo">
        <Exec Command="setx MyVariable Foo" />
        <Exec Command="echo 1. %MyVariable%" />
        <Exec Command="echo 2. $(MyVariable)" />
        <PropertyGroup>
            <MyVariable>$([System.Environment]::GetEnvironmentVariable('MyVariable', System.EnvironmentVariableTarget.User))</MyVariable>
        </PropertyGroup>
        <Message Text="3. $(MyVariable)" />
    </Target>
</Project>
Ilya Kozhevnikov
  • 10,242
  • 4
  • 40
  • 70
  • I guess I'm not sure! That's fantastic to know, but unfortunately it still doesn't read the environment variables from Package Manager Console without restarting Visual Studio! – Sebastian Nemeth Jun 18 '14 at 21:12
3

At the end of the day, no, I couldn't find a way to get the Environment variables without a restart.

In the end I solved this by using a separate Properties.targets (arbitrary name) to store my 'variables' like this:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">  
  <!-- Storage for global configuration properties -->
  <PropertyGroup>   
    <MyVariable1></MyVariable1>     
    <MyVariable2></MyVariable2>
  </PropertyGroup>
</Project>

and importing that into my build script with the following line:

<Import Project="Properties.targets" />

Then, to manipulate the variables I use two powershell functions, passing it $toolsPath from Init.ps1, and using this to set properties:

function SetPackageProperty($toolsPath, $name, $value) {
    $propertiesFile = [System.IO.Path]::Combine($toolsPath, "Properties.targets")
    $msbuild = New-Object -TypeName Microsoft.Build.Evaluation.Project -ArgumentList $propertiesFile
    $var = $msbuild.Xml.AddProperty($name, $value)
    $msbuild.Save()
    [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.UnloadProject($msbuild)
}

And to get properties:

function GetPackageProperty($toolsPath, $name) {
    $msbuild = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName) | Select-Object -First 1
    $propertiesFile = [System.IO.Path]::Combine($toolsPath, "Properties.targets")
    $msbuild = New-Object -TypeName Microsoft.Build.Evaluation.Project -ArgumentList $propertiesFile
    $var = $msbuild.Xml.Properties | Where-Object {$_.Name -eq $name} | Select-Object -First 1
    [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.UnloadProject($msbuild)
    return $var.Value   
}

You can import Properties.targets into any build script you want to use the properties.

Hope this helps somebody!

Michael Baker
  • 3,338
  • 26
  • 29
Sebastian Nemeth
  • 5,505
  • 4
  • 26
  • 36