Can MSBUILD receive NEW 'user' level environment variables without restarting Visual Studio? - powershell

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?

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>

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!

Related

Powershell Command Wont Read XML

I have a powershell script I'll be using to enhance a publish to folder profile. I want to be able to read my appsettings.json 'versionNumber' property and move the resulting file around and create a nice zip. I realize I can do all this with MSBUILD directly, but this is for a .net core project and im concerned about not doing it correctly. So for now I'm using a PS script AND a publish profile.
The powershell script refuses to read in the publish profile xml. Here is the PS script:
$foundPublishProfile = Test-Path '..\Properties\PublishProfiles\FolderProfile.pubxml'
if (!$foundPublishProfile)
{
Write-Host 'ERROR: Expected to find "..\Properties\PublishProfiles\FolderProfile.pubxml"' -ForegroundColor DarkRed
Write-Host ''
exit
}
[xml] $publishProfileXML = Get-Content '..\Properties\PublishProfiles\FolderProfile.pubxml'
Write-Host $publishProfileXML
$publishFilePath = $publishProfileXML.SelectSingleNode('//Project/PropertyGroup/PublishUrl') #| Select-Object -Expand '#text'
#$publishFilePath = Select-Xml -XPath '//Project/PropertyGroup/PublishUrl' -Path '..\Properties\PublishProfiles\FolderProfile.pubxml'
Write-Host $publishFilePath
And here is the publish profile, pubxml file:
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DeleteExistingFiles>False</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>F:\Fileshare\NCHIWebSite</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>netcoreapp3.1</TargetFramework>
<ProjectGuid>f7da8cd0-d62c-47c5-9ae7-7c9cb9a6d23d</ProjectGuid>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>
I expected to see something for the output, whether its from the loaded XML content or from my XPATH select single node, but instead I'm getting nothing. I've tried many variations. Is there something I'm doing incorrectly?
Your input XML uses namespaces, which you must account for when you use XPath queries with .SelectSingleNode() - elements that are part of a namespace cannot be located by their mere element name (such as Project), even if they're only implicitly part of a namespace via an ancestral xmlns attribute.
However, it is often simpler to use PowerShell's convenient, property-based adaptation of the XML DOM, which allows you to use simple dot notation to drill down to the element(s) of interest, and which ignores namespaces:
$publishProfileXM.Project.PropertyGroup.PublishUrl
See this answer for additional information, including how to alternatively use the Select-Xml cmdlet with XPath queries and namespaces.

Custom variables in TFS Release Management

I'm using TFS 2017.1 Builds and Release feature.
In my release definition, I have a couple of release variables which I need to refer in my PowerShell task (execute on remote machine). So far, I've tried the following options but couldn't get it to work.
Added an Execute PowerShell task to store release variables into Environment variables:
$releaseVaraiables = Get-ChildItem Env: | Where-Object { $_.Name -like "ACL_*" }
Write-Host "##vso[task.setvariable variable=aclVariables]$releaseVaraiables"
Added an Execute PowerShell on remote machine task:
Here I can't read the Environment variables (maybe because this is remote machine task?)
Write-Verbose "problem reading $env:aclVariables" -Verbose
Then I tried passing the environment variable as an argument, but that didn't work either
param
(
$RbacAccessTokenParams
)
$RbacAccessTokenParams.GetEnumerator() | % {$_.Name}
$RbacAccessTokenParams | % {
Write-Verbose "variable is $_" -Verbose
Write-Verbose "name is $_.Name" -Verbose
Write-Verbose "value is $_.Value" -Verbose
}
This is how I passed as argument
-RbacAccessTokenParams $(aclVariables)
What I'm missing here?
I've tested your scenario on my side with TFS 2017.3.1, and it works when pass the environment variable as an argument. You can upgrade your TFS first and try again. Attach my steps for your reference:
1.
2.
3.
4.
Non-secret variables are already stored as environment variables; you do not need to do anything special to access them. You can access them with $ENV:VariableName. Periods are replaced with underscores. So Foo.Bar would be $env:FOO_BAR.
Secret variables should be passed in to the script that requires them.
However, this only applies on the agent. If you're using the PowerShell On Target Machines task to run a script, you need to pass the variables as arguments to the script. There is no way around this, unless you choose to use deployment groups.
Or, better yet, follow a configuration-as-code convention and store application-specific values in source controlled configuration files that your scripts read, so that you are not tightly coupled to your deployment orchestration platform.

