Teamcity not printing built-in parameters using Powershell - powershell

I'm trying to print TeamCity parameters using Powershell. The Teamcity parameter is formed at run-time, by assigning it values elsewhere. The problem is that Teamcity only prints the string thus formed and not the value that is stored within the parameter. Please see a sample code where I need to print the build id, here the words "teamcity" and "build.id" are formed during run time. However, upon running Teamcity prints the string %teamcity.build.id%, and not the actual build id.
NOTE:
The type of TeamCity build in parameters that I need to print are agent parameters and not system parameters or environment parameters
$per = "%"
$t = "teamcity"
$b = ".build.id"
$indirect = $per+$t+$b+$per
Write-Output $indirect

PowerShell can't interpret the TeamCity variable at runtime.
The variable is injected into the script prior to it being run, thus the only way to do this would be to reference the variable as a whole.
e.g.
$buildId = "%teamcity.build.id%"
Write-Output $buildId

Related

Error when running a dot-sourced script after executing it

I've got very weird behaviour which I suppose is related to dot-sourcing somehow, but I cannot wrap my head around it. Here's what I have:
A script sourced.ps1 which contains the two functions and a class:
class MyData {
[string] $Name
}
function withClass() {
$initialData = #{
Name1 = "1";
Name2 = "2";
}
$list = New-Object Collections.Generic.List[MyData]
foreach ($item in $initialData.Keys) {
$d = [MyData]::new()
$d.Name = $item
$list.Add($d)
}
}
function withString() {
$initialData = #{
Name1 = "1";
Name2 = "2";
}
$list = New-Object Collections.Generic.List[string]
foreach ($item in $initialData.Keys) {
$list.Add($item)
}
}
I also have a script caller.ps1 which dot-sources the one above and calls the function:
$ErrorActionPreference = 'Stop'
. ".\sourced.ps1"
withClass
I then call the caller.ps1 by executing .\caller.ps1 in the shell (Win terminal with PS Core).
Here's the behaviour I cannot explain: if I call .\caller.ps1, then .\sourced.ps1 and then caller.ps1 again, I get the error:
Line |
14 | $list.Add($d)
| ~~~~~~~~~~~~~
| Cannot find an overload for "Add" and the argument count: "1".
However, if I change the caller.ps1 to call withString function instead, everything works fine no matter how many times I call caller.ps1 and sourced.ps1.
Furthermore, if I first call caller.ps1 with withString, then change it to withClass, there is no error whatsoever.
I suppose using modules would be more correct, but I'm interested in the reason for such weird behaviour in the first place.
Written as of PowerShell 7.2.1
A given script file that is both dot-sourced and directly executed (in either order, irrespective of how often) creates successive versions of the class definitions in it - these are distinct .NET types, even though their structure is identical. Arguably, there's no good reason to do this, and the behavior may be a bug.
These versions, which have the same full name (PowerShell class definitions created in the top-level scope of scripts have only a name, no namespace) but are housed in different dynamic (in-memory) assemblies that differ by the last component of their version number, shadow each other, and which one is effect depends on the context:
Other scripts that dot-source such a script consistently see the new version.
Inside the script itself, irrespective of whether it is itself executed directly or dot-sourced:
In PowerShell code, the original version stays in effect.
Inside binary cmdlets, notably New-Object, the new version takes effect.
If you mix these two ways to access the class inside the script, type mismatches can occur, which is what happened in your case - see sample code below.
While you can technically avoid such errors by consistently using ::new() or New-Object to reference the class, it is better to avoid performing both direct execution and dot-sourcing of script files that contain class definitions to begin with.
Sample code:
Save the code to a script file, say, demo.ps1
Execute it twice.
First, by direct execution: .\demo.ps1
Then, via dot-sourcing: . .\demo.ps1
The type-mismatch error that you saw will occur during that second execution.
Note: The error message, Cannot find an overload for "Add" and the argument count: "1", is a bit obscure; what it is trying to express that is that the .Add() method cannot be called with the argument of the given type, because it expects an instance of the new version of [MyData], whereas ::new() created an instance of the original version.
# demo.ps1
# Define a class
class MyData { }
# Use New-Object to instantiate a generic list based on that class.
# This passes the type name as a *string*, and instantiation of the
# type happens *inside the cmdlet*.
# On the second execution, this will use the *new* [MyData] version.
Write-Verbose -Verbose 'Constructing list via New-Object'
$list = New-Object System.Collections.Generic.List[MyData]
# Use ::new() to create an instance of [MyData]
# Even on the second execution this will use the *original* [MyData] version
$myDataInstance = [MyData]::new()
# Try to add the instance to the list.
# On the second execution this will *fail*, because the [MyData] used
# by the list and the one that $myDataInstance is an instance of differ.
$list.Add($myDataInstance)
Note that if you used $myDataInstance = New-Object MyData, the type mismatch would go away.
Similarly, it would also go away if you stuck with ::new() and also used it to instantiate the list: $list = [Collections.Generic.List[MyData]]::new()

