-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use command references with get-command #27
Comments
Do not replace Functee. This can remain as is. The only thing we have to do is internally use get-command on the Functee. This way, there is much less impact and theortecially, no client code needs to be changed. All we have to do is instead of using the & op , we can just use get-command <Functee>, then invoke the block on the returned FunctionInfo object. Simples. |
I recently discovered that invoking get-command on a function so we can access the script block for invocatin does not work in the same way as invoking a script block directly; and that is because of the way parameters are bound in. If you extract the script block from a function via get-command, you can only invoke with positional parameters; specifying a hashtable with the bound parameters does not work on a script block extracted from a function. I don't think this can be fixed because of this parameter binding issue. |
Refer to this issue: PowerShell/PowerShell#8839 I was going to post this comment, but realised in testing that using & operator on a script block instead of using Invoke-Command, doesnt work .... 🤔 I initially started writing this response thinking I was going to agree with the OP about this issue, but when coming up with some sample code I discovered that my existing code does actuall work, but you don't have to use Invoke-Command to get what you want. If you use & @splatted-arguments, you will be able to invoke by named parameters without needing to resort to Invoke-Command. My original issue was not because of the imagined failure of the invoke, it was something else entirely, to do with Pester which is too boring to go thruu now. Please see the following text from the point of view of thinking the named invoke does not work (when in fact it does!) ... I recently came accross this issue and was wondering why my attempt to invoke a script block extracted from a function (via get-command) with splatted parameters. This just does not work, because the invoke can only work in a posional manner. Consider this function Invoke-ByPlatform I wrote, which allows the user to specify a hashtable of strings (representing the name of the platform: 'windows' | 'mac' | 'lnux'), mapped to a script block, which the user can set up by defining a script block, or getting the script block via FunctionInfo from get-command on existing functions: function Invoke-ByPlatform {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "")]
param(
[Parameter()]
[System.Collections.Hashtable]$Hash
)
$result = $null;
[string]$platform = Get-PlatformName;
[PSCustomObject]$invokeInfo = if ($Hash.ContainsKey($platform)) {
$Hash[$platform];
}
elseif ($Hash.ContainsKey('default')) {
$Hash['default'];
}
else {
Write-Error "!!!!!! Missing platform: '$platform' (and no default available)" -ErrorAction Continue;
$null;
}
if ($invokeInfo.FnInfo) {
if ($invokeInfo.psobject.properties.match('Positional') -and $invokeInfo.Positional) {
[object[]]$positional = $invokeInfo.Positional;
if ([scriptblock]$block = $invokeInfo.FnInfo.ScriptBlock) {
$result = $block.Invoke($positional);
}
else {
Write-Error $("ScriptBlock for function: '$($invokeInfo.FnInfo.Name)', ('$platform': platform) is missing") `
-ErrorAction Continue;
}
} elseif ($invokeInfo.psobject.properties.match('Named') -and $invokeInfo.Named) {
[System.Collections.Hashtable]$named = $invokeInfo.Named;
$result = & $invokeInfo.FnInfo.Name @named; # <-- I THOUGHT THIS LINE WOULD NOT WORK
} else {
Write-Error $("Missing Positional/Named: '$($invokeInfo.FnInfo.Name)', ('$platform': platform)") `
-ErrorAction Continue;
}
}
return $result;
} So the user wants to invoke a function depending on the platform and so sets up a hashtable like this: [System.Collections.Hashtable]$script:platformsNamed = @{
'windows' = [PSCustomObject]@{
FnInfo = Get-Command -Name invoke-winFn -CommandType Function;
Named = @{ 'name' = 'cherry'; 'colour' = 'red' }
};
'linux' = [PSCustomObject]@{
FnInfo = Get-Command -Name invoke-linuxFn -CommandType Function;
Named = @{ 'name' = 'grass'; 'colour' = 'green' }
};
'mac' = [PSCustomObject]@{
FnInfo = Get-Command -Name invoke-macFn -CommandType Function;
Named = @{ 'name' = 'lagoon'; 'colour' = 'blue' }
};
}
} The invocation would look like this:
And a complete test case: Context 'Invoke by Named' {
It 'Fails because parameters cant be splatted by name' -Tag 'Current' {
Mock -ModuleName Elizium.Loopz Get-PlatformName { return 'windows' }
[string]$expected = 'win: Name:cherry, Colour:red';
$result = Invoke-ByPlatform -Hash $platformsNamed;
Write-Host ">>> RESULT: '$result'"
$result | Should -Be $expected;
}
} Running this test case generates this response:
So what this tells me @justin-romano is that perhaps your example would work if you refrained from using Invoke-Command and used & operator instead ... still some work to do to work this out. Here is some additional test code: Context "justin-romano's example" {
BeforeEach {
$script:args = @{
P1 = 1
P3 = 3
P2 = 2
}
}
It 'With & operator' -Tag 'Current' {
[scriptblock]$block = {
param(
$P1,
$P2,
$P3
)
Write-Host ">>> $P1-$P2-$P3"
}
& $block @args;
}
It 'With Invoke-Command' {
# Invoke-Command -ScriptBlock {
# param($P1, $P2, $P3)
# Write-Host "$P1 $P2 $P3"
# } -ArgumentList @args
# [-] Invoke-ByPlatform.given: Parameters By Position.justin-romano's example.With Invoke-Command 2ms (2ms|1ms)
# ParameterBindingException: Missing an argument for parameter 'ArgumentList'. Specify a parameter of type 'System.Object[]' and try again.
# at <ScriptBlock>, C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Tests\Public\invoke-ByPlatform.tests.ps1:97
Invoke-Command -ScriptBlock {
param($P1, $P2, $P3)
Write-Host "$P1 $P2 $P3"
} -ArgumentList @args -Session $somesession
}
} The problem with this code is that the parameters $P1, $P2 and $P3 are not bound and are left as $null. |
Actually, the way that PowerShell treats functions as first class citizens; ie a function an be assigned to a variable is by using the following syntax: $variable = $function:<function-name> eg: function add($a, $b) {
$a + $b
}
$add = $function:add As an aside, in powershell, a function can be declared as a filte function to be used in pipelines using the filter keyword eg: filter square {
$_ * $_
}
# is an equivalent way of defining
function square {
Param(
[Parameter(Mandatory, ValueFromPipeline)]
[int[]]$Number
)
process {
$_ * $_
}
}
1..10 | square # outputs 1, 4, 9, 16, 25, ... See functional |
This is a big change, but the right one to do, unfortunately for little payback. Use get-command/invoke on block rather than using the & op on the named function string. This will fix the problem using test functions in Pester tests.
See FuncitonInfo Class
branch: use-command-ref
The text was updated successfully, but these errors were encountered: