How to pass array of arguments to Powershell commandline - powershell

I am trying to pass array of arguments to powershell script file.
I was trying to pass the commandline like this in command line.
Powershell -file "InvokeBuildscript.ps1" "z:\"
"Component1","component2"
But it doesn't take the parameters it seems. What am i missing? how to pass array of arguments?

Short answer: More double quotes could help...
suppose the script is "test.ps1"
param(
[Parameter(Mandatory=$False)]
[string[]] $input_values=#()
)
$PSBoundParameters
Suppose would like to pass the array #(123,"abc","x,y,z")
Under Powershell console, to pass multiple values as an array
.\test.ps1 -input_values 123,abc,"x,y,z"
Under Windows Command Prompt Console or for Windows Task Scheduler; A double-quote are replaced by 3 double-quotes
powershell.exe -Command .\test.ps1 -input_values 123,abc,"""x,y,z"""
Hope it could help some

try
Powershell -command "c:\pathtoscript\InvokeBuildscript.ps1" "z:\" "Component1,component2"
if test.ps1 is:
$args[0].GetType()
$args[1].gettype()
call it from a dos shell like:
C:\>powershell -noprofile -command "c:\script\test.ps1" "z:" "a,b"
returns :
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
True True Object[] System.Array

One can also pass array variables as command line arguments. Example:
Consider the following Powershell Module
File: PrintElements.ps1
Param(
[String[]] $Elements
)
foreach($element in $Elements)
{
Write-Host "element: $element"
}
To use the above powershell module:
#Declare Array Variable
[String[]] $TestArray = "Element1", "Element2", "Element3"
#Call the powershell module
.\PrintElements.ps1 $TestArray
And if you want to concatenate and pass the TestArray as a single string of space separated elements then you can call the PS module by enclosing the argument in quotes as shown below:
#Declare Array Variable
[String[]] $TestArray = "Element1", "Element2", "Element3"
#Call the powershell module
.\PrintElements.ps1 "$TestArray"

Related

complex default values for powershell script Params