Dynamically set Azure DevOps variable in scripts

I have n number of variables that I need to assign as Azure DevOps variables in a Release pipeline, and it doesn't seem like I'm getting the syntax right.
The variables may have different values (variable names) such that they could be:
- {guid 1}
- {guid 2}
...
So I won't know them prior to runtime. The problem is that it seems all of the examples of vso[task.setvariable] use static variable names, but I need to set it dynamically.
Here's what should work but doesn't:
Write-Host "##vso[task.setvariable variable=$($myVariable)]$($myValue)"
I've also tried just using [Environment]::SetEnvironmentVariable (with user) and it doesn't seem to persist across two different tasks in the same job.
[Environment]::SetEnvironmentVariable($myVariable, $myValue, 'User')
(Is null in subsequent task)
Is there some way that I can dynamically create release variables that persist between tasks? I've tried to search and found one question on the developer community but no answer to it.
It actually looks like the issue isn't that the variable isn't set, but that after using task.setvariable, the variable will only be available in subsequent tasks (and not the current one).
So I would say this is the best way to set variables in Azure DevOps:
When needing to use variables in the same task/script step, use:
[Environment]::SetEnvironmentVariable(...)
Or just use a variable in PowerShell.
When needing to use variables with multiple steps, use:
$myVariable = "some name"
$myValue = "some value"
# Note that passing in $($variableName) should work with this call
Write-Host "##vso[task.setvariable variable=$($myVariable)]$($myValue)"
# Note that trying to use a Write-Host for $env:myVariable will return empty except in tasks after this one
Write-Host "Setting $($myVariable) to $($myValue)
It works. This is example from my build task:
$myVariableNewValue = '##vso[task.setvariable variable=myVariable]' + $newValue
Write-Host $myVariableNewValue

How to set a cloudformation parameter to a powershell variable

Trying to simply run some powershell in my cloudformation based on a user inpute parameter in cloudformation.
This works
write-host ${CFParameter} >> C:\temp\log.txt
but this does not
$PSVariable = ${CFParameter}
write-host $PSVariable >> C:\temp\log.txt
the second one just returns a blank line but the first one returns the correct information
If your powershell is being used in userdata and you can use ref function to refer to the parameter. I would recommend using cloudkast which is an online cloudformation template generator. It makes it easy to generate cloudformation templates.

Why is my powershell Base64 String truncated to 36 characters?

