I have a test powershell V2 script that looks like this:
function test_args()
{
Write-Host "here's arg 0: $args[0]"
Write-Host "here's arg 1: $args[1]"
}
test_args
If I call this from the powershell command prompt I get this on the screen:
here's arg[0]: [0]
here's arg[1]: [1]
Not quite what I wanted. It seems I have to copy $args[0] and $args[1] to new variables in the script before I can use them? If I do that I can access things fine.
Is there a way to access the indexed $args in my code? I've tried using curly braces around them in various ways but no luck.
I'll be moving to named parameters eventually, but the script I'm working on (not this demo one) is a straight port of a batch file.
Try this instead:
function test_args()
{
Write-Host "here's arg 0: $($args[0])"
Write-Host "here's arg 1: $($args[1])"
}
test_args foo bar
Note that it is $args and not $arg. Also when you use a PowerShell variable in a string, PowerShell only substitutes the variable's value. You can't directly use an expression like $args[0]. However, you can put the expression within a $() sub-expression group inside a double-quoted string to get PowerShell to evaluate the expression and then convert the result to a string.
Related
As far as I know, there is no way in PowerShell to execute an exe with parameters specified in a variable and direct the return of the exe into a variable. Therefore I am currently writing a small function to make this possible. But now I am stuck at the point that the parameters have to be passed individually when calling with &. (This is not necessary for all programs, but some programs cause problems if you pass all parameters as a string in a variable) Therefore I want to use a split to write the parameters passed to my function into an array. And then pass the array with the parameters in my exe call.
For this I have the following regex:
[^\s"']+|"([^"]*)"|'([^']*)'
This regex allows that single and double quotes are taken into account when passing parameters and that a text with spaces inside them is not split.
But unfortunately I don't have the slightest idea how to best escape this regex so that it doesn't cause any problems in the PowerShell script.
Here then still my function to make it a little easier to understand:
The function executes the file passed in the $Path parameter with the parameters from the $Arguments. Before the execution i try to split the $Arguments with the regex. As return of the function, you get an object with the ExitCode and the output of the executed file. Here you can see my attempt, but the quotes cause problems with the following code.
function Invoke-Process ($Path,$Arguments){
[PsObject[]] $ReturnValue = #()
$Params=$Arguments -split([regex]::escape([^\s"']+|"([^"]*)"|'([^']*)'))
$ExCode = 0
try {
$ProcOutput = & $Path $Params | out-string
}catch{
$ProcOutput = "Failed: $($_.Exception.Message)"
$ExCode = 1
}
$ReturnValue += [PsObject]#{ ExitCode = $ExCode; Output = $ProcOutput}
Return $ReturnValue
}
The function is called as follows:
$ExePath = "C:\arcconf\arcconf.exe"
$ExeParams = "getconfig 1"
$Output = Invoke-Process $ExePath $ExeParams
I hope you can understand my problem. I am also open to other ways of writing the function.
Greetings
There's nothing you need to escape - the pattern is perfectly valid.
All you need is a string literal type that won't treat the quotation marks as special. For this, I would suggest a verbatim here-string:
#'
This is a single-line string ' that " can have all sorts of verbatim quotation marks
'#
The qualifiers for a here-string is #' as the last token on the preceding line and '# as the first token on the following line (for an expandable here-string, use #" and "#).
Try running it with valid sample input:
$pattern = #'
[^\s"']+|"([^"]*)"|'([^']*)'
'#
'getconfig 1 "some string" unescaped stuff 123' |Select-String $pattern -AllMatches |ForEach-Object {$_.Matches.Value}
Which, as expected, returns:
gesture
1
"some string"
unescaped
stuff
123
As an alternative to a here-string, the best alternative is a regular verbatim string literal. The only character you need to escape is ', and the escape sequence is simply two in a row '', so your source code becomes:
$pattern = '[^\s"'']+|"([^"]*)"|''([^'']*)'''
Mathias R. Jessen's helpful answer answers your immediate question well.
Taking a step back:
there is no way in PowerShell to execute an exe with parameters specified in a variable and direct the return of the exe into a variable
No: PowerShell does support both of those things:
$output = someExternalProgram ... captures the stdout output from an external program in variable $output; use redirection 2>&1 to also capture stderr output; use >$null or 2>$null to selectively discard stdout or stderr output - see this answer for details.
someExternalProgram $someVariable ... passes the value of variable $someVariable as an argument to an external program; if $someVariable is an array (collection) of values, each element is passed as a separate argument. Instead of a variable reference, you may also use the output from a command or expression, via (...); e.g., someExternalProgram (1 + 2) passes 3 as the argument - see this answer for details.
Note: An array's elements getting passed as individual arguments only happens for external programs by default; to PowerShell-native commands, arrays are passed as a whole, as a single argument - unless you explicitly use splatting, in which case you must pass #someVariable rather than $someVariable. For external programs, #someVariable is effectively the same as $someVariable. While array-based splatting also works for PowerShell-native commands, for passing positional arguments only, the typical and more robust and complete approach is to use hashtable-based splatting, where the target parameters are explicitly identified.
A general caveat that applies whether or not you use variables or literal values as arguments: Up to at least PowerShell 7.2.x, passing empty-string arguments or arguments with embedded " chars. to external programs is broken - see this answer.
With the above in mind you can rewrite your function as follows, which obviates the need for - ultimately brittle - regex parsing:
function Invoke-Process {
# Split the arguments into the executable name / path and all
# remaining arguments.
$exe, $passThruArgs = $args
try {
# Call the executable with the pass-through arguments.
# and capture its *stdout* output.
$output = & $exe #passThruArgs
$exitCode = $LASTEXITCODE # Save the process' exit code.
} catch {
# Note: Only If $exe is empty or isn't a valid executable name or path
# does a trappable statement-terminating error occur.
# By contrast, if the executable can be called in principle, NO
# trappable error occurs if the process exit code is nonzero.
$exitCode = 127 # Use a special exit code
$output = $_.ToString() # Use the exception message
}
# Construct and output a custom object containing
# the captured stdout output and the process' exit code.
[pscustomobject] #{
ExitCode = $exitCode
Output = $output
}
}
Sample calls:
Invoke-Process cmd /c 'echo hi!'
# Equivalent, using an array variable for the pass-through arguments.
$argsForCmd = '/c', 'echo hi!'
Invoke-Process cmd #argsForCmd
Note:
Since Invoke-Process is a PowerShell command,
splatting (#argsForCmd) is necessary here in order to pass the array
elements as individual arguments, which inside the function are then reflected in the automatic $args variable variable.
The automatic $args variable is only available in simple functions, as opposed to advanced ones, which behave like cmdlets and therefore automatically support additional features, such as common parameters; to make your function and advanced one, replace the line $exe, $passThruArgs = $args at the top of the function with the following:
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string] $exe,
[Parameter(ValueFromRemainingArguments)]
[string[]] $passThruArgs
)
i tried harder to convert the case on the fly but it seems that powershell methods does not run for backrefence matches example below:
$a="string"
[regex]::replace( "$a",'(.)(.)',"$('$1'.toupper())$('$2'.tolower())" )
> string
$a -replace '(.)(.)',"$('${1}'.toupper())$('${2}'.tolower())"
> string
expected result
> StRiNg
don't know if it's possible or not
You need a script block to call the String class methods. You can effectively do what you want. For Windows PowerShell, you cannot do script block substitutions with the -replace operator. You can do that in PowerShell Core (v6+) though:
# Windows PowerShell
$a="string"
[regex]::Replace($a,'(.)(.)',{$args[0].Groups[1].Value.ToUpper()+$args[0].Groups[2].Value.ToLower()})
# PowerShell Core
$a="string"
$a -replace '(.)(.)',{$_.Groups[1].Value.ToUpper()+$_.Groups[2].Value.ToLower()}
Note that the script block substitution recognizes current MatchInfo object ($_). Using the Replace() method, the script block is passed in the MatchInfo object as an argument in the automatic variable $args unless you specify a param() block.
I'm trying to learn about script blocks at https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_script_blocks?view=powershell-6.
I want to send a script block to an event registration with a command-line option to the registration command, like this:
$Block = {
Param($option)
Write-Host "The option was $option"
if ($option==ABC) {
Write-Host "ABC was specified"
}
}
But when I test this by appending
Invoke-Command -ScriptBlock $Block -option ABC
I get the error
Invoke-Command : A parameter cannot be found that matches parameter name 'option'.
When in doubt, read the documentation:
-ArgumentList
Supplies the values of local variables in the command. The variables in the command are replaced by these values before the command is run on the remote computer. Enter the values in a comma-separated list. Values are associated with variables in the order that they are listed. The alias for ArgumentList is "Args".
The values in ArgumentList can be actual values, such as "1024", or they can be references to local variables, such as "$max".
To use local variables in a command, use the following command format:
{param($<name1>[, $<name2>]...) <command-with-local-variables>} -ArgumentList <value> -or- <local-variable>
The "param" keyword lists the local variables that are used in the command. The ArgumentList parameter supplies the values of the variables, in the order that they are listed.
The first element from the -ArgumentList array becomes the value of the first named parameter in the scriptblock, the second array element becomes the value of the second named parameter, and so on.
Basically, your statement should look like this:
Invoke-Command -ScriptBlock $Block -ArgumentList 'ABC'
Also, the syntax of the comparison operation in your scriptblock is wrong. The equality comparison operator in PowerShell is -eq, not ==.
Remember that you can always check the documentation: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/invoke-command?view=powershell-6
$Block = {Param($option)
Write-Host "The option was $option"
if ($option -eq "ABC") // -eq instead of ==, string in quotations
{Write-Host "ABC was specified"}
}
Invoke-Command -ScriptBlock $Block -ArgumentList "ABC"
If there are more arguments you can list them in order. Using script blocks isn't something you want to do very often. It's better to create a function (https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions?view=powershell-6)
You can use the call operator on scriptblocks too, and just provide the arguments after a space or -option:
& $block abc
The option was abc
ABC was specified
And you can later assign the scriptblock to a function. That's what a function is.
$function:myfunc = $block
There are already some questions about how to call one PS script with arguments from another like that one
Powershell: how to invoke a second script with arguments from a script
But I'm stuck in case I have first script with multiple positional parameters.
testpar2.ps1 calls testpars.ps1
#$arglist=(some call to database to return argument_string)
$arglist="first_argument second_argument third_argument"
$cmd=".\testpars.ps1"
& $cmd $arglist
$arglist variable should be populated with string from database. This string contains arguments for testpar.ps1.
testpars.ps1 looks like
echo argument1 is $args[0]
echo argument2 is $args[1]
echo arugment3 is $args[3]
# some_command_call $arg[0] $arg[1] $arg[2]
This arguments should be used in testpars.ps1 in some way, like to path them to some command.
But when i run testpars2.ps1 i got
argument1 is first_argument second_argument third argument
argument2 is
arugment3 is
It thinks that it is one argument, not a list of them.
As you can see, when you pass a string to a function, PowerShell treats that as a single value. This is usually good, because it avoids the problem in CMD of having to constantly quote and unquote strings. To get separate values, you need to split() the string into an array.
$arglistArray = $arglist.split()
Now you have an array of three strings, but they're still all passed as one parameter. PowerShell has an idea known as splatting to pass an array of values as multiple arguments. To use splatting, replace the $ with # in the argument list.
& $cmd #arglistArray
For more information on splatting, type Get-Help about_Splatting
I have a simple powershell script. It takes two parameters. Both are part of methods (Get-ResourcePool) which take string arguments.
If I call the function definition in the Powershell script, like so:
functName CI *FMS
That works fine.
The function call in Powershell is (and like so because this script will be called from outside):
FuncName $($args[0], $args[1])
I try to call this from Powershell editor, where I have the snapins I need all installed, like so:
C:\Script.ps1 A "*s"
Where scrpt is the name of my .ps1 file. There is one function. This, however, fails with an error that the argument is null or empty.
Any ideas why?
EDIT:
The function signature is:
function RevertToSnapshot($otherarg, $wildcard)
I use $wildcard here:
$SearchString = [System.String]::Concat("*",
$VMWildcard)
Name $SearchString (Name is a parameter to get-vm in powercli).
This calling style:
FuncName $($args[0], $args[1])
Will result in just one argument passed to FuncName - a single array with two elements. You probably want:
FuncName $args[0] $args[1]
In general, with PowerShell you call cmdlets, functions, aliases using space separated arguments and no parens. Calling .NET methods is the one exception to this rule where you have to use parens and commas to separate arguments e.g:
[string]::Concat('ab','c')