1

I want to wrap a function, that is to create a new function such that it would automatically pass some arguments to the old function, like python's partial functions. The arguments passed are the ones defined in the callee and not the caller. The important thing is that I don't want to refer to each of them explicitly (define them twice).

That is really done to save typing-in the same flags to complicated functions while allowing customization.

For example, in python, I would do:

call_with_x=partial(call,x=1)

or maybe use **kw and pass it to the callee in some cases .


This is my best try (based on Wrapper function in PowerShell: Pass remaining parameters) :

function Let
{
    [CmdletBinding()]
Param([parameter(mandatory=$true, position=0)][string]$Option,
    [parameter(mandatory=$false, position=1, ValueFromRemainingArguments=$true)]$Remaining)
Get @Remaining
}

function Get
{
    [CmdletBinding()]
        Param([parameter(mandatory=$false, position=0)][string]$OptionA,
[parameter(mandatory=$true, position=1)][string]$OptionB)
Write-Host $OptionA, $OptionB
}

But Let -Option c -OptionA 1

Prints -OptionA 1 which is obviously not what I intended.

user2679290
  • 144
  • 9
  • What is the intentions? You would only get `-OptionA 1` cause you're only splatting that parameter. Maybe I'm not understanding what you're asking for, but you can splat all the parameters using `@PSBoundParameters`. The only issue would be that the "*callee*" function would have to have the same parameters as the caller function. – Abraham Zinala Jan 13 '23 at 17:29
  • I would like the callee to have its optionA and optionB filled by args supplied to the caller. Notice that they don't have the same signature. `PSBoundParameters` seems like a good direction. – user2679290 Jan 13 '23 at 17:32
  • The remaining kind of absorbs all args. it seems to be the only problem. But solveable I guess. I didnt know that variable. – user2679290 Jan 13 '23 at 17:36
  • Perhaps it is possible to use DynamicParam to add the reamining parameters of the sec function. – user2679290 Jan 13 '23 at 17:52
  • Unfortunately `ValueFromRemainingArguments` produces an array, but for forwarding using splatting, you'd need a hashtable. You could start with `$PSBoundParameters` and remove the arguments that shouldn't be forwarded. – zett42 Jan 13 '23 at 18:12
  • 1
    if you define `$Remaining` as `[hashtable]` without a need for `ValueFromRemainingArguments` then `Get @Remaining` would work without problems as long as the hashtable being passed as argument has Keys matching the parameters form `Get`. This would also mean when calling `let` you're actually building a hash with the parameters as Keys... – Santiago Squarzon Jan 13 '23 at 19:25
  • 1
    the `DynamicParam` idea is not bad either, you could build the remaining parameters at runtime by reading the AST of `Get` (untested but I think that should work) – Santiago Squarzon Jan 13 '23 at 19:33
  • 1
    `(Get-Command Get).Parameters` gives you all the parameters without the need for the AST. – zett42 Jan 13 '23 at 20:00

2 Answers2

2

If you don't require "advanced" function features from CmdletBinding(), then you can get away with using $args for this:

# simple function parameters are positional and named, but not mandatory
function let {
  Param($OptionA,$OptionB)

  write-host "OptionA=$OptionA"
  write-host "OptionB=$OptionB"

  get @args
}

function get {
  Param($OptionC,$OptionD)

  write-host "OptionC=$OptionC"
  write-host "OptionD=$OptionD"
}

# not necessary to name or include parameters
let -OptionA A B -OptionC C D

OptionA=A
OptionB=B
OptionC=C
OptionD=D

# named parameters get assigned first, so the order is not too big a deal either
# these produce the same results as above:
let B -OptionA A D -OptionC C 
let -OptionD D -OptionC C A B

Any named parameters will not get positionally assigned to let

Any additional parameters, named or otherwise, will be forwarded to get

Cpt.Whale
  • 4,784
  • 1
  • 10
  • 16
-1

That was hard! For this you would need to add a bit of code in DynmaicParams and begin section of the function.

It might be possible to do it in an Attribute

function x{
[WrapperFor(y)]

Might do it Later.

function Get
{
    [CmdLetBinding()]
        Param([parameter(mandatory=$false, position=0)][string]$OptionA,
[parameter(mandatory=$false, position=1)][string]$OptionB)
Write-Host "opta",$OptionA
Write-Host "optb",$OptionB
}


function Let
{
    [CmdletBinding()]
Param([parameter(mandatory=$true, position=0)][string]$Option,[parameter(mandatory=$false, position=0)][string]$OptionB)


DynamicParam {
AddWrapper -For "Get" -To "Let"
}
Begin { 
    
   $params = GetRestOfParams "Get" $PSBoundParameters
}
Process {
    Get @params 
}
}

Needed code:

using namespace System.Management.Automation
function Empt
{
    [CmdletBinding()]
        Param([parameter(mandatory=$true, position=0)][string]$aaaa)
    1
}

function AddWrapper([parameter(mandatory=$true, position=0)][string]$For,[parameter(mandatory=$true, position=1)][string]$To) 
{
    $paramDictionary = [RuntimeDefinedParameterDictionary]::new()
    $paramset= $(Get-Command $For).Parameters.Values | %{[System.Management.Automation.RuntimeDefinedParameter]::new($_.Name,$_.ParameterType,$_.Attributes)}
    $paramsetlet= $(Get-Command empt).Parameters.Keys 
    $paramsetlet+= $(Get-Command $To).ScriptBlock.Ast.Body.ParamBlock.Parameters.Name | %{ $_.VariablePath.UserPath }
    $paramset | %{ if ( -not ($paramsetlet -contains $_.Name) ) {$paramDictionary.Add($_.Name,$_)}}
    return $paramDictionary
}
function GetRestOfParams($dst,$params)
{
    $dstorgparams=$(Get-Command $dst).Parameters.Keys
    $z= $params
    $z.Keys | %{ if ( -not ($dstorgparams -contains $_) ) {$z.Remove($_)} } | Out-Null
    return $z
}
user2679290
  • 144
  • 9