How to set a PowerShell switch parameter from TeamCity build configuration - powershell

I have a PowerShell script with a switch (boolean) parameter that I want to call from a TeamCity build step.
I want the value of the switch (true/false) to be set according to a TeamCity build parameter, a configuration parameter.
So, something like this:
And in the PowerShell runner build step:
But the above does not work.
I get this error
[14:27:01][Step 1/1] Cannot process argument transformation on parameter 'preRelease'. Cannot
[14:27:01][Step 1/1] convert value "System.String" to type
[14:27:01][Step 1/1] "System.Management.Automation.SwitchParameter". Boolean parameters accept only
[14:27:01][Step 1/1] Boolean values and numbers, such as $True, $False, 1 or 0.
As you can see, it seems that PowerShell insist on interpreting the parameter as a string.
I have tried many variants of writing the script argument. None of these work:
-preRelease:%IncludePreRelease%
-preRelease:([boolean]%IncludePreRelease%)
-preRelease:([System.Convert]::ToBoolean(%IncludePreRelease%))

No, this still won't work. It seems that TC will always treat the value as a string no matter what. Maybe the answer was from a version of TC that allowed this, but the latest release does not.
Without colon:
[16:18:37]Step 1/6: Migrate Up (Powershell)
[16:18:37][Step 1/6] PowerShell Executable: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
[16:18:37][Step 1/6] PowerShell arguments: [-NonInteractive, -ExecutionPolicy, ByPass, -File, C:\BuildAgent\work\f2797fec10821a01\data\Migration\MigrateUp.ps1, "data\change scripts", DB, xxxxxxx, sa, *******, -NoExec, $false]
[16:18:37][Step 1/6] C:\BuildAgent\work\f2797fec10821a01\data\Migration\MigrateUp.ps1 : A positional parameter cannot be found that accepts argument '$false'.
With colon:
[16:18:37]Step 2/6: Migrate Up (Powershell)
[16:18:37][Step 2/6] PowerShell Executable: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
[16:18:37][Step 2/6] PowerShell arguments: [-NonInteractive, -ExecutionPolicy, ByPass, -File, C:\BuildAgent\work\f2797fec10821a01\data\Migration\MigrateUp.ps1, "data\change scripts", DB, xxxxxx, sa, *******, -NoExec:$false]
[16:18:37][Step 2/6] C:\BuildAgent\work\f2797fec10821a01\data\Migration\MigrateUp.ps1 : Cannot process argument transformation on parameter 'NoExec'. Cannot convert value "System.String" to type "System.Management.Automation.SwitchParameter". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.
Removing the [switch] also does not work as any value you give it will not be treated as a Boolean - but rather a string assigned to a Boolean - and, thus, all Boolean operations will be $true.
In other words, anything you send will look like:
0 => '0'
1 => '1'
$false => '$false'
$true => '$true'
(none of which are == $false but all of which are equivalent to $true)
I have no idea how to get TC to accept Booleans!
Best I could do was assume the argument was a string and use System.Convert.ToBoolean() internally in my script...

This is an old question, but if you are still looking for an answer, I managed to get it working.
The main trick is that you shouldn't actually use the colon between the parameter name and the value as you would with PowerShell (I know, confusing...).
It looks like TeamCity is invoking the script in a different way and passes the parameters as string.
So with your example, the following should work:
-preRelease $%IncludePreRelease%
Note that I have added a dollar sign in front of the TeamCity variable to change "true" into "$true"
Let me know if it works for you
Thanks!

According to the Microsoft .Net documentation for Convert.ToBoolean the value passed to the method must be:
For a successful conversion to occur, the value parameter must equal
either Boolean.TrueString, a constant whose value is True,
Boolean.FalseString, a constant whose value is False, or it must be
null. In comparing value with Boolean.TrueString and
Boolean.FalseString, the method ignores case as well as leading and
trailing white space.
If you change your TeamCity IncludePreRelease variable value to either "True" or "False" (without the quotes) then it should convert correctly.

Make passing the switch at all dependent on the configuration parameter:
When you create the Configuration Parameter, choose Type "Checkbox".
In "Checked value", put "-preRelease".
Leave "Unchecked value" blank.
Simply append "%IncludePreRelease%" to your script arguments. When the parameter is checked, it'll pass "-preRelease" (=true), and when it's not, it will be omitted (=false).
This worked for me using TeamCity 10.0.4.