I'm using PowerShell inside VSTS Build for querying the VSTS API. I'm using PAT for authentication.
However, I see it failing when I generate the Auth string. Here is my code inside
$VstsAccessEmail = $Env:VstsAccessEmail
$VstsAccessToken = $Env:VstsAccessToken
$pair = "${VstsAccessEmail}:${VstsAccessToken}"
$base64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($pair))
Write-Host $base64
If I see the outcome here, I see the first 36 characters of the actual auth string. I generated the string using Powershell on my machine and I get the entire 108 characters. I have hard-coded this for time being to test this like the following steps that follow the above code.
$base641 = "a2FuZ2thbi5nb3N3YW1pQHVuaXN5cy5jb206cHh4b25oaHBlNmtjb3g3aTRhdHZxMzdoNms2ZnpuNHhyaWhyZ2ozdGZ3ejRlNmxxxxXXXX=="
if($base64.length -ne 108){
$base64 = $base641
}
Write-Host "base64 is: $base64 "
This works correctly. Initially, I thought it might be an issue with Writing to host. However, If I do invoke the RestMethod without updating with the hard-coded one, I get 401 Unauthorized.
Please help.
UPDATE:
I found the issue. I set the VstsAccessToken as a secret in the build variables. So the value is not coming through. making it unsecured works fine.
Can someone help how this can be done keeping the token as a secret?
SOLVED
Using $Env:variable does not allow to use the value when the variable is secret. However, passing it as a parameter to the PowerShell let the code read it, though the output to log is masked.
This section is definitely wrong. I see one thing that's definitely wrong, another that's possibly wrong.
$VstsAccessEmail = $Env:VstsAccessEmail
$VstsAccessToken = $Env:VstsAccessToken
$pair = "${VstsAccessEmail}:${VstsAccessToken}"
$base64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($pair))
$pair = "${VstsAccessEmail}:${VstsAccessToken}" should be $pair = ":${VstsAccessToken}". No email address is neccessary, just a colon and then the auth token.
UTF8.GetBytes may be wrong. I always use ASCII.GetBytes.
Using $Env:variable does not allow to use the value when the variable is secret. However, passing it as a parameter to the PowerShell let the code read it, though the output to log is masked.

Powershell script queues results of an if statement in a do while in a function

I'm using a function that I call from another script. It prompts a user for input until it gets back something that is not empty or null.
function GetUserInputValue($InputValue)
{
do{
$UserValue = Read-Host -Prompt $InputValue
if (!$UserValue) { $InputValue + ' cannot be empty' }
}while(!$UserValue)
$UserValue
return $UserValue
}
The issue is quite strange and likely a result of my lack of powershell experience. When I run the code and provide empty results, the messages from the if statement queue up and only display when I finally provide a valid input. See my console output below.
Console Results
test:
test:
test:
test:
test:
test:
test: 1
test cannot be empty
test cannot be empty
test cannot be empty
test cannot be empty
test cannot be empty
test cannot be empty
1
I can make this work however in the main file with hard coded values.
do{
$Server = Read-Host -Prompt 'Server'
if (!$Server) { 'Server cannot be empty' }
}while(!$Server)
I'm working Visual Studio Code. This is a function I have in another file I've named functions.ps1.
I call this from my main file like this,
$test = GetUserInputValue("test")
$test
When you put a naked value in a script like "here's a message" or 5 or even a variable by itself $PID what you're implicitly doing is calling Write-Output against that value.
That returns the object to the pipeline, and it gets added to the objects that that returns. So in a function, it's the return value of the function, in a ForEach-Object block it's the return value of the block, etc. This bubbles all the back up the stack / pipeline.
When it has nowhere higher to go, the host handles it.
The console host (powershell.exe) or ISE host (powershell_ise.exe) handle this by displaying the object on the console; this just happens to be the way they handle it. Another host (a custom C# application for example can host the powershell runtime) might handle it differently.
So what's happening here is that you are returning the message that you want to display, as part of the return value of your function, which is not what you want.
Instead, you should use Write-Host, as this writes directly to the host, skipping the pipeline. This is the correct command to use when you want to display a message to the user that must be shown (for other information you can use different commands like Write-Verbose, Write-Warning, Write-Error, etc.).
Doing this will give you the correct result, and prevent your informational message from being part of the return value of your function.
Speaking of which, you are returning the value twice. You don't need to do:
$UserValue
return $UserValue
The first one returns the value anyway (see the top of this answer); the second one does the same thing except that it returns immediately. Since it's at the end of the function anyway, you can use wither one, but only use one.
One more note: do not call PowerShell functions with parentheses:
$test = GetUserInputValue("test")
This works only because the function has a single parameter. If it had multiple params and you attempted to call it like a method (with parentheses and commas) it would not work correctly. You should separate arguments with spaces, and you should usually call parameters by name:
$test = GetUserInputValue "test"
# better:
$test = GetUserInputValue -InputValue "test"