How do I pass an array parameter to powershell -file? - powershell

I have the following powershell script:
param(
[Int32[]] $SomeInts = $null,
[String]$User = "SomeUser",
[String]$Password = "SomePassword"
)
New-Object PSObject -Property #{
Integers = $SomeInts;
Login = $User;
Password = $Password;
} | Format-List
If I execute .\ParameterTest.ps1 (1..10) I get the following:
Password : SomePassword
Login : SomeUser
Integers : {1, 2, 3, 4...}
However, I don't get the expected results if I run it in a separate powershell instance like this powershell -file .\ParameterTest.ps1 (1..10). In that case I get the following:
Password : 3
Login : 2
Integers : {1}
My question is how can I pass the array, or other complex data type from a command line?

The individual elements of the array (1..10) are being passed as parameters to the script.
An alternative would be to do:
powershell -command {.\test.ps1 (1..10)}
For a version that works both from powershell console and cmd:
powershell -command "&.\test.ps1 (1..10)"

The answer is to use powershell.exe -EncodedCommand and to base64 encode the parameters. The description for this is on the PowerShell.exe Console Help technet page. I have compressed their version of the ceremony to do this into a one-liner:
powershell.exe -EncodedCommand "$([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes('.\ParameterTest.ps1 (1..10)')))"

Related

Printing the argument in powershell

May I know, why the string argument is not printed in the following powershell script?
function Get-Name ( [string] $Username ) {
echo "user : $Username"
}
PS C:\> .\Get-Name.ps1 -username "test"
PS C:\>
The script file Get-Name.ps1 only defines the function Get-Name, it doesn't execute it.
Use the dot-source operator (.) to have it define the function in the calling scope, and then you can execute the function itself:
PS C:\> . .\Get-Name.ps1
PS C:\> Get-Name -Username test
user : test
Alternatively, remove the function Get-Name { and } part of the script as pointed out by Lee_Dailey, at which point the script file becomes a parameterized function in itself and you can then do:
PS C:\> .\Get-Name.ps1 -Username test
user : test
Please see the about_Scripts help file, especially the section about script scope and dot sourcing
the problem is that you define a function, not a callable script. [grin] this will work ...
Param ([string] $Username)
echo "user : $Username"
here's an example of calling the above ...
. .\Func_Get-Name.ps1 -username 'tutu'
output ...
user : tutu

Redirect standard input (read-host) to a Powershell script

Here's a sample powershell script:
$in = read-host -prompt "input"
write-host $in
Here's a sample 'test.txt' file:
hello
And we want to pass piped input to it from powershell. Here's some I have tried:
.\test.ps1 < test.txt
.\test.ps1 < .\test.txt
.\test.ps1 | test.txt
.\test.ps1 | .\test.txt
test.txt | .\test.ps1
.\test.txt | .\test.ps1
get-content .\test.txt | .\test.ps1
even just trying to echo input doesn't work either:
echo hi | \.test.ps1
Every example above that doesn't produce an error always prompts the user instead of accepting the piped input.
Note: My powershell version table says 4.0.-1.-1
Thanks
Edit/Result: To those looking for a solution, you cannot pipe input to a powershell script. You have to update your PS file. See the snippets below.
The issue is that your script \.test.ps1 is not expecting the value.
Try this:
param(
[parameter(ValueFromPipeline)]$string
)
# Edit: added if statement
if($string){
$in = "$string"
}else{
$in = read-host -prompt "input"
}
Write-Host $in
Alternatively, you can use the magic variable $input without a param part (I don't fully understand this so can't really recommend it):
Write-Host $input
You can't pipe input to Read-Host, but there should be no need to do so.
PowerShell doesn't support input redirection (<) yet. In practice this is not a (significant) limitation because a < b can be rewritten as b | a (i.e., send output of b as input to a).
PowerShell can prompt for input for a parameter if the parameter's value is missing and it is set as a mandatory attribute. For example:
function test {
param(
[parameter(Mandatory=$true)] [String] $TheValue
)
"You entered: $TheValue"
}
If you don't provide input for the $TheValue parameter, PowerShell will prompt for it.
In addition, you can specify that a parameter accepts pipeline input. Example:
function test {
param(
[parameter(ValueFromPipeline=$true)] [String] $TheValue
)
process {
foreach ( $item in $TheValue ) {
"Input: $item"
}
}
}
So if you write
"A","B","C" | test
The function will output the following:
Input: A
Input: B
Input: C
All of this is spelled out pretty concisely in the PowerShell documentation.
Yes; in Powershell 5.1 "<" is not implemented (which sucks)
so, this won't work: tenkeyf < c:\users\marcus\work\data.num
but,
this will: type c:\users\marcus\work\data.num | tenkeyf
...
PowerShell doesn’t have a redirection mechanism, but.NET have.
you can use [System.Diagnostics.Process] implements the purpose of redirecting input.
The relevant Microsoft documents are as follows.
Process Class
This is a sample program that works perfectly on my windows 10 computer
function RunAndInput{
$pi = [System.Diagnostics.ProcessStartInfo]::new()
$pi.FileName ="powershell"
$pi.Arguments = """$PSScriptRoot\receiver.ps1"""
$pi.UseShellExecute = $false
$pi.RedirectStandardInput = $true
$pi.RedirectStandardOutput = $true
$p = [System.Diagnostics.Process]::Start($pi)
$p.StandardInput.WriteLine("abc"+ "`r`n");
$p.StandardOutput.ReadToEnd()
$p.Kill()
}
RunAndInput
# OutPut
Please enter the information: abc
Received:abc
# receiver.ps1
('Received:' + (Read-Host -Prompt 'Please enter the information'))
Hope to help you!

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!

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?

How to pass array of arguments to Powershell commandline

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"