I have a script internal.ps1 which accepts certain params:
param ($paramA, $paramB)
Write-Host $PSBoundParameters
And a script caller.ps1 that calls it:
.\internal -paramA A -paramB B
It works great:
PS C:\temp> .\caller
[paramA, A] [paramB, B] <<<< bounded to both params
However, in caller I want to keep the parameters to internal in a var, and use it later. However, that doesn't work:
$parms = "-paramA A -paramB B"
# Later...
.\internal $parms
Result: [paramA, A -paramB B] <<<<< All got bounded to ParamA
Neither does using an array:
$parms = #("A", "B")
# Later...
.\internal $parms
Result: [paramA, System.Object[]] <<<< Again, all bound to ParamA
How can I accomplish this? Note that the actual commandline is more complex, and may have unknown length.
The splatting operator (#) should do what you need.
Consider first this simple function:
function foo($a, $b) { "===> $a + $b" }
Calling with explicit arguments yields what you would expect:
foo "hello" "world"
===> hello + world
Now put those two values in an array; passing the normal array yields incorrect results, as you have observed:
$myParams = "hello", "world"
foo $myParams
===> hello world +
But splat the array instead and you get the desired result:
foo #myParams
===> hello + world
This works for scripts as well as for functions. Going back to your script, here is the result:
.\internal #myParams
[paramA, hello] [paramB, world]
Finally, this will work for an arbitrary number of parameters, so know a priori knowledge of them is needed.
powershell -file c:\temp\test.ps1 #("A","B")
or
powershell -command "c:\temp\test.ps1" A,B
Your script expects 2 arguments, but your previous attempts pass just a single one (a string and an array respectively). Do it like this:
$parms = "A", "B"
#...
.\internal.ps1 $parm[0] $parm[1]
Related
I'm using a format operator inside a script with a following example:
$current = 1
$total = 1250
$userCN = "contoso.com/CONTOSO/Users/Adam Smith"
"{0, -35}: {1}" -f "SUCCESS: Updated user $current/$total", $userCN
This excpectedly shows the following output:
SUCCESS: Updated user 1/1250 : contoso.com/CONTOSO/Users/Adam Smith
The format operator is there to keep the targeted output text in place with current / total running numbers varying between 1-99999. Without the format operator I could highlight the success line like this:
Write-Host -BackgroundColor Black -ForegroundColor Green "SUCCESS: Updated user $current/$total: $userCN"
But the question is how could I use the highlight-colors combined with the format operator? There's only the -f parameter and it doesn't allow the color parameters because, well, it's not the same thing as Write-Host in the first place.
Unlike other shells, PowerShell allows you to pass commands and expressions as command arguments simply by enclosing them in parentheses, i.e by using (...), the grouping operator.
When calling PowerShell commands (cmdlets, scripts, functions), the output is passed as-is as an argument, as its original output type.
Therefore, Theo's solution from a comment is correct:
Write-Host -BackgroundColor Black -ForegroundColor Green `
("{0, -35}: {1}" -f "SUCCESS: Updated user $current/$total", $userCN)
That is, the -f expression inside (...) is executed and its output - a single string in this case - is passed as a positional argument to Write-Host (implicitly binds to the -Object parameter).
Note that you do not need, $(...), the subexpression operator, in this case:
(...) is sufficient to enclose an expression or command.
In fact, in certain cases $(...) can inadvertently modify your argument, because it acts like the pipeline; that is, it enumerates and rebuilds array expressions as regular PowerShell arrays (potentially losing strong typing) and unwraps single-element arrays:
# Pass a single-element array to a script block (which acts like a function).
# (...) preserves the array as-is.
PS> & { param($array) $array.GetType().Name } -array ([array] 1)
Object[] # OK - single-element array was passed as-is
# $(...) unwraps it.
PS> & { param($array) $array.GetType().Name } -array $([array] 1)
Int32 # !! Single-element array was unwrapped.
# Strongly-typed array example:
PS> & { param($array) $array.GetType().Name } ([int[]] (1..10))
Int32[] # OK - strongly typed array was passed as-is.
# Strongly-typed array example:
PS> & { param($array) $array.GetType().Name } $([int[]] (1..10))
Object[] # !! Array was *enumerated and *rebuilt* as regular PowerShell array.
The primary use of $(...) is:
expanding the output from expressions or commands inside expandable strings (string interpolation)
To send the output from compound statements such as foreach (...) { ... } and if (...) { ... } or multiple statements directly through the pipeline, after collecting the output up front (which (...) does as well); however, you can alternatively wrap such statements & { ... } (or . { ... } in order to execute directly in the caller's scope rather than a child scope) in order to get the usual streaming behavior (one-by-one passing of output) in the pipeline.
Taking a step back: Given that you already can use compound statements as expressions in variable assignments - e.g., $evenNums = foreach ($num in 1..3) { $num * 2 } - and expressions generally are accepted as the first segment of a pipeline - e.g., 'hi' | Write-Host -Fore Yellow - it is surprising that that currently doesn't work with compound statements; this GitHub issue asks if this limitation can be lifted.
In the context of passing arguments to commands:
Use $(...), the subexpression operator only if you want to pass the output from multiple commands or (one or more) compound statements as an argument and/or, if the output happens to be a single object, you want that object to be used as-is or, if it happens to be a single-element array (enumerable), you want it to be unwrapped (pass the element itself, not the array.
Of course, if you're constructing a string argument, $(...) can be useful inside that string, for string interpolation - e.g., Write-Host "1 + 1 = $(1 + 1)"
Use #(...), the array subexpression operator only if you want to pass the output from multiple commands as an argument and/or you want to ensure that the output becomes a array; that is, the output is returned as a (regular PowerShell) array, of type [object[]], even if it happens to comprise just one object. In a manner of speaking it is the inverse of $(...)'s behavior in the single-object output case: it ensures that single-object output too becomes an array.
I'm a PowerShell newbie and I've got stuck on something very simple. I am trying to create a variable that use placeholders ({0}{1}...) using string format (-f). The place holder variables are passed as parameters to the function that builds/formats the string. Unfortunately, the place holders remain blank.
Here's my code:
function SendName(){
BuildReport $MyInvocation.MyCommand, 1, 2
}
Function BuildReport($functionName, $param1, $param2){
$report="You are in {0}. Parameter expected is {1}. Actual parameter is {2}" -f $functionName, $param1, $param2
write-host $report
}
SendName
The output I get is:
You are in System.Object[]. Parameter expected is . Actual parameter is
You have to omit the comma (,) where you invoke the BuildReport method:
BuildReport $MyInvocation.MyCommand 1 2
Otherwise you will pass an array as the first parameter and $param1 and $param2 won't get populated.
The Output will be:
You are in SendName. Parameter expected is 1. Actual parameter is
2
I've learned from this Stack Overflow question, that PowerShell return semantics are different, let's say, from C#'s return semantics. Quote from the aforementioned question:
PowerShell has really wacky return semantics - at least when viewed from a more traditional programming perspective. There are two main ideas to wrap your head around: All output is captured, and returned.
The return keyword really just indicates a logical exit point.
Let's look at this example:
function Calculate
{
echo "Calculate"
return 11
}
$result = Calculate
If you echo $result you will realise that something is not right. You'd expect this:
11
But the reality, what you actually see, is different:
Calculate
11
So instead of getting back only the intended return value, you actually get back an array.
You could get rid of the echo statements and not polluting the return value, but if you call another function from your function, which echoes something, then you're in trouble again.
How can I write a PowerShell function that only returns one thing? If the built-in PowerShell functions do return only one value, why can't I do that?
The workaround that I'm using now and I'd love to get rid of:
function Calculate
{
# Every function that returns has to echo something
echo ""
return 11
}
# The return values is the last value from the returning array
$result = (Calculate)[-1]
You can also just assign everything to null, and then return just the variable you want:
Function TestReturn {
$Null = #(
Write-Output "Hi there!"
1 + 1
$host
$ReturnVar = 5
)
return $ReturnVar
}
Several answers already given do not fully answer your question because they do not account for other cmdlets you might call--as you point out--that might "pollute" your output. The answer from #Laezylion is essentially correct but I believe bears further elaboration.
It is clear that you appreciate that your current workaround--forcing every function to return an array then taking the last element--is not a very robust solution. (In fact, I think it is safe to call that a kludge. :-) The correct approach--and a direct answer to your question--is clear... though at first glance it sounds like a tautology:
Question: How do you ensure a function returns only one thing?
Answer: By ensuring that it returns only one thing.
Let me elucidate. There are really two kinds of functions/cmdlets in PowerShell: (A) those that return data and (B) those that do not return data but may instead report progress or diagnostic messages. Problems arise, as you have seen, when you try to mix the two. And this may easily happen inadvertently. Thus, it is your responsibility, as the function author, to understand each function/cmdlet call within your function: specifically, does it return anything, be it a true return value or just diagnostic output? You are expecting output from type A functions, but you need to be wary of any type B functions. For any one that does return something you must either assign it to a variable, feed it into a pipeline, or... make it go away. (There are several ways to make it go away: pipe it to Out-Null, cast it to void, redirect it to $null, or assign it to $null. See Jason Archer’s Stack Overflow post that evaluates the performance of each of these flavors, suggesting you shy away from Out-Null.)
Yes, this approach takes more effort, but you get a more robust solution by doing so. For a bit more discussion on this very issue, these A/B functions, etc. see Question 1 on A Plethora of PowerShell Pitfalls recently published on Simple-Talk.com.
Don't use echo to write information, use Write-Host, Write-Verbose or Write-Debug depending on the message.
Echo is an alias for Write-Output which will send it as an Object through the pipeline. Write-Host/Verbose/Debug however is only console-messages.
PS P:\> alias echo
CommandType Name ModuleName
----------- ---- ----------
Alias echo -> Write-Output
Sample:
function test {
Write-Host calc
return 11
}
$t = test
"Objects returned: $($t.count)"
$t
#Output
calc
Objects returned: 1
11
Also, if you return the value on the last line in your function, then you don't need to use return. Writing the Object/value by itself is enough.
function test {
Write-Host calc
11
}
The difference is that return 11 would skip the lines after it you use it in the middle of a function.
function test {
return 11
12
}
test
#Output
11
Worth mentioning:
Any unmanaged command output inside your function will end up as a return value.
I use to get this trouble with a function managing XML content:
Function Add-Node(NodeName){
$NewXmlNode = $Xml.createElement("Item")
$Xml.Items.appendChild($NewXmlNode) | out-null
}
If you remove the Out-Null, the return of your function will be the Node basic properties... (You can also manage the exit code of the appendChild command ... it depends on the motivation) :)
#Michael Sorens's answer is useful, but it suggests a design strategy that seems rather onerous. I don't want to have to separate out data processing functions from functions that report progress or diagnostic messages. Reading his blogpost, I found a less hacky work-around to the OP's question. Here's the key sentence from the blog post:
A PowerShell function returns all uncaptured output.
So what if we capture our output?
function Calculate
{
$junkOutput = echo "Calculate"
return 11
}
$result = Calculate
Now $result just contains 11, as intended.
You could replace echo (alias of Write-Output) with Write-Host:
PS> function test{ Write-Host "foo";"bar"}
PS> $a = test
test
PS >$a
bar
echo is an alias for Write-Output which sends the object to the next command.
Using Write-Host should help:
PS:> function Calculate
>> {
>> # Every function that returns has to echo something
>> Write-Host "test"
>> return 11
>> }
>>
PS:> $A = Calculate
test
PS:> $A
11
The use of parenthesis seems critical when calling a function that accepts parameters.
This example fills a DataTable from SQL Server, and returns an [int] from the first row, fourth column.
Note the parenthesis around the actual function call assigned to the variable [int]$tt1
function InvokeSQL-GetOneCount { param( [string]$cn, [string]$prj )
$sql = "SELECT * FROM [dbo].[dummytable] WHERE ProjectNumber = '$prj' ORDER BY FormID, PartID;"
$handler = [System.Data.SqlClient.SqlInfoMessageEventHandler] { param($sender, $event) Handle-Message -message $event.Message -fcolor "yellow" -bcolor "black"; };
$conn = new-Object System.Data.SqlClient.SqlConnection($cn)
$conn.add_InfoMessage($handler);
$conn.FireInfoMessageEventOnUserErrors = $true;
$cmd = New-Object System.Data.SqlClient.SqlCommand;
$cmd.Connection = $conn;
$cmd.CommandType = [System.Data.CommandType]::Text;
$cmd.CommandText = $sql;
$cmd.CommandTimeout = 0;
$sa = New-Object System.Data.SqlClient.SqlDataAdapter($cmd);
$dt = New-Object System.Data.DataTable("AllCounts");
$sa.Fill($dt) | Out-Null;
[int]$rval = $dt.Rows[0][3];
$conn.Close();
return $rval;
}
clear-host;
[int]$tt1 = (InvokeSQL-GetOneCount -cn "Server=[your server];DataBase=[your catalog];Integrated Security=SSPI" -prj "MS1904");
write-host $tt1;
I ended up using the following to make sure only one value is returned:
write-host 'some console message'
invoke-some-utility arg1 arg2 | write-host
functionCall arg1 arg2 | out-null # suppress any output
return theDesiredValue
PowerShell does pipes awkwardkly. To work around the return value problem and save the pipeline for real data, pass the function a value by reference, that is, an array. The array can be loaded with the value you wish to return. Here's a simple example:
# tp.ps1
# test passed parameters
# show that arrays are passed by reference, and can be used to provide
# return values outside the pipeline
function doprint
{
process { write-output "value now: $psitem" }
}
function recurse($thing, $rtx)
{
$thing++
if($thing -lt 20) {
if($thing -eq 15) { $rtx[0] = $thing }
write-output $thing | doprint
recurse $thing $rtx
}
}
j=0
$rtv=#(4)
recurse $j $rtv
write-output $rtv[0]
I was surprised how powershell return works. Because I never had to write a function which returns a value. It returns not the variable itself, but all output stream of the function. To not pass output, you would have to use Out-Null. So your example would be:
function Calculate
{
. {
echo "Calculate"
$result = 11
return
} | Out-Null
return $result
}
$result = Calculate
And result is:
11
Credits to https://riptutorial.com/powershell/example/27037/how-to-work-with-functions-returns
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 >
It seems that somebody has subtly changed the way that parameter switches are parsed on powershell. On some machines "split-path c:\x\y --parent" works. On some it fails. Can anyone tell me a) what causes the difference and b) how can I stop it?
Switch parameters should work in the same way in both V1 and V2 (that means -parent is the right syntax).
In your case --parent should be bound to an parameter as a string. It should not be interpreted as a switch. You can test the binding via Trace-Command
Trace-Command parameterbinding -Expression { split-path c:\x\y --parent} -PSHost
Further info:
Considering --: every string behind -- is interpreted as argument, no matter if it looks like a switch.
[14]: function test {
param([switch]$sw, [string]$str)
write-host switch is $sw
write-host str is $str
}
[15]: test 1
switch is False
str is 1
[16]: test -sw
switch is True
str is
[17]: test -- -sw
switch is False
str is -sw