I have a PowerShell script that I trigger from Bamboo, the script basically accepts 3 string parameters:
Param([string]$language, [string]$architecture ="x64", [string]$version)
$x64Path = "some path..."
$x86Path = "some path.."
$arguments = $language,$architecture,$version
If($architecture -eq "x64") {
Write-Host "Executing:" $x64Path" at x64 using" $language "and" $version
# & $x64Path $arguments
}
Else {
Write-Host "Executing:" $x86Path "at x86 using" $language "and" $version
# & $x86Path $arguments
}
if I run the PowerShell script from the ISE, for example:
.\MyScript.ps1 "JPN" "x64" "AB 6.0 R"
I will correctly get this output:
Executing: some path... at x64 using JPN and AB 6.0 R
I have created a plan in Bamboo with the following:
Variables
Task
I have tried adding the variables (argument) as:
"${bamboo.Language}" "${bamboo.Architecture}" ${bamboo.Version}
and aso as:
"${bamboo.Language}" "${bamboo.Architecture}" "${bamboo.Version}"
But I always get the same output:
Executing: some path... at x64 using JPN and AB
Notice that the last variable is cut out (I don't get the full AB 6.0 R)
Is this a bug in Bamboo, or something I'm doing wrong? or is there a workaround it?
PS. To achieve this run in Bamboo (by giving the variable values), I'm running the plan as follows:
I suspect the quotes get stripped after variable replacement in bamboo (ie. before the arguments are passed to powershell).
Add an inner set of single-quotes to the argument that has spaces:
"${bamboo.Language}" "${bamboo.Architecture}" "'${bamboo.Version}'"
Related
My code has the following function:
function status{
if ($args[0] -eq "Stopped"){
Write-Host -NoNewline "Stopped" -fore red
.....
}
}
and the function is used as:
...
Write-Host "$($count)) $($i.DisplayName) STATUS: $(status $i.State)"
...
The result after script execution is:
Stopped 1) XXXXXX STATUS:
Why "Stopped" is in the beginning of the line? can someone help me?
The function is only to change the text color. As the example -fore red is to Stopped value.
You could use ANSI Escape sequences for this but it wouldn't work in old terminals. I'm not convinced if this is possible combining outputs from Write-Host and, if it is, I'll assume is an extremely tedious task.
function status {
$ansi = switch($args[0]) {
Stopped { "$([char] 27)[91m" }
Running { "$([char] 27)[32m" }
}
$ansi + $args[0]
}
foreach($state in 'Running', 'Stopped') {
$count++
$i = [pscustomobject]#{ state = $state; displayname = 'Hello!' }
Write-Host "$($count)) $($i.DisplayName) STATUS: $(status $i.State)"
}
Which would produce the following output:
Santiago Squarzon's helpful answer provides an effective solution; let me complement it with an explanation of what you tried:
Why "Stopped" is in the beginning of the line?
The reason is that Write-Host doesn't write to PowerShell's success output stream and therefore doesn't "return" a value from your status function.
Instead, Write-Host instantly prints to the host (display; in PSv5+ via the information stream), before exiting the function, and your function produces no success-stream output.
Since subexpressions (via $(...)) are evaluated first in an expandable string (or command in general):
the Write-Host call inside your status function executes right away and prints at the start of the line.
only then does the outer Write-Host call execute, which - due to the inner call's -NoNewLine switch - prints on the same line, after what the function printed, and $(status $i.State), due to the status call producing no success output, evaluates to the empty string.
A simple example can illustrate the problem:
Write-Host "foo: >>$(Write-Host bar)<<"
Output:
bar # The inner Write-Host printed right away.
foo >><< # $(Write-Host bar) produced no success output
Santiago's solution avoids this problem by replacing the Write-Host call inside the function with sending a string (implicitly) to the success output stream, so that it becomes the function's "return value" that subexpression $(status $i.State) expands to, and to make this string colored, ANSI / VT escape sequences are needed.
Note that while "$([char] 27)" is required in Windows PowerShell to embed an ESC character in an expanable string, PowerShell (Core) 7+ now offers escape sequence "`e"
Also, PowerShell (Core) 7.2+ offers the automatic $PSStyle variable, which, while more verbose, offers a more descriptive way to embed ANSI / VT escape sequences; e.g.:
# Prints only the word "green" in green.
"It ain't easy being $($PSStyle.Foreground.Green)green$($PSStyle.Reset)."
Note:
If you send strings with ANSI / VT sequences to the success output stream, as in the example above, colors are not automatically reset; $PSStyle.Reset is needed for a manual reset.
By contrast, if Write-Host prints a string, it resets colors automatically.
A dll can be invoked in Powershell; it takes several parameters, and I must run it several times while most of them stay the same, like this:
dotnet C:\App\App.dll AppCmd --MySwitch --Param1 ab --Param2 cd --Param30 xx
dotnet C:\App\App.dll AppCmd --MySwitch --Param1 ab --Param2 cd --Param30 yy
dotnet C:\App\App.dll AppCmd --MySwitch --Param1 ab --Param2 cd --Param30 zz --Param31 x:y y:z
I'd like to 'splat' the common parameters and manually write out only what is unique to each invocation, like:
$CommonArgs = #{
Param1 = 'ab',
Param2 = 'cd'
...
}
dotnet C:\App\App.dll AppCmd --MySwitch --Param30 xx -- #CommonArgs
dotnet C:\App\App.dll AppCmd --MySwitch --Param30 yy -- #CommonArgs
dotnet C:\App\App.dll AppCmd --MySwitch --Param30 zz --Param31 x:y y:z -- #CommonArgs
The App stops complaining about missing required parameters with the above syntax but it returns an error: Unhandled Exception: System.FormatException: The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters. I'm not sure whether that error message comes from the Application or Powershell itself.
I figured out splatting within Powershell functions and I read the -- should pass the parameters along to the dotnet call but apparently I'm still missing something. What is it?
Using hashtable-based splatting with external programs rarely works, because of the very specific format that PowerShell translates the hashtable entries to: -<key>:<value> - this won't work in your case.
For external programs, array-based splatting is the better choice - and, in fact, you don't even need the splatting sigil (# instead of $) at all and you can just pass an array variable as-is:
$CommonArgs = #( # array, not hashtable
'--Param1', 'ab',
'--Param2', 'cd'
# ...
)
# ...
dotnet C:\App\App.dll AppCmd --MySwitch --Param30 zz --Param31 x:y $CommonArgs y:z
Note how each argument - irrespective of whether it represents a parameter name or a value - must be its own array element.
PowerShell simply space-concatenates the array elements when building the command line to use for invocation behind the scenes, applying double-quoting around elements with embedded spaces as needed.
I've omitted --, because it would be passed through to the external program - not sure if that's the intent (only for PowerShell-native commands is it removed by the parameter binder).
Troubleshooting tip:
The bottom section of this answer shows how to build a helper executable that can be used to echo the exact command line it was given, as well as how it parsed it into individual arguments (using .NET's parsing rules, which are based on Microsoft's C/C++ compiler).
Too long for a comment, and I have no idea if this would work, but perhaps you can use a small helper function to convert the common arguments from the splatting Hashtable into commandline syntax like
function Format-CliArguments {
param (
$prefix = '--'
)
$numTypes = 'byte','sbyte','int','int16','int32','int64','uint16','uint32',
'uint64','long','float','single','double','decimal'
$cliCommand = for ($i = 0; $i -lt $args.Count; $i += 2) {
if ($numTypes -contains $args[$i + 1].GetType().Name) {
'{0}{1} {2}' -f $prefix, $args[$i].Trim("-:"), $args[$i + 1]
}
else {
'{0}{1} "{2}"' -f $prefix, $args[$i].Trim("-:"), $args[$i + 1]
}
}
$cliCommand -join ' '
}
# you can make this [ordered]#{..} if the sequence is important
$CommonArgs = #{
Parameter1 = 'ab'
Parameter2 = 123
Parameter3 = 'zz'
}
$standardArgs = Format-CliArguments #CommonArgs
dotnet C:\App\App.dll AppCmd --MySwitch --Param30 xx $standardArgs
dotnet C:\App\App.dll AppCmd --MySwitch --Param30 yy $standardArgs
etc.
I want to substitute the variable into computer path..
Write-Host \\$store.storeIp\$Global:config.dest
Expected
\\127.0.0.1\D$\foo
Actual
\\#{storeNumber=1111; storeName=CAT; storeIp=127.0.0.1; status=FAILED}.storeIp\#{inputFile=./store-deployment-inp
ut.csv; src=.\foo; dest=D$\foo; destDir=D:; installerFileName=xxx.exe; po=; serviceName=xxx; forceDeploy=False; legacyScript=}.dest
Somehow, look like the powershell output all the object instead of output the select value when in conjuction with \$.
You could do this like this:
Write-Host ("\\{0}\{1}" -f $store.storeIp, $Global:config.dest)
Or since it's a path:
Join-Path $store.storeIp $Global:config.dest
EDIT: I've changed the code here to a simple test case, rather than the full implementation where this problem is arising.
I am trying to call one Powershell script from another, but things aren't working out as I'm expecting. As I understand things, the "&" operator is supposed to expand arrays into distinct parameters. That's not happening for me.
caller.ps1
$scriptfile = ".\callee.ps1"
$scriptargs = #(
"a",
"b",
"c"
)
& $scriptfile $scriptargs
callee.ps1
Param (
[string]$one,
[string]$two,
[string]$three
)
"Parameter one: $one"
"Parameter two: $two"
"Parameter three: $three"
Running .\caller.ps1 results in the following output:
Parameter one: a b c
Parameter two:
Parameter three:
I think that the problem I'm experiencing is $scriptargs array is not expanded, and is rather passed as a parameter. I'm using PowerShell 2.
How can I get caller.ps1 to run callee.ps1 with an array of arguments?
When invoking a native command, a call like & $program $programargs will correctly escape the array of arguments so that it is parsed correctly by the executable. However, for a PowerShell cmdlet, script, or function, there is no external programming requiring a serialize/parse round-trip, so the array is passed as-is as a single value.
Instead, you can use splatting to pass the elements of an array (or hashtable) to a script:
& $scriptfile #scriptargs
The # in & $scriptfile #scriptargs causes the values in $scriptargs to be applied to the parameters of the script.
You're passing the variables as a single object, you need ot pass them independently.
This here works:
$scriptfile = ".\callee.ps1"
& $scriptfile a b c
So does this:
$scriptfile = ".\callee.ps1"
$scriptargs = #(
"a",
"b",
"c"
)
& $scriptfile $scriptargs[0] $scriptargs[1] $scriptargs[2]
If you need to pass it as a single object, like an array, then you can have the callee script split it; the specific code for that would depend on the type of data you're passing.
Use Invoke-Expression cmdlet:
Invoke-Expression ".\callee.ps1 $scriptargs"
As the result you'll get :
PS > Invoke-Expression ".\callee.ps1 $scriptargs"
Parameter one: a
Parameter two: b
Parameter three: c
PS >
I'm attempting to pass a property to MSBuild. The property is a semicolon-delimited list of values. Unlike this question, I'm running MSBuild from PowerShell.
I get:
PS> msbuild .\Foo.sln /p:PackageSources="\\server\NuGet;E:\NuGet"
MSBUILD : error MSB1006: Property is not valid.
Switch: E:\NuGet
If I run the same command from Command Prompt, it works fine. How do I get it to work in PowerShell?
Wrap the parameter in single quotes:
... '/p:PackageSources="\\Server\NuGet;E:\NuGet"'
On PowerShell v3 try this:
msbuild .\Foo.sln --% /p:PackageSources="\\Server\NuGet;E:\NuGet"
Also using ASCIII value helps:
msbuild .\Foo.sln /p:PackageSources="\\Server\NuGet%3BE:\NuGet"
VBScript function below can be used to escape property values passed to MSBuild.exe inside double quotes:
Function Escape(s)
Escape = s
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.Global = True
objRegEx.Pattern = "(\\+)?"""
Escape = objRegEx.Replace(Escape,"$1$1\""")
objRegEx.Pattern = "(\\+)$"
Escape = objRegEx.Replace(Escape,"$1$1")
End Function
The following example demonstrates usage of Escape() function
Set objShell = WScript.CreateObject("WScript.Shell")
objShell.Run "msbuild.exe echo.targets /p:Param1=""" & Escape("ParamValue1") & """,Param2=""" & Escape("ParamValue1") & """", 1, True