How can I assert parameter binding will succeed without side effects? - powershell

Suppose I have a function into which a dependency and some parameters are injected like the following:
function Invoke-ACommandLaterOn
{
param
(
# ...
[string] $CommandName,
[object] $PipelineParams,
[object[]] $PositionalParams,
[hashtable]$NamedParams
# ...
)
Assert-ParameterBinding #PSBoundParameters
# ...
# Some complicated long-running call tree that eventually invokes
# something like
# $PipelineParams | & $CommandName #PositionalParams #NamedParams
# ...
}
I would like to immediately assert that binding of the parameters to $CommandName succeeds. That's what Assert-ParameterBinding is meant to do. I'm not exactly sure how to implement Assert-ParameterBinding, however.
Of course I could try to invoke $CommandName immediately, but in this case doing so has side-effects that cannot occur until a bunch of other long-running things are completed first.
How can I assert parameter binding to a function will succeed without invoking the function?

What if you did something like this (inside the Assert- function):
$cmd = Get-Command $CommandName
$meta = [System.Management.Automation.CommandMetadata]::new($cmd)
$proxy = [System.Management.Automation.ProxyCommand]::Create($meta)
$code = $proxy -ireplace '(?sm)(?:begin|process|end)\s*\{.*','begin{}process{}end{}'
$sb = [scriptblock]::Create($code)
$PipeLineParams | & $sb #PositionalParams #NamedParams
I'm actually not sure if it will work with the positional params or with splatting two different sets, off the top of my head (and I didn't do much testing).
Explanation
I had a few thoughts. For one, parameter binding can be very complex. And in the case of a pipeline call, binding happens differently as different blocks are hit.
So it's probably a good idea to let PowerShell handle this, by essentially recreating the same function but with a body that does nothing.
So I went with the built in way to generate a proxy function since it takes care of all that messy work, then brutally replaced the body so that it doesn't actually call the original.
Ideally then, you'll be making a call that follows all the regular parameter binding process but in the end accomplishes nothing.
Wrapping that in a try/catch or otherwise testing for errors should be a pretty good test of whether this was a successful call or not.
This even handles dynamic parameters.
There are probably edge cases where this won't quite work, but I think they will be rare.
Additionally, ValidateScript attributes and dynamic parameters could conceivably create side effects.

Related

ArrayList .Add vs .AddRange vis-a-vis the Pipeline

