Calling powershell script with many args from cmd - powershell

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')

Related

Call Windows executable from PowerShell script, passing all arguments

I've looked all over, and I can't find the answer. Most things I've seen have answers that go around in circles and talk about all sorts of complicated things with no direct answer.
My question is simple. I have the following Windows batch file foo.bat:
#ECHO OFF
bar.exe %*
If I call foo.bat foobar 123 from the command line, it invokes bar.exe foobar 123. This works with no command-line arguments. This works with multiple command-line arguments.
What is the equivalent PowerShell script that does basically the same thing: invoke another executable, passing all CLI parameters the user provided?
I wouldn't expect this would be difficult, but I sure can't find any straightforward answer.
The PowerShell equivalent of your foo.bat file is a foo.ps1 file with the following content:
# Passes all arguments received by this script to bar.exe
bar.exe #args
PowerShell exposes all (unbound) positional arguments[1] as an array stored in the automatic $args variable.
By prefixing $args with # instead of $, argument splatting is employed, which means that the elements of the $args array are passed as individual arguments to bar.exe
Note: This isn't strictly necessary when calling external programs (such as bar.exe) - $args would work there too - but is more versatile in that it can also pass named arguments correctly through to other PowerShell commands, which typically have declared parameters that can be bound by name (e.g., -Path C:\temp to bind value C:\temp to declared parameter -Path)
As for working with $args in general:
$args.Count tells you how many (unbound) positional arguments were passed,
$args[0] returns the first such argument, $args[1] the second, and so on.
However, it is usually preferable to formally declare parameters in PowerShell scripts and functions, which can then also be bound by name (e.g., -FooParam foo instead of just foo). See this answer for more information.
[1] If your script doesn't formally declare any parameters (via a param(...) block - see about_Scripts and the linked answer), all arguments are by definition unbound (i.e. not mapped to a declared parameter) and positional (not prefixed by a target parameter name). However, if your script does declare parameters, $args only contains those arguments, if any, that were passed in addition to those binding to declared parameters. If your script is an advanced script (or function), $args isn't supported at all, because passing unbound arguments is then categorically prevented. See this answer for more information.

start powershell wont accept variable as parameter

issue
the called powershell script will accept parameters but not all of them:
Current Set-Up and code:
I have a common folder where two .ps1 scripts are located:
DoWork.ps1
Workmanager.ps1
Workmanager.ps1 calls the Dowork.ps1:
$targetPath="M:\target"
echo "target path: $targetPath"
start powershell {.\DoWork.ps1 -target $targetPath -tempdrive D:\}
output (as expected):
target path: M:\target
DoWork.ps1 contains some start code:
param
(
[string]$tempdrive,
[string]$target,
[int] $threads = 8,
[int] $queuelength = -1
)
echo "variables:"
echo "temp drive: $tempdrive"
echo "target path: $target"
Unexpectedly, the $target is not beeing assigned. Previously I had the variable named $targetpath, which did not work either.
variables:
temp drive: D:\
target path:
Findings
It appears that the issue relies in Workmanager.ps1. Spcifying the parameter as fixed string rather than as variable will load the parameter. Any solution for this?
start powershell {.\DoWork.ps1 -target "foo" -tempdrive D:\}
When you use a ScriptBlock as an argument to powershell.exe, variables aren't going to be evaluated until after the new session starts. $targetPath has not been set in the child PowerShell process called by Workmanager.ps1 and so it has no value. This is actually an expected behavior of a ScriptBlock in general and behaves this way in other contexts too.
The solution is mentioned in the help text for powershell -?:
[-Command { - | <script-block> [-args <arg-array>] <========== THIS GUY
| <string> [<CommandParameters>] } ]
You must provide the -args parameter which will be passed to the ScriptBlock on execution (separate multiple arguments with a ,). Passed arguments are passed positionally, and must be referenced as though you were processing the arguments to a function manually using the $args array. For example:
$name = 'Bender'
& powershell { Write-Output "Hello, $($args[0])" } -args $name
However, especially with more complicated ScriptBlock bodies, having to remember which index of $args[i] contains the value you want at a given time is a pain in the butt. Luckily, we can use a little trick with defining parameters within the ScriptBlock to help:
$name = 'Bender'
& powershell { param($name) Write-Output "Hello, $name" } -args $name
This will print Hello, Bender as expected.
Some additional pointers:
The ScriptBlock can be multiline as though you were defining a function. way. The examples above are single line due to their simplicity.
A ScriptBlock is just an unnamed function, which is why defining parameters and referencing arguments within one works the same way.
To exemplify this behavior outside of powershell.exe -Command, Invoke-Command requires you to pass variables to its ScriptBlock in a similar fashion. Note however that answer uses an already-defined function body as the ScriptBlock (which is totally valid to do)
You don't need to use Start-Process here (start is its alias), at least as demonstrated in your example. You can simply use the call operator & unless you need to do something more complex than "run the program and wait for it to finish". See this answer of mine for more information.
If you opt to pass a string to powershell.exe instead, you don't need to provide arguments and your variables will get rendered in the current PowerShell process. However, so will any other unescaped variables that might be intended to set within the child process, so be careful with this approach. Personally, I prefer using ScriptBlock regardless, and just deal with the extra parameter definition and arguments.
Using the call & operator is optional when you are not executing a path rendered as a string. It can be omitted in the examples above, but is more useful like so:
& "C:\The\Program Path\Contains\spaces.exe"
& $programPathAsAVariable

