views:

149

answers:

2

I'm trying to programmatically create a parameter block for a function ( along the lines of this blog post ).

I'm starting with a CommandMetadata object (from an existing function). I can create the ParameterMetadata object and set things like the ParameterType, the name, as well as some attributes.

The problem I'm running into is that when I use the GetParamBlock method of the ProxyCommand class, none of my attributes that I set in the Attributes collection of the ParameterMetadata are generated.

The problem this causes is that when the GetParamBlock is called, the new parameter is not annotated with the appropriate Parameter attribute.

Example:

function test 
{
    [CmdletBinding()]
    param (
    [Parameter()]
    $InitialParameter)

    Write-Host "I don't matter."
}

$MetaData = New-Object System.Management.Automation.CommandMetaData (get-command test)

$NewParameter = New-Object System.Management.Automation.ParameterMetadata 'NewParameter'

$NewParameter.ParameterType = [string[]]

$Attribute = New-Object System.Management.Automation.ParameterAttribute 
$Attribute.Position = 1
$Attribute.Mandatory = $true
$Attribute.ValueFromPipeline = $true

$NewParameter.Attributes.Add($Attribute)
$MetaData.Parameters.Add('NewParameter', $NewParameter)


[System.Management.Automation.ProxyCommand]::GetParamBlock($MetaData) 
+1  A: 

Hi Steven,

The reason it does not show up is because your NewParameter needs to belong to at least one parameter set. In this case, it should be a member of the special parameterset, "__AllParameterSets."

You can verify this by copying the ParameterSetMetadata instance from InitialParameter. Unfortuntely I can't see immediately how to get this ParameterSetMetadata if you don't have any parameters to grab it from. Copying it from the other parameter makes it appear in the output, but it's the metadata from InitialParameter, so this is not the solution, only the reason why it doesn't work (yet.) I'll update this post when I figure it out.

-Oisin

x0n
Thanks for the answer. I can copy the metadata from an existing parameter, but that just seems wrong, especially since you can then have a discrepancy between the attributes property and the parameter sets property. ;(
Steven Murawski
yeah, it's not the right way at all. sorry if that didn't come across clearly.
x0n
+3  A: 
function test 
{
    [CmdletBinding()]
    param (
    [Parameter()]
    $InitialParameter)

    Write-Host "I don't matter."
}

$MetaData = New-Object System.Management.Automation.CommandMetaData (get-command test)

$NewParameter = New-Object System.Management.Automation.ParameterMetadata 'NewParameter'

$NewParameter.ParameterType = [string[]]

$Attribute = New-Object System.Management.Automation.ParameterAttribute 
$Attribute.Position = 1
$Attribute.Mandatory = $true
$Attribute.ValueFromPipeline = $true

$NewParameter.Attributes.Add($Attribute)
$MetaData.Parameters.Add('NewParameter', $NewParameter)

$ParameterSetMetadata = "System.Management.Automation.ParameterSetMetadata"
$ParameterSetInfo = new-object psobject -Property @{ 
    Position=[Int]::MinValue
    Flags=3
    HelpMessage="Please Enter a Value"
} | ForEach { 
    $_.PSTypeNames.Add("Deserialized.$ParameterSetMetadata")
    write-Output $_ 
}

$converter = new-object  Microsoft.PowerShell.DeserializingTypeConverter
$ConvertedSet = $converter.ConvertFrom($ParameterSetInfo,$ParameterSetMetadata, $null, $true)

$NewParameter.ParameterSets.Add('__AllParameterSets', $ConvertedSet )

[System.Management.Automation.ProxyCommand]::GetParamBlock($MetaData)
Jaykul
First off, thanks for the answer, however using 'OutVariable' to copy the parameter set info from throws an error - Cannot index into a null array.At line:25 char:55+ $MetaData.Parameters['OutVariable'].ParameterSets[ <<<< '__AllParameterSets'] ) + CategoryInfo : InvalidOperation: (__AllParameterSets:String) [], RuntimeException + FullyQualifiedErrorId : NullArrayIf I try using my previous parameter, I get different information in the attribute property and the parameter set property. The information from the parameter set property is what script is generated.
Steven Murawski
Yeah, you can't alter the parameter set, or else it changes the place you got it from. Forget that, try this new reflection method. I can't believe there's no way to create that class.
Jaykul
Yeah, I was surprised that there wasn't a constructor for that. Thanks for the workaround.
Steven Murawski
@jaykul, yeah, i wasted about 45 minutes on this - i came to the same conclusion but wasn't willing to accept that reflection is the answer so I didn't go that route. I still can't believe it.
x0n
Since x0n couldn't figure it out, that provided the extra motivation for me to dig up the sanitized route to that method. This new code actually does the conversion using the same internal static method I was using before, but via a public method (albeit via a slick hack of a custom PSObject I couldn't have known to create without reflection).
Jaykul
Good job Jaykul - I love the Deserialized hack.
x0n