I know this is a late answer but I just had a colleague at work stumble upon the same problem and found this question.
If we forget about TC for a minute, if you wanted to execute the same command from a cmd (not powershell console) the exact command would be:
powershell.exe -File script.ps1 -preRelease
This command sets the switch to True. What is important here is that the command is first parsed by the shell (cmd).
Because the shell is cmd, powershell code in the command line will not be executed.
For example:
powershell -File script.ps1 -preRelase:"([System.Convert]::ToBoolean(%IncludePreRelease%))"
after first pass of the cmd parsing it ends up being:
powershell -File script.ps1 -preRelase:"([System.Convert]::ToBoolean(True))"
so the preRelease flag is being set to the whole string which gives you exactly
Cannot process argument transformation on parameter 'preRelease'.
Cannot convert value "System.String" to type
"System.Management.Automation.SwitchParameter"
Based on the documentation, when using -File you either pass the switch parameters or you don't. There doesn't seem to be a way to pass the True or False value.
To make it work change from -File to -Command like so:
powershell -Command .\script.ps1 -preRelease:$%IncludePreRelease%
Now, back to TeamCity. Teamcity doesn't seem to support -Comand like above. They support it by dumping your whole script into the command line as a script block which can lead to very interesting errors.
The solution is to change the Script to Source and inside the script content add
.\script.ps1 -preRelease:$%env.IncludePreRelease%

Related

Why does a variable in PowerShell expand to `-encodedCommand` instead of its actual value?

