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
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.
Reason I'm Doing This
I'm trying to set a token in a file I have. The contents of the token is 1 line in the file, and it's string value is $token=$false
Simplified to test code
When I try to convert this token into a bool value, I'm having some problems. So I wrote test code and found I'm not able to convert the string to a bool value.
[String]$strValue = "$false"
[Bool]$boolValue = $strValue
Write-Host '$boolValue =' $boolValue
This gives the following error...
Cannot convert value "System.String" to type "System.Boolean", parameters of this type only accept booleans or numbers, use $true, $false, 1 or 0 instead.
At :line:2 char:17
+ [Bool]$boolValue <<<< = $strValue
As you can see, I am using the $false value as is suggested by the error message, but it's not accepting it. Any ideas?
In PowerShell, the usual escape character is the backtick. Normal strings are interpolated: the $ symbol is understood and parsed by PowerShell. You need to escape the $ to prevent interpolation. This should work for you:
[String]$strValue = "`$false"
To convert "$true" or "$false" to a boolean in a generic way, you must first drop the leading $:
$strValue = $strValue.Substring(1)
Then convert to boolean:
[Boolean]$boolValue = [System.Convert]::ToBoolean($strValue)
Using your code from your comment, the shortest solution would be:
$AD_Export_TokenFromConfigFile =
[System.Convert]::ToBoolean(Get-Content $AD_Export_ConfigFile
| % {
If($_ -match "SearchUsersInfoInAD_ConfigToken=") {
($_ -replace '*SearchUsersInfoInAD_ConfigToken*=','').Trim()
}
}.Substring(1))
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 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]
Wondering how I could pass an array as command line argument to an exe file in powershell? Here is what I am currently working on
I am calling an exe file from a powershell function in the following format
$firstParam = "test1"
$secondParam = "test2"
$thirdParam = #()
$thirdParam = 'test3'
$thirdParam = $thirdParam + 'test4'
[void](& '..\SomeApp.exe' "$firstParam" "$secondParam" "$thirdParam"
Here is what I am seeing as the input arguments in the Application.exe
The third input parameter that was passed from the powershell was an array but it got concatenated (space separated) when passed to the exe file.
Is it possible to pass "test3" as the third argument and "test4" as the fourth argument?
$thirdParam cannot be an array with your implementation. When you write $thirdParam = #(), you do declare an empty array, but then you re-assign it to a string: $thirdParam = 'test3' and then to another string $thirdParam = $thirdParam + 'test4'. I am still not clear about your original intent, but here's how you would pass test3 as third argument and test4 as the fourth:
$fourthParam = 'test4'
[void](& '..\SomeApp.exe' "$firstParam" "$secondParam" "$thirdParam" "$fourthParam"
If you only have 2 fixed parameters, and you can have N parameters, switch to Invoke-Expression instead of Invoke-Command:
[void](Invoke-Expression "..\SomeApp.exe $firstParam $secondParam $thirdParam"
and make sure your parameters are correctly quoted. In this case, if $thirdParam contains spaces, it will determine your parameter #4 and onwards.