VSTS Build: Replacing token with build number

I'm trying to update a web.config app setting called 'AppVersion' with the build number when building my application in VSTS.
Here are my build steps:
The 'Replace tokens' step converts any variables you set for your build and replaces the tokens you've set in your config files. This part works but what it won't do is get an environment variable like the build number and do a replace. It will just replace whatever text has been specified. Here's my build variables:
So after the build step is completed, my app setting is...
<add key="AppVersion" value="$(BuildNumber)" />
when it should be something like...
<add key="AppVersion" value="20160520.1" />
Can anyone point me in the right direction?
Many thanks.
I did something similar using the "Replace Tokens in **/*config" task.
To update the value for the key "AppVersion" with the current build number, your line should look like the following,
<add key="AppVersion" value="#{Build.BuildNumber}#" />
You can add a PowerShell script task before "Replace Token" task to pass the "BuildNumber" to "AppVersion" variable as following.
In VSTS, use $(Build.BuildNumber) as specified in this doc.
Note that you cannot use $(Build.BuildNumber) to set a variable's value, because it is taken literally; it should be an argument to the task. If your task does not accept it, you can replace with a little Powershell script and the BUILD_BUILDNUMBER environment variable.
param (
[Parameter(Mandatory = $true)]
[String]$fileWithTokens,
[Parameter(Mandatory = $false)]
[String]$tokenRegex = "__(\w+)__"
)
$vars = Get-ChildItem -path env:*
$contents = Get-Content -Path $fileWithTokens
$newContents = "";
$contents | % {
$line = $_
if ($_ -match $tokenRegex) {
$setting = Get-ChildItem -path env:* | ? { $_.Name -eq $Matches[1] }
if ($setting) {
Write-Host ("Replacing key {0} with value from environment" -f $setting.Name)
$line = $_ -replace $tokenRegex, $setting.Value
}
}
$newContents += $line + [Environment]::NewLine
}
Set-Content $fileWithTokens -Value $newContents
```
Source https://github.com/colindembovsky/cols-agent-tasks/tree/master/Tasks/ReplaceTokens
After a day of research, finally found/created a better option than using any random app (Replace Token) from Marketplace.
The option I am talking is already available in VSTS, Azure CLI task.
Here are the stpes:
Add setting BUILD_NUMBER with initial value of 1.0 in appsettings.json
Read appsettings.json in your app and display it. I am sure you all are smart enough to figure out how to use appsettings to display Build Number on your WebApplication.
In Azure Portal, similarly create an App Setting named BUILD_NUMBER with initial value of 1.0 in Azure Application settings section under App Services for your App.
In VSTS, In your Release definition, add a task Azure CLI.
Populate required fields such as Azure Subscription, Script Location with Inline script and last but most important Inline Script with following CLI command
az webapp config appsettings set -n iCoreTestApi -g ArchitectsSandbox -s Dev --settings BUILD_NUMBER=$(Build.BuildNumber)
Command explanation:
iCoreTestApi should be replaced by your real WebApp or Api name in Azure
ArchitectsSandbox should be replaced by your resource group in Azure
Dev is the slot name, you may or may not have it.
Rest of the command remains same.
Once you will queue new build, after successful completion of the deployment, you can see app settings section on Azure is updated with new BUILD_NUMBER.
Let me know if you still have any question.

Gradle -v command points to old version

Currently I'm trying to update Gradle 2.0 to 2.11. In the getting-started.html file of my gradle distribution (2.11) is described how to install a newer Gradle version. I unpacked the .zip to the desired location and unpacked it. I set the environment variable GRADLE_HOME accordingly and it is included in the PATH.
However gradle -v will print:
------------------------------------------------------------
Gradle 2.0
------------------------------------------------------------
Build time: 2014-07-01 07:45:34 UTC
Build number: none
Revision: b6ead6fa452dfdadec484059191eb641d817226c
I checked my environment variable by using Get-Childitem env:GRADLE_HOME, which does print:
Name Value
---- -----
GRADLE_HOME C:\dev\programs\gradle-2.11
Restarting PowerShell or even the computer didn't help. I've also set a variable pointing to GRADLE_USER (which is to a .gradle folder) is there a known issue with that? Or did I just miss something in the Installation process?
PowerShell will run executables without a path only if they're located in one of the folders listed in the PATH environment variable ($env:Path in PowerShell). Most likely you still have the path to the old installation listed there (something like ...;C:\dev\programs\gradle-2.0\bin;...). Depending on where it's defined you need to change it in the system or your user environment.
You can avoid the need to update the PATH environment variable with every Gradle update by using the GRADLE_HOME environment variable in it. However, for this to work you must make the respective registry value a REG_EXPAND_SZ value (default is REG_SZ). The system environment is stored in this registry key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
The user environment is stored in this key:
HKEY_CURRENT_USER\Environment
You can change the type of the PATH value to REG_EXPAND_SZ with something like this:
$key = 'HKEY_CURRENT_USER\Environment'
$path = [Microsoft.Win32.Registry]::GetValue($key, 'PATH', $null)
$path += ';%GRADLE_HOME%\bin'
[Microsoft.Win32.Registry]::SetValue($key, 'PATH', $path, 'ExpandString')
or (a little more elaborate) like this:
$key = 'HKEY_CURRENT_USER\Environment'
$path = [Microsoft.Win32.Registry]::GetValue($key, 'PATH', $null) -split ';' |
Where-Object { $_ -notlike '*gradle*' }
$path = (#($path) + '%GRADLE_HOME%\bin') -join ';'
[Microsoft.Win32.Registry]::SetValue($key, 'PATH', $path, 'ExpandString')
The latter will remove an existing Gradle path from the environment variable before adding the GRADLE_HOME-based path.
Change $key to the system environement key to modify the system instead of your user environment (requires admin privileges).
By putting %GRADLE_HOME%\bin in the PATH (and have the operating system expand the variable by making the registry value a REG_EXPAND_SZ) PowerShell will always use the gradle.exe from the bin directory in your $env:GRADLE_HOME.

TFS 2013 - Update MSBuild Parameters from pre-build PowerShell script

I have recently upgraded from TFS 2012 to TFS 2013 and am trying to use the new template (TfvcTemplate.12.xaml). I need to set the version numbers of my .NET applications and WiX installers as part of this process.
In my TFS 2012 process I customised the build template using TFS Community Build Extensions to do the following:
Generate a version number. The Major and Minor version numbers are static, the release number is the number of days since 2014-11-25, the build number is the number of times the build definition has run today.
Update all AssemblyInfo.cs files with the new version number
Update the MsBuildArguments argument to pass the version as a parameter. This is so that I can set the version number in my WiX installers.
Before I resort to customising the build template again I would like to try to achieve the above using a Pre-build PowerShell script.
Items 1 and 2 were easy in PowerShell but I am stuck with the third requirement. Is it possible for the Pre-build PowerShell script to update the MSBuildArguments?
I think there is a simpler way to do this. This is what we did.
Created an include file called Version.wsi. The file contains these 4 lines:
<?xml version="1.0" encoding="utf-8"?>
<Include>
<?define CurrVersion="1.0.0.0" ?>
</Include>
In our wxs file, right after the line
<Wix xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" xmlns="http://schemas.microsoft.com/wix/2006/wi" >
add the following:
<?include Version.wxi ?>
In your wxs file use the variable $(var.CurrVersion) where you currently specify your version.
Now all you need to include in your powershell script is some code to modify the wxi file with the correct version number. This is how I do it:
function UpdateVersion($newVersion)
{
$wxiFile = gci "$Somedir\Version.wxi"
#clear the read-only bit just in case
Set-ItemProperty $wxiFile -name IsReadOnly -value $false -Force
$newContent = $content -replace "1.0.0", $prodVersion
Set-Content -Path $wxiFile -Value $newContent
}