What is the simplest way to make Alias in powershell?

I was wondering if there's any simple way to make aliases for powershell like cmd.
For example: In cmd, doskey art=php artisan $* where $* is optional. Currently, I'm using the following alias in powershell.
function runArtisanCommand
{
param(
[Parameter(Mandatory=$false, Position = 0, ValueFromRemainingArguments = $true)]
$command
)
php artisan $command
}
Set-Alias art runArtisanCommand
This works somewhat but don't take flags. For example: I can't write art -h or art route:list -c. In art -h command, it prints the output of php artisan and don't read flag at all but in art route:list -c command, it errors out with.
runArtisanCommand : Missing an argument for parameter 'command'. Specify a parameter of type 'System.Object' and try again.
At line:1 char:16
+ art route:list -c
+ ~~
+ CategoryInfo : InvalidArgument: (:) [runArtisanCommand], ParameterBindingException
+ FullyQualifiedErrorId : MissingArgument,runArtisanCommand
I would love a simpler solution than this. Thanks in advance.
The simplest and most convenient way to pass unknown arguments through is by spatting
the automatic $args array - as #args - in a simple function or script (one that neither uses a [CmdletBinding()] nor [Parameter()] attributes):
# Note: #args rather than $args makes the function work with named
# arguments for PowerShell commands too - see explanation below.
function runArtisanCommand { php artisan #args }
# As in your question: Define alias 'art' for the function
# Note: Of course, you could directly name your *function* 'art'.
# If you do want the function to have a longer name, consider one
# that adheres to PowerShell's Verb-Noun naming convention, such as
# 'Invoke-ArtisanCommand'.
Set-Alias art runArtisanCommand
As an aside: Since the target executable, php, is neither quoted nor specified based on a variable or expression, it can be invoked as-is; otherwise, you would need &, the call operator - see this answer for background information.
As for what you tried:
The problem was that use of -c as a pass-through argument only works if you precede it with --:
# OK, thanks to '--'
art -- route:list -c
-- tells PowerShell to treat all remaining arguments as unnamed (positional) arguments, instead of trying to interpret tokens such as -c as parameter names.
Without --, -c is interpreted as referring to your -command parameter (the parameter you declared as $command with ValueFromRemainingArguments = $true), given that PowerShell allows you to specify name prefixes in lieu of full parameter names, as long as the given prefix is unambiguous.
Because a parameter of any type other than [switch] requires an associated argument, -c (aka -command) failed with an error message to that effect.
You could have avoided the collision by naming your parameter so that it doesn't collide with any pass-through parameters, such as by naming it -_args (with parameter variable $_args):
function runArtisanCommand
{
param(
# Note: `Mandatory = $false` and `Position = 0` are *implied*.
[Parameter(ValueFromRemainingArguments)]
$_args
)
php artisan #_args
}
However, given that use of a [Parameter()] attribute implicitly makes your function an advanced function, it invariably also accepts common parameters, such as -ErrorAction, -OutVariable, -Verbose... - all of which can be passed by unambiguous prefix / short alias too; e.g., -outv for -OutVariable, or alias -ea for ErrorAction; collisions with them cannot be avoided.
Therefore, intended pass-through arguments such as -e still wouldn't work:
# FAILS, because -e ambiguously matches common parameters -ErrorAction
# and -ErrorVariable.
PS> art router:list -e
Parameter cannot be processed because the parameter name 'e' is ambiguous.
Possible matches include: -ErrorAction -ErrorVariable.
Again, -- is needed:
# OK, thanks to '--'
art -- router:list -e
Summary:
Especially for functions wrapping calls to external programs, such as php.exe, using a simple function with #args, as shown at the top, is not only simpler, but also more robust.
For functions wrapping PowerShell commands (with explicitly declared parameters):
a simple function with #args works too,
but if you also want support for tab-completion and showing a syntax diagram with the supported parameters, by passing -?, or via Get-Help, consider defining an (invariably advanced) proxy (wrapper) function via the PowerShell SDK - see below.
Optional background information: Pass-through arguments in PowerShell
As Mathias R. Jessen points out, the simplest way to pass (undeclared) arguments passed to a function or script through to another command is to use the automatic $args variable, which is an automatically populated array of all the arguments passed to a simple function or script (one that isn't advanced, through use of the [CmdletBinding()] and/or [Parameter()] attributes).
As for why #args (splatting) rather than $args should be used:
Using $args as-is in your wrapper function only works for passing positional arguments through (those not prefixed by the parameter name; e.g., *.txt), as opposed to named arguments (e.g., -Path *.txt).
If the ultimate target command is an external program (such as php.exe in this case), this isn't a problem, because PowerShell of necessity then treats all arguments as positional arguments (it cannot know the target program's syntax).
However, if a PowerShell command (with formally declared parameters) is ultimately called, only splatting the $args array - which syntactically means us of #args instead - supports passing named arguments through.[1]
Therefore, as a matter of habit, I suggest always using #args in simple wrapper functions, which equally works with external programs.[2]
To give an example with a simple wrapper function for Get-ChildItem:
# Simple wrapper function for Get-ChildItem that lists recursively
# and by relative path only.
function dirTree {
# Use #args to make sure that named arguments are properly passed through.
Get-ChildItem -Recurse -Name #args
}
# Invoke it with a *named* argument passed through to Get-ChildItem
# If $args rather than #args were used inside the function, this call would fail.
dirTree -Filter *.txt
Using a proxy function for more sophisticated pass-through processing:
The use of #args is convenient, but comes at the expense of not supporting the following:
tab-completion, given that tab-completion only works with formally declared parameters (typically with a param(...) block).
showing a syntax diagram with the supported parameters, by passing -?, or via Get-Help
To overcome these limitations, the parameter declarations of the ultimate target command must be duplicated in the (then advanced) wrapper function; while that is cumbersome, PowerShell can automate the process by scaffolding a so-called proxy (wrapper) function via the PowerShell SDK - see this answer.
Note:
With respect to common parameters such as -ErrorAction, it is the proxy function itself that (automatically) processes them, but that shouldn't make a difference to the caller.
Scaffolding a proxy function only works with PowerShell commands, given that PowerShell has no knowledge of the syntax of external programs.
However, you can manually duplicate the parameter declarations of the external target program.
[1] Note that the automatic $args array has built-in magic to support this; passing named arguments through with splatting is not supported with a custom array and requires use of a hash table instead, as discussed in the help topic about splatting linked to above.
[2] In fact, only #args also supports the correct interpretation of --%, the stop-parsing symbol.

What is the Powershell equivalent for bash $*?

In other words how can I get the command line of the script itself?
So, I know about $PSBoundParameters, but it is not the same. I just want to get the string containing the passed in parameters as is.
How do I do it?
See get-help about_Automatic_Variables.
$Args
Contains an array of the undeclared parameters and/or parameter
values that are passed to a function, script, or script block.
When you create a function, you can declare the parameters by using the
param keyword or by adding a comma-separated list of parameters in
parentheses after the function name.
In an event action, the $Args variable contains objects that represent
the event arguments of the event that is being processed. This variable
is populated only within the Action block of an event registration
command. The value of this variable can also be found in the SourceArgs
property of the PSEventArgs object (System.Management.Automation.PSEventArgs)
that Get-Event returns.
Example:
test.ps1
param (
)
Write-Output "Args:"
$args
Output:
PS L:\test> .\test.ps1 foo bar, 'this is extra'
Args:
foo
bar
this is extra
$MyInvocation.Line
Read about_Automatic_Variables:
$MyInvocation
Contains an information about the current command, such as the name,
parameters, parameter values, and information about how the command was
started, called, or "invoked," such as the name of the script that called
the current command.
$MyInvocation is populated only for scripts, function, and script blocks.
just want to get the string containing the passed in parameters as is.
You're looking for a powershell equivalent of "$#", not $*.
And the other answers in the thread are not equivalent. The best quick way I have found for this is:
$args | join-string -sep ' '
which can in turn be used for any string array you may have on hand, not just the $args array.

Powershell script from another script with multiple positional arguments

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