Cannot Generate ParameterSetMetadata While Programmatically Creating A Parameter Block
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)
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)
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
rant on: I was even very, very angry that we can instantiate a Type of System.Management.Automation.ParameterMetadata but we cannot initialisize it. Microsoft destroys much joy of the class library by contraining the classes by use of private or internal or sealed...... they use it to often, and without any thinkable reason. That is a very nuts library design! rant off:
For metaprogramming and to create ProxyCommands (proxy functions) I had the need to create a Windows PowerShell Parameter programatically from scratch. I even dont like to break into classes and steal and use vorbidden stuff, that is subject to change. Even the serialization trick is the same dirty way to do the same on a different route.
here is my prototype of a solution. I am creating a Function with a Parameter as Text (function sourcecode). my first atempt was to do a New-Item Function:\ -value {code} into the function drive and then do a Get-Command to the new function to extract the Metadata. But this shows up, that the function was a dead sorcecode only horse. It was not got compiled. So I had to use Invoke-Expression to 'compile' the sourcecode of the function.
Function New-Parameter {
[CmdletBinding()]
param(
[Switch]$Mandatory,
[UInt32]$Position,
[Switch]$ValueFromPipeline,
[Switch]$ValueFromPipelineByPropertyName,
[Switch]$ValueFromRemainingArguments,
[String]$HelpMessage,
[Type]$Type=[Type]'System.Management.Automation.SwitchParameter',
[Parameter(Mandatory=$True)]
[String]$Name,
[String]$DefaultValue,
[Switch]$DontShow,
[String[]]$ParameterSetName,
[String[]]$Aliases,
# if Metadata is present the result is an System.Management.Automation.ParameterMetadata object
# If Metadata is absent the sourcecode for the Parameter is returned
[Switch]$Metadata
)
$ParameterAttrib = [System.Collections.ArrayList]@()
# using GUID to create an unique function Name
$Guid = ([Guid]::NewGuid()).ToString()
# using a StringBuilder to glue the sourcecode
$stringBuilder = New-Object System.Text.StringBuilder
If($Metadata.IsPresent) {
# Open the Function{} block
[Void]$stringBuilder.AppendLine("Function $Guid {")
# add the [CmdletBinding()] attribute
[Void]$stringBuilder.AppendLine("[CmdletBinding()]")
# Open the Param() block
[Void]$stringBuilder.AppendLine("param(")
}
# query if we have one or more ParameterSetName
$ParmameterSetNameCount = 0
If(-not [String]::IsNullOrEmpty($ParameterSetName)) {
$ParmameterSetNameCount = @($ParameterSetName).Count
}
# Open the [Parameter()] attribut
[Void]$stringBuilder.Append('[Parameter(')
If($Mandatory.IsPresent) {
[Void]$ParameterAttrib.Add('Mandatory=$True')
}
If($Position) {
[Void]$ParameterAttrib.Add("Position=$Position")
}
If($ParmameterSetNameCount -gt 0){
# in the first full blown [Parameter()] attribut allways insert the first ParametersetName
[Void]$ParameterAttrib.Add("ParameterSetName='$($ParameterSetName[0])'")
}
If($ValueFromPipeline.IsPresent) {
[Void]$ParameterAttrib.Add('ValueFromPipeline=$True')
}
If($ValueFromPipelineByPropertyName.IsPresent) {
[Void]$ParameterAttrib.Add('ValueFromPipelineByPropertyName=$True')
}
If($ValueFromRemainingArguments.IsPresent) {
[Void]$ParameterAttrib.Add('ValueFromRemainingArguments=$True')
}
If($DontShow.IsPresent) {
If($PSVersionTable.PSVersion.Major -lt 4) {
Write-Warning "The 'DontShow' attribute requires PowerShell 4.0 or above! `n Supressing the 'DontShow' attribute!"
} Else {
[Void]$ParameterAttrib.Add('DontShow')
}
}
If(-not [String]::IsNullOrEmpty($HelpMessage)) {
[Void]$ParameterAttrib.Add("HelpMessage='$HelpMessage'")
}
# generate comma separated list from array
[Void]$stringBuilder.Append("$($ParameterAttrib -Join ',')")
$ParameterAttrib.Clear()
# close the [Parameter()] attribut
[Void]$stringBuilder.AppendLine(")]")
$ParmameterSetLoopCounter++
# If we have more then one ParametersetName
IF($ParmameterSetNameCount -gt 1) {
# add remaining parameterset names the parameter belongs to
for ($i = 1; $i -lt $ParmameterSetNameCount; $i++) {
[Void]$stringBuilder.AppendLine("[Parameter(ParameterSetName='$($ParameterSetName[$i])')]")
}
}
# Create Alias Attribute from Aliases
If(-not [String]::IsNullOrEmpty($Aliases)) {
[Void]$stringBuilder.AppendLine("[Alias('$($Aliases -join "','")')]")
}
# add Parameter Type
[Void]$stringBuilder.Append("[$($Type.Fullname)]")
# add the Parameter Name
[Void]$stringBuilder.Append("`$$Name")
If(-not [String]::IsNullOrEmpty($ParameterSetName)) {
[Void]$stringBuilder.Append("=$DefaultValue")
}
If($Metadata.IsPresent) {
# close the Param() block
[Void]$stringBuilder.AppendLine()
[Void]$stringBuilder.AppendLine(')')
# close the Function block
[Void]$stringBuilder.AppendLine('}')
}
# return the result
If($Metadata.IsPresent) {
# if we have to return a ParameterMetadata Object we create a temporary function
# because you can instatiate a ParameterMetadata Object but most of the Properties are constrained to get only and not to set!
# Create and 'compile' the function into the function: drive
Invoke-Expression ($stringBuilder.ToString())
# from the temporary function we query the the ParameterMetadata and
# return theParameterMetadata Object
(Get-Command -Name $Guid -CommandType Function).Parameters.$Name
# remove the Function from Function: drive
$Null = Remove-Item Function:\$Guid -Force
} Else {
# return the sourcecode of the Parameter
Write-Output $stringBuilder.ToString()
}
}
#Example calls:
# without Parametersets
New-Parameter -Name 'Param1' -Mandatory -Position 3 -ValueFromPipeline -ValueFromPipelineByPropertyName -ValueFromRemainingArguments -HelpMessage "Give me hope Joana!" -Type 'System.String' -Aliases 'Ali1','Ali2','Ali3' -DontShow -DefaultValue 34
New-Parameter -Name 'Param1' -Mandatory -Position 3 -ValueFromPipeline -ValueFromPipelineByPropertyName -ValueFromRemainingArguments -HelpMessage "Give me hope Joana!" -Type 'System.String' -Aliases 'Ali1','Ali2','Ali3' -DontShow -DefaultValue 34 -Metadata
# with Parametersets
New-Parameter -Name 'Param1' -Mandatory -Position 3 -ValueFromPipeline -ValueFromPipelineByPropertyName -ValueFromRemainingArguments -HelpMessage "Give me hope Joana!" -Type 'System.String' -ParameterSetName 'Snover','Payette' -Aliases 'Ali1','Ali2','Ali3' -DontShow -DefaultValue 34
New-Parameter -Name 'Param1' -Mandatory -Position 3 -ValueFromPipeline -ValueFromPipelineByPropertyName -ValueFromRemainingArguments -HelpMessage "Give me hope Joana!" -Type 'System.String' -ParameterSetName 'Snover','Payette' -Aliases 'Ali1','Ali2','Ali3' -DontShow -DefaultValue 34 -Metadata
AM I DOING RIGHT TO CONSTRUCT THE PARAMETERSETS?
精彩评论