I want the $mydate variable or the command Get-Date -Format yyyy-MM-dd to expand in the following command line (note the curly braces required by the svn.exe client:
$mydate = Get-Date -Format yyyy-MM-dd
svn log https://svn.apache.org/repos/asf/ -r {Get-Date -Format yyyy-MM-dd}
svn log https://svn.apache.org/repos/asf/ -r {$mydate}
In both cases I am getting the following error:
svn: E205000: Syntax error in revision argument '-encodedCommand'
Why does the variable becomes -encodedCommand? Should I escape curly braces? How? Tick '`' does not work:
Error formatting a string: Input string was not in a correct format..
At line:1 char:1
+ svn log https://svn.apache.org/repos/asf/ -r `{$mydate`}
What am I doing wrong?
I don't have enough points to comment, but try:
svn log https://svn.apache.org/repos/asf/ -r "{$($mydate)}"
The outer " quotes convert it all into a string, so ignores the curly braces. The $() allows the variable to be correctly interpreted (not as a string).
To complement yaquaholic's helpful answer with background information:
Unquoted use of {...} has special meaning in PowerShell: it creates a script block (type [scriptblock]), which is a reusable piece of PowerShell code that can be passed as an argument or stored in a variable for later execution on demand.
Therefore, to pass arguments with embedded { or } characters, quote them (with '...' (literal string, e.g., '{foo}') or "..." (expandable string, e.g. "{$foo}"), as needed).
The behavior that PowerShell exhibits as of Windows PowerShell v5.1 / PowerShell Core 7.0 with unquoted {...} is a known problem:
A script block has no meaning outside of PowerShell, such as when passing arguments to an external program like svn.
By contrast, calling PowerShell's own CLI - powershell.exe (Windows PowerShell), pwsh (PowerShell Core) - with a script block is supported, via behind-the-scenes Base64-encoding of the script block's content, with the encoded string passed via -encodedCommand, and CLIXML serialization applied to arguments and pipeline input - see this comment on GitHub
This mechanism is currently - pointlessly - applied to other external programs too, which is why you saw an -encodedCommand argument appear.

Powershell (?) transforms argument in a very weird way - removes comma from string

I have a powershell build step in TeamCity:
param ([string] $a)
Write-Host "`$a is '$a'."
and in this step I set parameter $a as -a "%TestParam%" or as "-a %TestParam%", where TestParam has two lines abra and cadabra.
When I run the build I get the following output:
[Step 1/10] PowerShell arguments: -NoProfile, NonInteractive, -ExecutionPolicy, ByPass, -File, C:\buildAgent\temp\buildTmp\powershell1746295357460795314.ps1, -a, "abra, cadabra"
[Step 1/10] $a is 'abra cadabra'.
The only question I have: What on earth happened to the comma? Why has it disappeared?
If I do not use quotes at all (-a %TestParam%), then TeamCity passes each line as a separate parameter and I see $a is 'abra'..
Mathias R. Jessen's answer explains PowerShell's parsing of ,-separated tokens as arguments [his answer has since been deleted, but I hope it will be undeleted], but that doesn't apply in the case at hand, because any arguments passed to PowerShell's CLI via -File are not subject to PowerShell's command-line parsing - instead, such arguments are treated as literals.
That is, if the command line invoked by TeamCity truly were the following:
powershell ... -File C:\...ps1 -a "abra, cadabra"
then parameter variable $a would receive value abra, cadabra, as expected.
In other words: What is actually being passed in your case must be abra cadabra, not
abra, cadabra, so you need to revise the value of %TestParam% to ensure that it actually contains the desired comma.
As for why the log of the command invoked suggests that there is a , present in what you're passing:
I can only speculate, based on your own guess:
I suspect TeamCity of being a liar, showing lines joined with comma, but passing them without it.
Perhaps TeamCity, when logging invocation of a command line, naively breaks that command line into tokens by whitespace only, without considering quoting, and then presents them as a ,-separated list.
If this is indeed the case, then argument "abra cadabra" - without comma - would be logged as
"abra, cadabra", which would explain the confusion.

PowerShell arguments beginning with / are replaced with "True"

As the title says, any argument passed to a PowerShell script that starts with a '/' is replaced with True.
For a script
param (
[string]$wd = ""
)
echo $wd
And run with .\script.ps1 -wd "/etc/xyz", the output is True instead of the expected /etc/xyz. I was unable to find this behavior documented elsewhere, except for a similar issue in vbscript.
How can I get the string as-is?

What is correct syntax with depend parameter flag in Windows Powershell?

I am using Windows PoweShell to submit jobs to a high performance cluster, and I'm having a problem with the depend parameter flag. I cannot post the actual code as it would probably get me in trouble at work so here's the gist:
job add $jobid /scheduler:xxxxx /name:task1 /workdir:M:\dir foo.exe "foo" -logfile task1.log
job add $jobid /scheduler:xxxxx /name:task2 /workdir:M:\dir foo.exe "foo" -logfile task2.log
job add $jobid /scheduler:xxxxx /name:task3 \depend:task1,task2 /workdir:M:\dir foo.exe "foo" -logfile task1.log
The problem occurs when it hits task2 in \depend:task1,task2. If I skip task2 and remove it from the depend statement, then everything is fine. Maybe I need some sort of brackets or whatnot to indicate that I'm giving a list, rather than a single parameter, to the depend flag.
What's the proper syntax for this?
I'm going out on a limb here, but task1,task2 is most likely interpreted as a PowerShell array and /depend probably doesn't accept that as input (or just the first array element). Try putting the list in double quotes:
job add $jobid /scheduler:xxxxx /name:task3 /depend:"task1,task2" ...
or using the magic parameter (if you're using PowerShell v3 or newer):
job add $jobid --% /scheduler:xxxxx /name:task3 /depend:task1,task2 ...

TFSBuild to include an array in parameters to powershell

I have a TFS 2010 build which calls a powershell script for deployment. I've defined several arguments for the build script and these have worked great. They are used by the build and also included in the arguments that are passed into Powershell via the Arguments property of the InvokeProcess control.
I now have a requirement for the powershell script to deploy to a variable number of servers, so I'd like to pass the server ID's in on the argument list from TFS.
In the build definition, I have declared a new argument called TargetServers of type string[]. I have populated this from the Build Process Parameters dialog prior to executing a build.
I have set the FileName property of the InvokeProcess control to "Powershell", and the Arguments property as follows:
String.Format(" ""& '{0}' '{1}' '{2}' '{3}' '{4}' '{5}' '{6}' '{7}' '{8}' '{9}' "" ", DeploymentScriptFileName, IO.Path.GetDirectoryName(DeploymentScriptFileName), "ExecuteBizTalkAppMSI.ps1", MSIFileName, BTDFFilename, TargetServerPath, TargetServers, ServerDeploymentFolder, InstallFolder, HostInstanceFilter, ApplicationName)
My problem is that the TargetServers argument being passed to Powershell is simply System.String[].
From the build log I can see the following output of the Invoke Process control:
Powershell "& 'C:\Builds\3\x.Int.MIS.Deployment\CopyDeployScriptThenExecute.ps1'
'C:\Builds\3\\x.Int.MIS.Deployment' 'ExecuteBizTalkAppMSI.ps1'
'x.Int.MIS-3.0.0.msi' 'x.Int.MIS.Deployment.btdfproj'
'\\d-vasbiz01\BizTalkDeployment' 'System.String[]' 'c:\BizTalkDeployment'
'c:\Program Files (x86)\x.Int.MIS for BizTalk 2010\3.0' 'BTSSvc*MIS*' "
Can anyone please advise how to pass the array?
As strings delimited by commas:
PS> function foo([string[]]$x){$x}
PS> foo a,2,3
a
2
3
If you want, you can put quotes around each individual item but you don't need to unless they contain spaces or other characters reserved for the syntax.