I would like to set a default Param value that must be determined at script runtime (cannot be hardcoded).
Given powershell script file.ps1
<#
.PARAMETER arg1
The first argument. Defaults to ???
#>
Param (
[string] $arg1,
)
I would like:
Set $arg1 to a value that must be determined when the script runs, for example, the host IP address.
Print the default value of $arg1 within the .PARAMETER arg1 help message.
Typically I might add
$arg1_default = (Test-Connection -ComputerName "www.google.com" -Count 1).Address.IPAddressToString
<#
.PARAMETER arg1
The first argument. Defaults to $arg1_default.
#>
Param (
[string] $arg1 = $arg1_default,
)
However, in Powershell, the Param statement must be the first processed statement. If I add any complex statements before Param statement then it results in an error:
PS> .\file.ps1
Param: file.ps1:9
Line |
9 | Param (
| ~~~~~
| The term 'Param' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the
| name, or if a path was included, verify that the path is correct and try again.
How do I set default values for a powershell script Param ?
I'm using Powershell 7.
You can use a function with [CmdletBinding()] attribute like Toto in the file test.ps1 defining a default value for a particular parameter for this CmdLet with $PSDefaultParameterValues
# Test.ps1
$PSDefaultParameterValues["Toto:arg1"]=Invoke-Command -ScriptBlock {(Test-Connection -ComputerName "www.google.com" -Count 1).IPV4Address.IPAddressToString}
Function Toto
{
[CmdletBinding()]
Param ([string] $arg1)
Write-Output $arg1
}
Now in the script where you want to use this (or these) function(s) you can first dot source the.ps1 file that contains the function(s), then call your function toto
. /Test.ps1
Toto # without argument gives "172.217.18.196" for me now
Toto titi # With argument give "titi"

Is -Args a correct substitute for -ArgumentList?

I came across some code using Start-Process in this form.
Start-Process <cmd> -Args <arguments>
On checking the Start-Process docs, it says Start-Process takes a parameter of -ArgumentList but doesn't actually mention -Args.
Is this shortening documented somewhere or is it just a known thing that you can shorten ArgumentList to Args? If so, do they behave in the same way?
Yes it's already documented on the same link.Args is an alias name for ArgumentList.
-ArgumentList
Specifies parameters or parameter values to use when this cmdlet starts the process. Arguments can be accepted as a single
string with the arguments separated by spaces, or as an array of
strings separated by commas. The cmdlet joins the array into a single
string with each element of the array separated by a single space.
The outer quotes of the PowerShell strings are not included when the
ArgumentList values are passed to the new process. If parameters or
parameter values contain a space or quotes, they need to be surrounded
with escaped double quotes. For more information, see
about_Quoting_Rules.
Type: String[]
Aliases: Args
Position: 1
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
-Args is an Alias for -ArgumentList as mentioned on Abdul Niyas P M answer, parameters in PowerShell can have many aliases. This can be particularly useful for example on functions that accept value from pipeline by property name.
See Benefits of Using Aliases
function SayHello {
param(
[parameter(ValueFromPipelineByPropertyName)]
[alias('alias1','alias2')]
[string]$Message
)
"Hello $Message"
}
PS /> [pscustomobject]#{ Message = 'World!' } | SayHello
Hello World!
PS /> [pscustomobject]#{ alias1 = 'World!' } | SayHello
Hello World!
PS /> [pscustomobject]#{ alias2 = 'World!' } | SayHello
Hello World!
If you want to know if a functions has aliases you can always use Get-Help:
PS /> Get-Help Start-Process -Parameter * |
Where-Object Aliases -NE 'none' |
Select-Object Name, Aliases
name aliases
---- -------
ArgumentList Args
Credential RunAs
FilePath PSPath
LoadUserProfile Lup
NoNewWindow nnw
RedirectStandardError RSE
RedirectStandardInput RSI
RedirectStandardOutput RSO
Other relevant documention:
about_Pipelines
about_Functions
about_Functions_Advanced_Parameters

Pass parameter to powershell encoded command

I have a script which has quite a lot of lines.
I can easily paste this script in a scriptblock parameter without having to edit it (e.g. put backslashes in front of quotes in the script). I can then encode the script so it can be passed to powershell as en encoded parameter:
$myscript = {
#paste of simplified script
$calc = 6 + 9
echo $calc
}
# Convert script to a string
$command = $carvingScript.ToString()
# Convert string to base64 encoded command
$bytes = [System.Text.Encoding]::Unicode.GetBytes( $command )
$encodedCommand = [Convert]::ToBase64String( $bytes )
I would like to be able to pass one parameter in the script that gets base64 converted. Like this:
$parameter = 9
$myscript = {
$calc = 6 + $parameter
echo $calc
}
Any ideas how to tackle this? I know scriptblock can contain arguments, but in order to parse the argument the whole script needs to be parsed, not just the one parameter
The direct answer to how to add variables to a script block is this:
$parameter = 9
$myscript = #'
$calc = 6 + {0}
echo $calc
'# -f $parameter
$scriptblock = [scriptblock]::Create($myscript)
Basically build it as a string and use the create method from [scriptblock] to convert.
But you can skip creating the scriptblock since you will just convert it back to a string directly afterwards.
It's an old post, but I found this article which worked for me, so I want to share it with you dear community :)
You can use param block with mandatory parameters inside your script block:
$myscript = {
param
(
[Parameter(Mandatory)]
[decimal]
$First,
[Parameter(Mandatory)]
[decimal]
$Second
)
[decimal]($First + $Second)
}
$bytes = [System.Text.Encoding]::Unicode.GetBytes($myscript)
$encodedCommand = [Convert]::ToBase64String($bytes)
$encodedCommand | Set-Content 'C:\temp\encodedCommand.txt' -Encoding UTF8
Then pass parameters by pipeline between two powershell.exe calls:
powershell.exe -noprofile -command "3.3, 2.7" | powershell.exe -encodedcommand DQAKACAAIAAgACAAcABhAHIAYQBtAA0ACgAgACAAIAAgACgADQAKACAAIAAgACAAIAAgACAAIABbAFAAYQByAGEAbQBlAHQAZQByACgATQBhAG4AZABhAHQAbwByAHkAKQBdAA0ACgAgACAAIAAgACAAIAAgACAAWwBkAGUAYwBpAG0AYQBsAF0ADQAKACAAIAAgACAAIAAgACAAIAAkAEYAaQByAHMAdAAsAA0ACgANAAoAIAAgACAAIAAgACAAIAAgAFsAUABhAHIAYQBtAGUAdABlAHIAKABNAGEAbgBkAGEAdABvAHIAeQApAF0ADQAKACAAIAAgACAAIAAgACAAIABbAGQAZQBjAGkAbQBhAGwAXQANAAoAIAAgACAAIAAgACAAIAAgACQAUwBlAGMAbwBuAGQADQAKACAAIAAgACAAKQANAAoAIAAgACAAIABbAGQAZQBjAGkAbQBhAGwAXQAoACQARgBpAHIAcwB0ACAAKwAgACQAUwBlAGMAbwBuAGQAKQANAAoA
This is using Powershell interactive input mode which is visible in the overall output, so be aware if you pass any passwords or secrets:
cmdlet at command pipeline position 1
Supply values for the following parameters:
First: 3.3
Second: 2.7
6.0
If you ever try to have list (array) parameter and pass list of values to the encoded command then you need to remember that last array element must be an empty string - this is how you trick interactive input mode into setting the list parameter.
You also need to remember to do not mark list parameter as ValueFromPipeline otherwise, it will not consume values properly.
$command = {
param
(
[Parameter(Mandatory)]
[string[]]
$MyList
)
$MyList | ForEach-Object { Write-Host $_ }
}
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
"powershell.exe -noprofile -command `"'test1', 'test2', 'test3', ''`" | powershell.exe -encodedcommand $encodedCommand" | Set-Content 'C:\temp\test.txt' -Encoding UTF8
PS C:\temp> powershell.exe -noprofile -command "'test1', 'test2', 'test3', ''" | powershell.exe -encodedcommand DQAKACAAIAAgACAAcABhAHIAYQBtAA0ACgAgACAAIAAgACgADQAKACAAIAAgACAAIAAgACAAIABbAFAAYQByAGEAbQBlAHQAZQByACgATQBhAG4AZABhAHQAbwByAHkAKQBdAA0ACgAgACAAIAAgACAAIAAgACAAWwBzAHQAcgBpAG4AZwBbAF0AXQANAAoAIAAgACAAIAAgACAAIAAgACQATQB5AEwAaQBzAHQADQAKACAAIAAgACAAKQANAAoAIAAgACAAIAAkAE0AeQBMAGkAcwB0ACAAfAAgAEYAbwByAEUAYQBjAGgALQBPAGIAagBlAGMAdAAgAHsAIABXAHIAaQB0AGUALQBIAG8AcwB0ACAAJABfACAAfQANAAoA
cmdlet at command pipeline position 1
Supply values for the following parameters:
MyList[0]: test1
MyList[1]: test2
MyList[2]: test3
MyList[3]:
test1
test2
test3
I hope it'll help someone in the future. Peace!

Why this code passing switch parameters to a PowerShell function fails

This question is about passing switch parameters. Let's see the code. I have this PowerShell 3.0 function:
#test1.ps1
param(
[switch] $param1 = $false
)
Write-Host "param1: $($param1.IsPresent)"
Write-Host
I have this main PowerShell function that invokes test.ps1 in four different ways:
#Test0.ps1
cls
$param1 = $True
# 1
.\test1.ps1 -param1
# 2
.\test1.ps1 -param1:$true
# 3
$potato = "-param1:`$$($param1)"
Write-Host "Parameter value: $potato"
.\test1.ps1 $potato
# 4
$command = ".\test1.ps1 -param1:`$$($param1)"
Write-Host "Command: $command"
iex $command
exit
Why is the 3rd way of doing it failing? I know I can do 4th way but I would love to understand why 3rd is failing.
Here is the output. As result all the parameters should be True but third one is False...
param1: True
param1: True
Parameter value: -param1:$True
param1: False
Command: .\test1.ps1 -param1:$True
param1: True
What happens is that:
The parser looks at the provided argument: "-param1:$true"
Fails to bind it to parameter param1, since the value you provided is a string, not a switch/bool
No specific parameter type is required at position 0, argument is ignored
If you make param1 positional, you can see how PowerShell fails to bind it properly:
function test-parambinding {param([Parameter(Position=0)][switch]$param1);$param1.IsPresent}
test-parambinding "-param1:`$true"
You'll see a ParameterArgumentTransformationException thrown before anything else happens
Mathias explains why your 3rd way of passing the parameter fails, but there is another way to pass parameters that lets you do roughly what you seem to be attempting here.
I've used a function here as it's a bit less to type when calling it, but your script file will work just the same:
PS C:\> function test1() {
param(
[switch] $param1 = $false
)
Write-Host "param1: $($param1.IsPresent)"
Write-Host
}
PS C:\> $param1 = $True
PS C:\> $potato = #{'param1'=$param1}
PS C:\> $potato
Name Value
---- -----
param1 True
PS C:\> test1 #potato
param1: True
So, instead of passing the argument and value as a single string, create a hash with the argument name as the key and the argument as the value. Then call the function or script using the # splatting operator. (See help about_Splatting for more detail).

Powershell Start-Process to start Powershell session and pass local variables

Is there a way to use the Powershell Start-Process cmdlet to start a new Powershell session and pass a scriptblock with local variables (once of which will be an array)?
Example:
$Array = #(1,2,3,4)
$String = "This is string number"
$Scriptblock = {$Array | ForEach-Object {Write-Host $String $_}}
Start-Process Powershell -ArgumentList "$Scriptblock"
Thanks.
I'm pretty sure there's no direct way to pass variables from one PowerShell session to another. The best you can do is some workaround, like declaring the variables in the code you pass in -ArgumentList, interpolating the values in the calling session. How you interpolate the variables into the declarations in -ArgumentList depends on what types of variables. For an array and a string you could do something like this:
$command = '<contents of your scriptblock without the curly braces>'
Start-Process powershell -ArgumentList ("`$Array = echo $Array; `$String = '$String';" + $command)
I was able to get this to work by joining the array with "/" to create a string and entering the scriptblock into another .ps1 script with appropriate parameters and splitting the joined string back to an array within the second script and using
Start-Process Powershell -ArgumentList "&C:\script.ps1 $JoinedArray $String"
Ugly, but it's the only way I could get it to work. Thanks for all the replies.
You could wrap the contents of your script block in a function, and then call the function from the ArgumentList and pass in the variables as parameters to the function, as I do on this post.
$ScriptBlock = {
function Test([string]$someParameter)
{
# Use $someParameter to do something...
}
}
# Run the script block and pass in parameters.
$myString = "Hello"
Start-Process -FilePath PowerShell -ArgumentList "-Command & {$ScriptBlock Test('$myString')}"
The command line options for PowerShell.exe say that you should be able to pass arguments when using a script block by adding -args:
PowerShell.exe -Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] }
However when I try to do that I get the following error:
-args : The term '-args' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and
try again.
I added $MyInvocation | fl to the script block to see what was happening, and it looks like the -args is just appended to the deserialized commands in the script block (hence the error since -args is not a valid command). I also tried using GetNewClosure() and $Using:VariableName but those only appear to work when the script block is invoked (as opposed to this where we are using it to serialize/deserialize the commands).
The I was able to get it to work by wrapping it in a function like deadlydog's answer.
$var = "this is a test"
$scriptblock = {
$MyInvocation | fl #Show deserialized commands
function AdminTasks($message){
write-host "hello world: $message"
}
}
Start-Process powershell -ArgumentList '-noexit','-nologo','-noprofile','-NonInteractive','-Command',$scriptblock,"AdminTasks('$var')" -Verb runAs #-WindowStyle Hidden
#Output:
MyCommand :
$MyInvocation | fl #Show deserialized commands
function AdminTasks($message){
write-host hello world: $message
}
AdminTasks('this is a test')
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 0
OffsetInLine : 0
HistoryId : 1
ScriptName :
Line :
PositionMessage :
PSScriptRoot :
PSCommandPath :
InvocationName :
PipelineLength : 2
PipelinePosition : 1
ExpectingInput : False
CommandOrigin : Runspace
DisplayScriptPosition :
hello world: this is a test
Wrapping it in a script block and using $args[0] or $args[1] also works, just be aware that you many need to wrap the $var0 or $var1 in quotes if there are issues when it is deserialized and use `$ to prevent the $sb from being replaced with "" since that variable doesn't exist in the caller's scope:
$var0 = "hello"
$var1 = "world"
$scriptblock = {
$MyInvocation | fl #Show deserialized commands
$sb = {
write-host $args[0] $args[1]
}
}
Start-Process powershell -ArgumentList '-noexit','-nologo','-noprofile','-NonInteractive','-Command',$scriptblock,"& `$sb $var0 $var1"
If you want to pass objects that are serializable, but are not strings, I wrote up a solution: Is there a way to pass serializable objects to a PowerShell script with start-process?