PowerShell Script Arguments Passed as Array - powershell

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 >

Related

PowerShell: Passed Parameters not in Format String

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

Powershell: unexpected return value from function, use of $args to access parameters

Ok, I have coded for quite a while in different, but I am not getting Powershells concept of a function return?....
I am very new to Powershell, so I am sure I am missing something very basic.
I have the function below:
function plGetKeyValue ([string] $FileName, [string] $SectionName, [string] $Key)
{
if ($PSBoundParameters.Count -lt 2 -or $PSBoundParameters.Count -gt 3 )
{
"Invalid call to {0} in {1}" -f $MyInvocation.MyCommand.Name,
$MyInvocation.MyCommand.ModuleName
return
}
# Declaration
$lFileContents = ""
$lSections = ""
$lDataStart = ""
$lStart = -1
$lEnd = -1
$lFoundSection = ""
$lNextSection = ""
$lResults = ""
$lRetValue = ""
# Handle the optional parameter.
if ( $PSBoundParameters.Count -eq 2 ) {
$PSBoundParameters.Add('Key', $SectionName)
$PSBoundParameters.Remove('SectionName')
$Key = $SectionName
$SectionName = $null
}
# Read the file in
$lFileContents = Get-Content $FileName | Select-String -Pattern .*
# Get the sections.
$lSections = $lFileContents -match '\['
$lSections = $lSections -notmatch '#'
# Start of the data.
$lDataStart = $lFileContents | Select-String -Pattern "^#", "^$" -NotMatch `
| select-object -First 1
# We have a section.
if ( $PSBoundParameters.ContainsKey( 'SectionName' ) ) {
# Find the section.
$lFoundSection = $lSections | Select-String -Pattern "$lSectionName\b"
# If none found we are out.
if ( -Not $lFoundSection) { return $lRetValue }
# Starting point for the key search is the line following
# the found section.
$lStart = $lFoundSection[0].LineNumber
# Loop through the sections and find the one after the found one.
$lNextSection = $lSections | ForEach-Object {
# If we hit it, break.
if ($_.LineNumber -gt $lStart) {
break;
}
}
# Set the ending line for the search to the end of the section
# or end of file. Which ever we have.
if ($lNextSection) {
$lEnd = $lNextSection[0].LineNumber
} else {
$lEnd = $lFileContents[-1]
}
} else {
# No section.
$lStart = $lDataStart.LineNumber
# Set the ending line for the search to the end of the section
# or end of file. Which ever we have.
if ($lSections) {
$lEnd = $lSections[0].LineNumber
} else {
$lEnd = $lFileContents[-1]
}
}
# Extract the lines starting with the key.
$lResults = $lFileContents[$lStart..$lEnd] -match "$Key\b"
# We got results.
# Split the value off.
return $lRetValue = $lResults[0] | Select -ExpandProperty "Line"
}
The process of creating this function has sparked several questions that I have researched and become confused with
1) The documentation indicates that $args should be used to determine arguments. It never seems to populate for me? I am using version 4? As a alternative I used $PSBoundParameters. Is this advisable?
2) Based on a lot of reading and head scratching, I have found that return values from functions rturn all uncaptured output to the pipeline. Can someone, please clarify uncaptured?
As an example, I would like the function below to return a string in the variable $lRetValue. Currently, it is returning True. Based on that I believe I have something uncaptured? But everything I am executing is captured in a variable. What am I missing?
The calling routine is calling the code in the following form:
$FileName = "S:\PS\Home\GlobalConfig\jobs.cfg"
$key = "Help"
$section = "Section"
$r = plGetKeyValue $FileName $Key
write-host "r is: $r"
The output shows as follows:
PS C:> S:\PS\Home\Job\Test.ps1
r is: True
Any assistance would be very much appreciated.
Terminology note: somewhat arbitrarily, I'll distinguish between parameters and arguments below:
- parameters as the placeholders that are defined as part of a function declaration,
- as distinct from arguments as the values that are bound to the placeholders in a given invocation.
Conceptual information:
1) The documentation indicates that $args should be used to determine arguments.
$args is a fallback mechanism for examining unbound arguments in non-advanced (non-cmdlet) functions.
$args is populated:
ONLY if your function is not an advanced function (a function is marked as an advanced function by the presence of a param(...) parameter-declaration statement - as opposed to declaring the parameters inside function someFunc(...)) - if decorated with a [CmdletBinding()] attribute).
even then it only contains the unbound arguments (those not mapped to declared parameters).
In other words: only if you declare your function without any parameters at all does $args contain all arguments passed.
Conversely, in an advanced function there mustn't be unbound arguments, and invoking an advanced function with arguments that cannot be bound to parameters simply fails (generates an error).
Since defining advanced functions is advisable in general, because they are best integrated with the PowerShell infrastructure as a whole, it's best to make do without $args altogether.
Instead, use a combination of multiple parameter sets and/or array parameters to cover all possible valid input argument scenarios.
$PSBoundArguments contains the arguments bound to declared parameters, and is normally not needed, because the variable names corresponding to the parameters names (e.g., $SectionName) can be used directly. (It has specialized uses, such as passing all bound parameters on to another cmdlet/function via splat #PSBoundArguments).
2) Based on a lot of reading and head scratching, I have found that return values from functions return all uncaptured output to the pipeline. Can someone, please clarify "uncaptured"?
Generally, any PowerShell statement or expression that generates output is sent to the success stream (loosely comparable to stdout in Unix shells) by default, UNLESS output is captured (e.g., by assigning to a variable) or redirected (e.g., by sending output to a file).
Thus, in a reversal of how most programming languages behave, you must take action if you do NOT want a statement to produce output.
If you're not interested in a statement's output (as opposed to capturing / redirecting it for later use), you can redirect to $null (the equivalent of /dev/null), pipe to cmdlet Out-Null, or assign to dummy variable $null ($null = ...).
Therefore, in a manner of speaking, you can call output that is sent to the success stream uncaptured.
That, however is unrelated to the return statement:
The return statement does not work the same way as in other languages; its primary purpose in PowerShell is as a control-flow mechanism - to exit a function or script block - rather than a mechanism to output data (even though it can also be used for that: with an argument, is another way to send output to the success stream).
Diagnosing your specific problem:
There are many ways in which your function could be made a better PowerShell citizen[1]
, but your immediate problem is this:
$PSBoundParameters.Remove('SectionName')
returns a Boolean value that is sent to the output stream, because you neither suppress, capture nor redirect it. In your case, since the $SectionName parameter is bound, it does have an entry in $PSBoundParameters, so $PSBoundParameters.Remove('SectionName') returns $true.
To suppress this unwanted output, use something like this:
$null = $PSBoundParameters.Remove('SectionName')
Generally speaking, unless you know that a statement does not generate output, it's better to be safe and prepend $null = (or use an equivalent mechanism to suppress output).
Especially with direct method calls on objects, it's often not clear whether a value - which turns into output (is sent to the success stream) - will be returned.
[1] The following help topics provide further information:
- USE of parameters, including how to inspect them with help -Full / -Detailed ...:
help about_Parameters
- DEFINING simple functions and their parameters:
help about_functions,
from which you can progress to advanced functions:
help about_functions_advanced
and their parameter definitions:
help about_Functions_Advanced_Parameters

Concatenate elements of a char array and strings in powershell

I'm probably over thinking this, but this is not coming out the way I expect. I've searched google, I've searched stackoverflow. Please Help.
Here are some variables for testing, and the invocation of a function:
$SQL_FirstName = "PowerShell";
$SQL_LastName = "CreateUser";
$SQL_Office = "TEST";
$SQL_IsAdmin = $true;
Create_User($SQL_FirstName.ToLower(), $SQL_LastName.ToLower(), $SQL_Office, $SQL_IsAdmin);
Here is the function, not much there yet:
Function Create_User([string]$FirstName, [string]$LastName, $Office, $IsAdmin)
{
$FirstNameCharArray = [char[]]$FirstName;
$UserName = [string]$FirstNameCharArray[0] + $LastName;
Write-Host $UserName;
}
Now I expect the output to be "pcreateuser". But it's not. I have tried casting different things, I have tried surrounding my variables with $(). I have tried using the + symbol and not using the + symbol. I have tried smashing the variables right up against each other. Every single time it just outputs "p".
Any help would be appreciated. Thanks!
It's because of how you are calling the function. You are not supposed to use brackets for function calls nor use commas to separate the parameters (unless you are sending array values on purpose or subexpressions). You have passed it a single array of those elements.
Create_User $SQL_FirstName.ToLower() $SQL_LastName.ToLower() $SQL_Office $SQL_IsAdmin
In your function call your sent an array to $firstname which was casted as a string "powershell createuser TEST True". The other parameters would have been blank. Hence your output.
They work just the same as cmdlet calls. Just use spaces to separate the parameters and their values.
Get-ChildItem -Filter "*.txt" -Path "C:\temp"
String to char array
For what it is worth you don't need to cast the string as a char array. You can just use array notation directly on the string.
PS C:\Users\Matt> [string]"Bagels"[0]
B
Heck you don't even need to cast it "Bagels"[0]

How to keep parameter list in a variable

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]

Powershell - Pass an array as argument to an executable called from command line

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.