Can't access build variables from powershell script - powershell

I'm trying to use the Build variables in a script. According to this documentation I should be able to use the following:
Write-Host "BUILD_DATE: $Env:BUILD_DATE"
Write-Host "BUILD_REV: $Env:BUILD_REV"
However, I only get the following output
BUILD_DATE:
BUILD_REV:
I've also tried this syntax:
Write-Host "BUILD_DATE: $(Env:BUILD_DATE)"
Write-Host "BUILD_REV: $(Env:BUILD_REV)"
Write-Host "BUILD_DATE: $(Build.Date)"
Write-Host "BUILD_REV: $(Build.Rev)"
But the first segment gives The term 'Env:BUILD_DATE' is not recognized and the second segment gives The term 'Build.Date' is not recognized
How can I use the build variables in my script?

Disclaimer: I know virtually nothing about Azure pipelines, so my answer is based on reading the docs. Do let us know if I got things wrong.
Your first command uses the correct syntax for referencing environment variables in PowerShell (also inside an expandable (double-quoted) string).
(The other commands, based on subexpression operator $(...), mistakenly try to execute commands named Env:BUILD_DAT, ... rather than referencing variables.)
Your problem seems to be that the targeted environment variables do not exist.
The list of predefined variables that are exposed as environment variables does not contain variables named Build.Date / $env:BUILD_DATE and Build.Rev / $env:BUILD_REV.
By contrast, variables named Date and Rev seemingly do exist - as you state, they are used in the default format definition for the Build.BuildNumber / $Env:BUILD_BUILDNUMBER build variable, $(Date:yyyyMMdd)$(Rev:.r) - but are seemingly of a different kind not exposed as env. vars. (unlike Build.BuildNumber / $Env:BUILD_BUILDNUMBER itself, which is exposed).
(I don't know where these variables are defined or how they are classified, and where this is documented - do tell us if you know.)
A quick workaround would be to split the value of $Env:BUILD_BUILDNUMBER into its constituent parts:
# Split the build number into date and revision, by "."
$date, $rev = $Env:BUILD_BUILDNUMBER -split '\.'
"BUILD_DATE: $date"
"BUILD_REV: $rev"

Related

Windows $env:path ="$($env:path);." where was it added?

I "fixed" a problem by running $env:path ="$($env:path);." from PowerShell. Apparently it added the current directory to my path. Which path variable did it add to please? In my environment variables dialogue, where would I see it added? User variables? System variables?
I'm confused because I had already added the folder to system variables path, but couldn't run the contained script until running ``$env:path ="$($env:path);."
Updates to $env:EnvVarName affect the current process only - no persistent changes via the registry are made:
$env:EnvVarName = 'foo'
is the equivalent of calling .NET method System.Environment.SetEnvironmentVariable as follows:
[Environment]::SetEnvironmentVariable('EnvVarName', 'foo', 'Process')
That is, the scope of the update is the current process.
Only if you substitute 'User' or 'Machine' for 'Process' in the above call (supported on Windows only[1]) do you persistently update environment variables in the registry (for the current user or the local machine (all users), respectively), for future sessions (processes)[2].
As of PowerShell [Core] 7.2, there is no PowerShell-native way to persistently update environment variables, but introducing one is being discussed on GitHub.
In other words: if you want to update not only the registry-based definition but also the value in the current process, you need to make both calls; e.g., for the current user:
# Windows only: Update / create a persistent definition for the current user,
# stored in the registry.
[Environment]::SetEnvironmentVariable('EnvVarName', 'foo', 'User')
# Update value for current process too.
$env:EnvVarName = 'foo'
Or, more in the spirit of DRY:
'User', 'Process' | foreach {
[Environment]::SetEnvironmentVariable('EnvVarName', 'foo', $_)
}
If the new value is to be based on the existing one from a given registry scope, retrieve the scope-specific value via System.Environment.GetEnvironmentVariable; e.g.:
# Get the registry-based *user* value
[Environment]::GetEnvironmentVariable('Path', 'User')
Caveat: Non-support for Windows environment variables based on REG_EXPAND_SZ registry values:
On Windows, persistently defined environment variables may be defined based on other environment variables, namely if the underlying registry value defining the variable is of type REG_EXPAND_SZ.
As of .NET 6, the System.Environment type's methods do not (directly) support such environment variables:
On getting such a variable's value, its expanded form is invariably returned; that is, references to other environment variables such as %SystemRoot% are replaced by their values.
On setting environment variables, REG_SZ registry values are invariably created, i.e. static, verbatim values - even when updating an existing REG_EXPAND_SZ value.
While quietly converting REG_EXPAND_SZ environment variables to static REG_SZ ones may often have no ill effects (as long as the new value only contains literal values), it certainly can: for instance, say a variable is defined in terms of %JAVADIR%; if that variable is converted to a static value based on the then-current value of %JAVADIR%, it will stop working if the value of %JAVADIR% is later changed.
Unfortunately, retrieval of raw REG_EXPAND_SZ environment variables and proper updating of their values currently requires direct registry access, which is quite cumbersome (not even the Windows API seems to have support for it) - see this answer.
Important considerations for the Path environment variable ($env:PATH) on Windows:
The Path environment variable is special in that it is a composite value: when a process starts, the in-process value is the concatenation of the Machine (local machine, for all users) value and the User (current user) value.
Note that since the machine-level value comes first, its entries take precedence over the user-level value's entries.
Therefore, if you want to modify (append to) the existing Path, it's better not to define the new value simply by appending to the existing in-process value ($env:Path), because you'll be duplicating the Machine or User values, depending on which scope you target.
Instead, retrieve the existing value from the target scope selectively, modify that value (typically by appending a directory, and then write the modified value back to the same scope.
To robustly make the same modification effective in the current process too is nontrivial, given that the in-process copy of $env:Path may have been modified; however, in the simple case of appending a new directory to the user's path, you can simply do $env:Path += ';' + $newDir; you may get away with this simple approach in other cases too, but note that the behavior may be different, given that the order in which the directories are listed in $env:Path matters.
Important: The Path environment variable on Windows is REG_EXPAND_SZ-based by default, so the caveats re the quiet conversion to a static REG_SZ-based value that the code below below performs apply - again, see this answer for a proper, but much more complex solution.
Example:
# New dir. to add to the *user's* path
$newDir = 'c:\foo\bin'
# Get current value *from the registry*
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
# Append the new dir and save back to the registry.
[Environment]::SetEnvironmentVariable('Path', ($userPath + ';' + $newDir), 'User')
# To also update the current process, append $newDir to the in-process
# variable, $env:Path
$env:Path += ';' + $newDir
As an aside: On Unix-like platforms, the separator is :, not ; (reflected in [System.IO.Path]::PathSeparator , and the case-sensitive variable name is Path. As stated, .NET fundamentally doesn't offer persistent environment-variable definitions on Unix-like platforms (as of .NET Core 3.1), because the various platforms have no unified native mechanism for doing so.
[1] On Unix-like platforms, targeting User or Machine is quietly ignored as of .NET Core 3.1
[2] Caveat: New processes created directly by the current PowerShell session (direct invocation, Start-Process, Start-Job) do not yet see the registry changes, because the inherit the current session's environment.
If you take apart that command, you are assigning a new string to the $env:Path variable. The string is:
"$($env.Path);."
When you place a $ followed by a set of parens () within a double-quoted string, it causes PowerShell to evaluate the contents of the parens and put the output of that evaluation into the string. See the help for about_Quoting_Rules in PowerShell help, or here in the section about evaluating expressions. Therefore:
PS C:> $A = "abc"
PS C:> $B = "AB$($A)CD"
PS C:> $B
ABabcCD
So the command you posted is appending ";." to the end of your path. When doing this, the "." will automatically be expanded out to the current directory, so effectively you will be adding a semi-colon plus the current directory to your $env:PATH variable each time you run it.
Hope this helps.
It added to $env:PATH at the process scope, meaning it's not set as a user variable or machine variable and the new value does not exist outside of your current PowerShell session. You will not see an environment variable set in this way from the Environment Variables dialog under System Properties.
If you do want to set a persistent environment variable at the User or Machine scope from PowerShell, you can, but you have to call a special method for this from the Environment class, SetEnvironmentVariable:
# Set system (machine) level environment variable
[Environment]::SetEnvironmentVariable( 'VARIABLE_NAME', 'VARIABLE_VALUE', [EnvironmentVariableTarget]::Machine )
# Set user level environment variable
[Environment]::SetEnvironmentVariable( 'VARIABLE_NAME', 'VARIABLE_VALUE', [EnvironmentVariableTarget]::User )
You can also use this method to set a Process level environment variable, but you can already do this with the $env:VARIABLE_NAME = 'VARIABLE_VALUE' syntax which is idiomatic to PowerShell.
I'm confused because I had already added the folder to system variables path
What probably happened here is that you opened a PowerShell session, then went to the Environment Variables dialog, and set the variable value. Problem is, normally environment variables, including PATH, are only read when the process starts. Most of the time, you just need to restart your PowerShell session to get the new values.
If you have Chocolatey installed, you can use the refreshenv command, which reads the current stored environment variables from the registry, and re-sets the variables in the current process. If you want to implement this sort of thing yourself, here is the source for it. Though it's written as a cmd script, you can reimplement the logic in PowerShell yourself, or just copy the script source yourself to use.
Also, don't add . to your path. While convenient, it circumvents built in PowerShell security measures. Add the directory with your programs/scripts you want to run by command to your PATH directly, or invoke them from the current directory by prepending the command with .\. For example:
.\My-ScriptInThisDirectory.ps1

Azure DevOps Release Pipelines - Using env parms with a period . in

I am finding using AZDO Release pipeline variables maddening in Powershell steps.
I am running an Azure PowerShell step to return a primary key value. It is 2 lines…
$primarykey = (Get-AzRelayKey -ResourceGroupName ${env:az-resourcegroupname} -Namespace ${env:az-relaynamespace} -HybridConnection ${env:serviceBus.primaryRelay.ConnectionName} -Name ${env:serviceBus.primaryRelay.KeyName} | Select-Object -ExpandProperty PrimaryKey)
Write-Host "##vso[task.setvariable variable=serviceBus.primaryRelay.Key]$primarykey"
In my pipeline I have a mix of variable names, some I have complete control over (the az- prefixed ones) and others I don’t (the ones starting serviceBus.)
The reason I have no control over the latter is that they are used for a later File Transform step that navigates an appsettings.json file to find/replace values, and its unable to be changed (for example serviceBus.primaryRelay.ConnectionName is a value that is changed in the JSON and the file transform step specifies to navigate the JSON structure, it has to be separated with a period . )
When this script runs it always complains about the -HybridConnection value being empty. This is because the variable has a period in it.
I’ve tried everything I can think of to retrieve that value in the code.
Are they suggesting here that a variable with a period isn’t workable in Powershell in AZDO release pipelines? I’m completely lost.
I have found the answer by looking under the Release Pipelines "Initialize Job" log. It appears to substitute the period . with a dash -
The log revealed this...
[SERVICEBUS_PRIMARYRELAY_CONNECTIONNAME] --> [dev-sbrelay]

Updating a variable in a linked variable group with $(Build.BuildId) from a Linux pipeline

I'd like to update a variable in a linked variable group with $(Build.BuildId) from a Linux pipeline. I see a few examples using the #echo ##vso[task.setvariable command, but can't get it to work because I don't think I'm referencing the source or destination right.
The linked variable group is NightlyBuildID and the variable is LinuxBuildID.
Here's one of my many attempts:
#echo ##vso[task.setvariable variable=LinuxBuildID]$(Build.BuildId)
The $(variable) syntax is only valid within the build editor interface. Within a script, you have to reference it as an environment variable. Periods are replaced with underscores.
Thus, in Linux, $(Build.BuildId) would be accessed as $BUILD_BUILDID.
Apparently, this can't be done, according to the link below. I'll have to come up with a work around, bummer.
https://visualstudio.uservoice.com/forums/330519-visual-studio-team-services/suggestions/32083207-allow-variables-in-variable-groups-to-be-settable

how to add variables together in VSTS

I want to use a variable which is composed of another vsts variable and text, for instance:
vnetname = $vnet_prefix + "vnetid"
However i get an error saying that "A positional parameter cannot be found that accepts argument +.
Anyone advise?
If you mean use the variable in build/release processes, then you can add a variable like this (reference below screenshot):
vnetname = $(vnet_prefix)_vnetid
Then you can use the variable $vnetname or $(vnetname) directly, see Build variables-Format for how to use the variables in different tools.
Alternatively you can pass the value with Logging Commands:
Copy and paste below strings then save as *.ps1 file:
$value = $env:vnet_prefix + "vnetid"
Write-Host "##vso[task.setvariable variable=vnetname]$value"
Check in the PS file
Add a PowerShell task to run the PS file
Use the variable $vnetname in later steps

PowerShell Syntax Error

I am working on a powershell script that will create TFS build definitions. I have used below example as my starting point.
http://geekswithblogs.net/jakob/archive/2010/04/26/creating-a-build-definition-using-the-tfs-2010-api.aspx
I have the script done in powershell and it creates me a build definition file in TFS. One thing I am stuck in is creating Process information such as "Item to build" and "Projects to build". The C# code for this is given below
//Set process parameters
varprocess = WorkflowHelpers.DeserializeProcessParameters(buildDefinition.ProcessParameters);
//Set BuildSettings properties
BuildSettings settings = newBuildSettings();
settings.ProjectsToBuild = newStringList("$/pathToProject/project.sln");
settings.PlatformConfigurations = newPlatformConfigurationList();
settings.PlatformConfigurations.Add(newPlatformConfiguration("Any CPU", "Debug"));
process.Add("BuildSettings", settings);
buildDefinition.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(process);
Below is the powershell code I have written to achive above.
Write-Host"Set process parameters "$now
$process=[Microsoft.TeamFoundation.Build.Workflow.WorkflowHelpers]::DeserializeProcessParameters($def.ProcessParameters)
Write-Host"Set build settings properties "$now
$settings=new-object-`enter code here`TypeNameMicrosoft.TeamFoundation.Build.Workflow.Activities.BuildSettings
$sList=New-Object-TypeNameMicrosoft.TeamFoundation.Build.Workflow.Activities.StringList
$sList="$/pathToProject/project.sln"
$settings.ProjectsToBuild =$sList
$process.Add("BuildSettings", $sList)
But the above segment of code does not create me the Build settings in my build definition file. Myquestion is am I doing this the correct way in powershell? I feel I am not writing the powershell code incorrectly as I am newbie to powershell. Any guidance and
help would be appreciated
Calling a constructor with parameters should be done like this in PowerShell:
$ns = 'Microsoft.TeamFoundation.Build.Workflow.Activities'
$settings.ProjectsToBuild = new-object "$ns.StringList" '$/pathToProject/project.sln'
Also note the use of single quotes around the TF server path. $ is s special character in PowerShell - tells it what follows is either a variable name or sub-expression even in a string. Unless that string is single quoted. In which case, PowerShell doesn't interpret any special characters within the string.