How can I pass-through parameters in a powershell function? - function

I have a function that looks something like this:
function global:Test-Multi {
Param([string]$Suite)
& perl -S "$Suite\runall.pl" -procs:$env:NUMBER_OF_PROCESSORS
}
I would like to allow the user to specify more parameters to Test-Multi and pass them directly to the underlying legacy perl script.
Does powershell provide a mechanism to allow additional variadic behavior for this purpose?

After seeing your comment, option 3 sounds like exactly what you want.
You have a few options:
Use $args (credit to hjpotter92's answer)
Explicitly define your additional parameters, then parse them all in your function to add them to your perl call.
Use a single parameter with the ValueFromRemainingArguments argument, e.g.
function global:Test-Multi {
Param(
[string]$Suite,
[parameter(ValueFromRemainingArguments = $true)]
[string[]]$Passthrough
)
& perl -S "$Suite\runall.pl" -procs:$env:NUMBER_OF_PROCESSORS #Passthrough
}

$args is not going to pass through arguments correctly. if you want the arguments to remain as separate arguments, you should use #args instead.

I'm not sure about what you wish to achieve, but the arguments passed to a function are accessible in the $args variable available inside the function.

Related

How do I get a variable from a module run space in PowerShell?

There is a module that has an "initialize" function that sets a variable that gets used in other scripts/functions in the module to validate that the initialize function was run. Something like
Start-InitializeThing
Connect to the API
$Script:SNOWinit = $true
Then in another script/function it will check:
if ($Script:SNOWinit -eq $true) { Do the thing!}
Is there a way to grab that $Script:SNOWinit in the same PowerShell window, but not the same module?
I want to run the same check but for a different function that is not in the module.
Can I do this, can I "dig" into like the modules runspace and check that variable. I don't have the means to edit the functions in the module so I cant change what type of variable is set once the initialize script has run.
Assuming that the module of interest is named foo and that it has already been imported (loaded):
. (Get-Module foo) { $SNOWinit }
If you want to import the module on demand:
. (Import-Module -PassThru foo) { $SNOWinit }
The above returns the value of the $SNOWinit variable defined in the root scope of module foo.
See this blog post for background information.
Note that it is generally not advisable to use this technique, because it violates the intended encapsulation that modules provide. In the case at hand, $SNOWinit, as a non-public module variable, should be considered an implementation detail, which is why you shouldn't rely on its presence in production code.
From the bible, WPiA. More mysterious uses for the call operator.
# get a variable in module scope
$m = get-module counter
& $m Get-Variable count
& $m Set-Variable count 33

PowerShell pass all parameters received in function, and handle parameters with spaces

I am a novice with PowerShell.
In Msys2 (or Lnux), I have defined a function npp
npp ()
{
${NPP_PATH} "$#"
}
such that if I call from the command prompt npp it launches Notepad++ (as defined in ${NPP_PATH}).
If I call npp "mydir/stage 1/a.txt" it opens that file for editing.
Generally speaking, it allows:
Any number of parameters.
Parameters containing spaces, if suitably escaped.
What would be the equivalent in PowerShell?
I guess in PS I should also go for a function to obtain a similar behavior.
So far, I could receive an undefined number of parameters, and use them in a foreach loop, see code below.
But I could not find the equivalent of the simple "$#" to pass all parameters as they are received.
Moreover, if I use quotes in one of the arguments, they are removed, so it will probably have problems with file paths including blanks.
function multi_params {
param(
[Parameter(
ValueFromRemainingArguments=$true
)][string[]]
$listArgs
)
$count = 0
foreach($listArg in $listArgs) {
'$listArgs[{0}]: {1}' -f $count, $listArg
$count++
}
}
Assuming that NPP_PATH is an environment variable, the equivalent PowerShell function is:
function npp {
& $env:NPP_PATH $args
}
If NPP_PATH is the name of a regular PowerShell variable, use & $NPP_PATH $args.
& is the call operator, which is needed for syntactic reasons whenever you want to invoke a command whose name/path is specified in quotes and/or via a variable.
In simple functions (as opposed to advanced functions) such as the above (use of neither [CmdletBinding()] nor [Parameter()] attributes), you can use the automatic $args variable to pass any arguments through to another command.
If the target command is not an external program, such as here, but a PowerShell command, use the form #args to ensure that all arguments - including those preceded by their parameter names - are properly passed through - see about_Splatting.
Note that the form #args works with external programs too, where it is generally equivalent to $args (the only difference is that only #args recognizes and removes --%, the stop-parsing token)
Note that passing arguments with embedded " chars. and empty arguments to external programs is still broken as of PowerShell v7.0 - see this answer.
Passing arguments through in simple vs. advanced functions (scripts):
In simple functions only, $args contains all arguments that did not bind to declared parameters, if any, on invocation.
If your simple function doesn't declare any parameters, as in the example above, $args contains all arguments passed on invocation.
If your simple function does declare parameters (typically via param(...)), $args contains only those arguments that didn't bind to declared parameters; in short: it collects any arguments your function did not declare parameters for.
Therefore, $args is a simple mechanism for collecting arguments not declared or known in advance, either to be used in the function itself - notably if declaring parameters isn't worth the effort - or to pass those arguments through to another command.
To pass arguments that comprise named arguments (e.g., -Path foo instead of just foo) through to another PowerShell command, splatting is needed, i.e. the form #args.
Note that while $args is technically a regular PowerShell array ([object[]]), it also has built-in magic to support passing named arguments through; a custom array can not be used for this, and the hash-table form of splatting is then required - see about_Splatting
In advanced functions, $args is not available, because advanced functions by definition only accept arguments for which parameters have been declared.
To accept extra, positional-only arguments, you must define a catch-all ValueFromRemainingArguments parameter, as shown in the question, which collects such arguments in an array-like[1] data structure by default.
To also support named pass-through arguments, you have two basic option:
If you know the set of potential pass-through parameters, declare them as part of your own function.
You can then use splatting with the $PSBoundParameters dictionary (hash table) - see below - to pass named arguments through, possibly after removing arguments meant for your function itself from the dictionary.
This technique is used when writing proxy (wrapper) functions for existing commands; the PowerShell SDK makes duplicating the pass-through parameters easier by allowing you to scaffold a proxy function based on an existing command - see this answer.
Otherwise, there is only a suboptimal solution where you emulate PowerShell's own parameter parsing to parse the positional arguments into parameter-name/value pairs - see this answer.
The automatic $PSBoundParameters variable is a dictionary that is available in both simple and advanced functions:
$PSBoundParameters applies only if your function declares parameters, and contains entries only for those among the declared parameters to which arguments were actually bound (passed) on invocation; the dictionary keys are the parameter names, albeit without the initial -.
Note that parameters bound by a default value are not included - see this GitHub issue for a discussion.
Again, note that in advanced functions you can only pass a given argument if a parameter was declared for it, so any argument passed in a given invocation is by definition reflected in $PSBoundParameters.
Because it is a dictionary (hash table), it can be used with hash-table based splatting - #PSBoundParameters - to pass named arguments through to other PowerShell commands and, since it is mutable, you have the option of adding or removing named arguments (such as the ones intended for your function itself).
[1] That type is [System.Collections.Generic.List[object]]; however, you can specify a collection type explicitly, such as [object[]] to get a regular PowerShell array.

Can't seem to do a Function.Invoke with multiple parameters in powershell?

I'm trying to pass a function to a method and then pass parameters to the method I passed when calling it, but if I pass more than one parameter then the method fails with an error:
function debugMeStuffs($someBlah, $somePoo) {
Write-Host $someBlah
Write-Host $somePoo
}
function invokeOnHosts ([scriptblock]$funcToCall, $param1, $param2, $startRange, $endRange) {
#Param($funcToCall)
$i = $startRange
for($i = [int]$startRange; $i -le $endRange; $i++) {
# HOW DO I MAKE THIS WORK WITH MULTIPLE PARAMETERS?!?!?!?
$funcToCall.Invoke('blah' 'poo')
}
}
invokeOnHosts $function:debugMeStuffs "param1" "param2" 4 7
Things I've tried:
$funcToCall("blah" "poo")
$funcToCall('blah' 'poo')
$funcToCall.Invoke("blah" "poo")
$funcToCall.Invoke('blah' 'poo')
$funcToCall 'blah' 'poo'
$funcToCall.Invoke 'blah' 'poo'
$funcToCall "blah" "poo"
$funcToCall.Invoke "blah" "poo"
None of the above seem to work. Is there something else I need to do to make this work?
.Invoke() is a .NET method, so the usual method-call syntax applies: you need
parentheses - (...) - around the list of arguments
you must separate the arguments with ,
$funcToCall.Invoke('blah', 'poo')
This contrasts with PowerShell's own syntax for calling cmdlets and functions, which is shell-like[1]:
no (...) around the argument list
arguments must be separated with spaces.
& $funcToCall blah poo # equivalent of the method call above.
A command such as the above is parsed in argument mode, which is why quoting the arguments in this simple case is optional.
Note the need for &, PowerShell's call operator, which is needed to execute the script block stored in $funcToCall; this is generally necessary for invoking a command stored in a variable, and also for command names / paths that are quoted.
Given that it's easy to get confused between PowerShell's command syntax and .NET's method syntax, it's best to stick with PowerShell-native features[2], if possible.
That said, being able to call methods on .NET types directly is a wonderful extensibility option.
To help avoid accidental use of method syntax when calling PowerShell commands, you can use Set-StrictMode -Version 2 or higher, but note that that entails additional strictness checks.
[1] PowerShell is, after all, a shell - but it is also a full-featured scripting language that offers near-unlimited access to the .NET framework.
Reconciling these two personalities is a difficult balancing act, and the shell-like command-invocation syntax is a frequent problem for newcomers with a programming background, given that the rest of the language looks like a traditional programming language and that calling methods on .NET types does use the traditional syntax.
[2] This means preferring PowerShell's cmdlets, functions, and operators to use of the underlying .NET types' methods; doing so also usually provides rewards you with operating at a higher level of abstraction.

Do powershell parameters need to be at the front of the script?

I'm trying to have a script with both executable code and a function, like the following:
function CopyFiles {
Param( ... )
...
}
// Parameter for the script
param ( ... )
// Executable code
However, I run into the following error: "The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property"
When I list my function at the end of the file, it says that the function name is undefined. How do I call a powershell function from executable code within the same script?
The correct order is:
1.Script parameters
# Parameter for the script
param([string]$foo)
2.Function definitons
function CopyFiles {
Param([string]$bar)
...
}
3.Script code
# Executable code
CopyFiles $foo $bar
Why would you want it any other way?
Parameters go first always. I had a similar issue at one point in time with providing parameter input to a script. Your script should go:
param ( . . . )
# functions
# script body
For some reason, the PowerShell parsing engine doesn't appreciate the param keyword not being on the first line of a script, not counting comment lines. You can also do this:
param (
# params must be at the top of the file
)
You can also check to see if your parameters have been declared, or if they have the input you want, using Get-Variable. One other thing; if you want to cast data to a certain type, such as System.Boolean, I would do it AFTER the param block, and BEFORE functions. If you type-cast something to System.Boolean in the parameter declaration, then you'll have errors if people running your script don't submit the input argument in a Boolean value, which is much harder than using the .NET System.Convert static method to convert the value afterwards, and checking to see what it evaluated to.

Adding help to the user defined functions in TCL

how can i add some kind of help to the user defined functions in TCL
Supposing if i have a function called runtest {ip_address test_time},
How to describe what the test or procedure is about in the TCL_shell?
How can i specify the information to the user, if he types in function_name --help in the TCL shell the user should be able to know what the function does and what exactly are the parameters.
how can i do this?
While it is true that it is not part of the language, it is fairly easy to implement something that adds this functionality to pre-existing functions. The only caveat being that the function then cannot take the string --help as a valid argument since that argument will trigger the feature.
Here's one simple implementation:
# Lets call the feature "document". As in, add documentation:
proc document {procname text} {
rename $procname __$procname
proc $procname args [string map [list %TEXT% $text %PROC% $procname] {
if {$args == "--help"} {
puts {%TEXT%}
} else {
set script [linsert $args 0 __%PROC%]
return [uplevel 1 $script]
}
}]
}
What this does is to override the function (by renaming and then declaring another function of the same name) and see if the function is called with the argument --help. If it is it prints the documentation otherwise it executes the original function. Just be careful not to call this twice on the same function (it can be modified for it to work though).
So you can do things like:
proc foo {} {puts 2}
document foo {Prints the number 2.}
Now if you call:
foo --help
and it would output:
Prints the number 2.
You don't have to touch the existing procedures:
proc help {procname} {
puts $::helptext($procname)
}
proc addhelp {procname text} {
set ::helptext($procname) $text
}
addhelp foo "this is the help text for procedure foo"
help foo
Without redefining the proc command, you cannot. Ie, that functionality is not built into the language, but would be possible to add if you so wished.
I will note that adding the capability yourself, while possible, is probably beyond the difficulty where it's worth doing, especially for someone that isn't intimately familiar with the language. That being said, you can check the tclers wiki for a preexisting implementation.
I tend to prefer to put help in a separate file (e.g., as HTML) which lets me browse it in another window. There are so many ways you can do that while still keeping the docs with the code, such as through doxygen.
There are also a number of ways of doing interactive documentation, several of which are described in the Tcler's Wiki; it would seem to me that some of the techniques mentioned on that page (together with #slebetman's answer) would give you what you wanted. (I think it would be easier to have a separate help command as that would avoid having the help syntax interfere with the command, but that's your call.)