Given a properly defined variable
$test = New-Object System.Collections.ArrayList
.Add pollutes the pipeline with the count of items in the array, while .AddRange does not.
$test.Add('Single') will dump the count to the console. $test.AddRange(#('Single2')) will be clean with no extra effort. Why the different behavior? Is it just an oversight, or is there some intentional behavior I am not understanding?
Given that .AddRange requires coercing to an array when not using a variable (that is already an array) I am tending towards using [void]$variable.Add('String') when I know I need to only add one item, and [void]$test.AddRange($variable) when I am adding an array to an array, even when $variable only contains, or could only contain, a single item. The [void] here isn't required, but I wonder if it's just best practice to have it, depending of course on the answer above. Or am I missing something there too?
Why the different behavior? Is it just an oversight, or is there some intentional behavior I am not understanding?
Because many years ago, someone decided that's how ArrayList should behave!
Add() returns the index at which the argument was inserted into the list, which may indeed be useful and makes sense.
With AddRange() on the other hand, it's not immediately clear why it should return anything, and if yes, what? The index of the first item in the input arguments? The last? Or should it return a variable-sized array with all the insert indices? That would be awkward! So whoever implemented ArrayList decided not to return anything at all.
In C# or VB.NET, for which ArrayList was initially designed, "polluting the pipeline" doesn't really exist as a concept, the runtime would simply omit copying the return value back to the caller if someone invokes .Add() without assigning to a variable.
The [void] here isn't required, but I wonder if it's just best practice to have it, depending of course on the answer above. Or am I missing something there too?
No, it's completely unnecessary. AddRange() is not magically one day gonna change to output anything.
If you don't ever need to know the insert index, use a [System.Collections.Generic.List[psobject]] instead:
$list = [System.Collections.Generic.List[psobject]]::new()
# this won't return anything, no need for `[void]`
$list.Add(123)
If for some reason you must use an ArrayList, you can "silence" it by overriding the Add() method:
function New-SilentArrayList {
# Create a new ArrayList
$newList = [System.Collections.ArrayList]::new()
# Create a new `Add()` method, then return the list
$newAdd = #{
InputObject = $newList
MemberType = 'ScriptMethod'
Name = 'Add'
Value = {param($obj) $this.AddRange(#($obj))}
}
Write-Output $(
Add-Member #newAdd -Force -PassThru
) -NoEnumerate
}
Now your ArrayList's Add() will never make a peep again!
PS C:\> $list = New-SilentArrayList
PS C:\> $list.Add(123)
PS C:\> $list
123
Apparently I didn't quiet understand where you where heading to.
"Add pollutes the pipeline", at a second thought is a correct statement but .Net methods like $variable.Add('String') do not use the PowerShell pipeline by itself (until the moment you output the array using the Write-Output command which is the default command if you do not assign it to a variable).
The Write-Output cmdlet is typically used in scripts to display
strings and other objects on the console. However, because the default
behavior is to display the objects at the end of a pipeline, it is
generally not necessary to use the cmdlet.
The point is that Add method of ArrayList returns a [Int32] "The ArrayList index at which the value has been added" and the AddRange doesn't return anything. Meaning if you don't assign the results to something else (which includes $Null = $test.Add('Single')) it will indeed be output to the PowerShell Pipeline.
Instead you might also consider to use the Add method of the List class which also doesn't return anything, see also: ArrayList vs List<> in C#.
But in general, I recommend to use native PowerShell commands that do use the Pipeline
(I can't give you a good example as it is not clear what output you expect but I noticed another question you removed and from that question, I presume that this Why should I avoid using the increase assignment operator (+=) to create a collection answer might help you further)

Allowing extra parameters on a Powershell advanced function

Environmental note: I'm currently targetting PowerShell 5.1 because 6 has unrelated limitations I can't work around yet.
In the Powershell module I'm writing, there is one main function that's sort of a conglomeration of a bunch of the smaller functions. The main function has a superset of the smaller function's parameters. The idea is that calling the main function will call each smaller function with the necessary parameters specified on the main. So for example:
function Main { [CmdletBinding()] param($A,$B,$C,$D)
Sub1 -A $A -B $B
Sub2 -C $C -D $D
}
function Sub1 { [CmdletBinding()] param($A,$B)
"$A $B"
}
function Sub2 { [CmdletBinding()] param($C,$D)
"$C $D"
}
Explicitly specifying the sub-function parameters is both tedious and error prone particularly with things like [switch] parameters. So I wanted to use splatting to make things easier. Instead of specifying each parameter on the sub-function, I'll just splat $PSBoundParameters from the parent onto each sub-function like this:
function Main { [CmdletBinding()] param($A,$B,$C,$D)
Sub1 #PSBoundParameters
Sub2 #PSBoundParameters
}
The immediate problem with doing this is that the sub-functions then start throwing an error for any parameter they don't have defined such as, "Sub1 : A parameter cannot be found that matches parameter name 'C'." If I remove the [CmdletBinding()] declaration, things work but I lose all the benefits of those subs being advanced functions.
So my current workaround is to add and additional parameter on each sub-function that uses the ValueFromRemainingArguments parameter attribute like this:
function Sub1 { [CmdletBinding()]
param($A,$B,[Parameter(ValueFromRemainingArguments)]$Extra)
"$A $B"
}
function Sub2 { [CmdletBinding()]
param($C,$D,[Parameter(ValueFromRemainingArguments)]$Extra)
"$C $D"
}
Technically, this works well enough. The sub-functions get their specific params and the extras just get ignored. If I was writing this just for me, I'd move on with my life and be done with it.
But for a module intended for public consumption, there's an annoyance factor with that -Extra parameter being there. Primarily, it shows up in Get-Help output which means I have to document it even if just to say, "Ignore this."
Is there an extra step I can take to make that extra parameter effectively invisible to end users? Or am I going about this all wrong and there's a better way to allow for extra parameters on an advanced function?
My usual approach is to export only "wrapper" functions that call internal (i.e., not user-facing) functions in the module.

How do I properly pass arguments to a scriptblock using powershell runspaces?

While using runspaces I would like to pass predictable arguments to my scriptblock from outside. In order to do this without utilizing $args (to avoid making the code less readable), I am storing my argument in a variable called $var and adding the variable to the InitialSessionState of the RunspacePool. To do this I am using the Add method of System.Management.Automation.Runspaces.InitialSessionState.Create().Variables
This works for my purposes, but I also noticed the PowerShell.AddParameter method of [PowerShell]::Create()which would add my parameter to each powershell process as it is created.
And finally there is the PowerShell.AddArgument which I could use like AddParameter, and use a param() block. this would work, however, each argument will be passed in order, and ends up working as well as $args.
So my question is what is the recommended way of passing arguments\parameters\variables into a scriptblock for the purposes I have defined? Is there a performance advantage of one over the other? Can you expand on what purpose you may use one over the other?
You can use cmdlet binding inside of the script block definition.
$ScriptBlock = {
Param (
[int]$NumberofHours,
[string]$ClientName
)
$LogPath = 'd:\Program Files\Exchange\Logging\RPC Client Access\*.LOG'
$today = (Get-Date).AddHours($NumberofHours)
}
Then define call these parameters when you execute the script block.
Invoke-Command -ComputerName $servers -ScriptBlock $ScriptBlock -ArgumentList -1,$cn -ErrorAction SilentlyContinue
Here's my $.02. I know this thread is old but I came across it unanswered while searching for something else.
This example uses a runspace pool to demonstrate how to pass parameters to the script block while using a runspace. The function this comes from passes a datatable to a script block for processing. Some variable names were changed to help with out-of-context readability.
$maxThreads = 5
$jobs = New-Object System.Collections.ArrayList
$rsp = [runspacefactory]::CreateRunspacePool(1,$maxThreads)
$rsp.open()
$params = New-Object 'System.Collections.Generic.Dictionary[string, string]'
$params.Add('Data', $myDataTable)
$params.Add('TableName', $myTableName)
$PS = [powershell]::Create()
$PS.Runspacepool = $rsp
$PS.AddScript($myScriptBlockCode)AddParameters($parameters)
Another option would be to forgo the iDictionary step and simply call .AddParameter repeatedly.
$PS.AddScript($myScriptBlockCode).AddParame'ter(Data',$myDataTable).AddParameter('tableName',$myTableName)
A third way is to pass the parameters as arguments in the order they are defined in your script block
$PS.AddScrip(t$myScriptBlockCode).AddArgument($myDataTable).AddArgument($myTableName)
I hope this helps someone out. I welcome comments as to ways to do this better. As long as the comment doesn't suggest turning it into some amateurish one-liner that no one can read....

Are there technical benefits using "param(<params here))" inside a function over "function myfunc(<param here>)"?

Why are there two different formatting possible for parameters input? Are there any benefits other than personal taste to this?
For example, i'm using this because that's what I first seen reading documentation about Powershell
function MyAwesomeFunction
(
[parameter(Mandatory=$Whatever)]
[string]
$MyAwesomeVariable = MyAwesomeDefaultValue
)
{MyAwesome stuff to do}
But there's case when looking at user made examples where the parameters are made that way
function MyAwesomeFunction
{
param(
[parameter(Mandatory=$Whatever)]
[string]
$MyAwesomeVariable = MyAwesomeDefaultValue
)
MyAwesome stuff to do
}
If you're just writing a plain old function, PowerShell doesn't care which style you use; it should work just as well either way. However, a lot of people use Param() for consistency, as it is required in certain situations. Most notably, the CmdletBinding() attribute requires the use of Param() (even if your function doesn't have any parameters).

Is there a simple way to pass specific *named* PowerShell parameters through directly to a called function?

I am sure I read somewhere that there is an easy way to pass named parameters from a calling function to a called function without explicitly naming and specifying each parameter.
This is more than just reusing the position; I'm interested in the case where the name of the passed parameters is the same in some cases, but not in others.
I also think there is a way that is not dependent on position.
function called-func {
param([string]$foo, [string]$baz, [string]$bar)
write-debug $baz
write-host $foo,$bar
}
function calling-func {
param([int]$rep = 1, [string]$foo, [string]$bar)
1..$rep | %{
called-func -foo $foo -bar $bar -baz $rep ## <---- Should this be simpler?
}
}
calling-func -rep 10 -foo "Hello" -bar "World"
What would the method be, and is there a link?
I thought it might have been Jeffrey Snover, but I'm not sure.
In PowerShell v2 (which admittedly you may not be ready to move to yet) allows you to pass along parameters without knowing about them ahead of time:
called-func $PSBoundParameters
PSBoundParameters is a dictionary of all the parameters that were actually provided to your function. You can remove parameters you don't want (or add I suppose).
Well, I think I was confusing a blog post I read about switch parameters. As far as I can tell the best way is to just reuse the parameters like so:
called-func -foo:$foo -bar:$bar
How about
called-func $foo $bar