CodeGenerator/ConvertFrom-TypeToCmdlet.ps1
function ConvertFrom-TypeToScriptCmdlet { <# .Synopsis Converts .NET Types into Windows PowerShell Script Cmdlets according to a number of rules. that have been added with Add-CodeGeneration rule .Description Converts .NET Types into Windows PowerShell Script Cmdlets according to a number of rules. Rules are added with Add-CodeGenerationRule #> param( [Parameter(ValueFromPipeline=$true)] [Type[]]$Type, [Switch]$AsScript, [Switch]$AsCSharp, [ref]$ConstructorCmdletNames ) begin { $LinkedListType = "Collections.Generic.LinkedList" Set-StrictMode -Off # Default as Script if(!$AsScript) { $AsCSharp = $true } } process { foreach ($t in $type) { $Parameters = New-Object "$LinkedListType[Management.Automation.ParameterMetaData]" $BeginBlocks = New-Object "$LinkedListType[ScriptBlock]" $ProcessBlocks = New-Object "$LinkedListType[ScriptBlock]" $EndBlocks = New-Object "$LinkedListType[ScriptBlock]" if ($PSVersionTable.BuildVersion.Build -lt 7100) { $CmdletBinding = "[CmdletBinding()]" } else { $CmdletBinding = "" } try { $Help = @{ Parameter = @{} } $Verb = "" $Noun = "" $BaseType = $t foreach ($rule in $CodeGenerationRuleOrder) { if (-not $rule) { continue } if ($rule -is [Type] -and (($t -eq $rule) -or ($t.IsSubclassOf($rule)))) { $nsb = $ExecutionContext.InvokeCommand.NewScriptBlock($codeGenerationCustomizations[$rule]) $null = . $nsb } else { if ($rule -is [ScriptBlock] -and ($t | Where-Object -FilterScript $rule)) { $nsb = $ExecutionContext.InvokeCommand.NewScriptBlock($codeGenerationCustomizations[$rule]) $null = . $nsb } } } } catch { Write-Error "Problem building $t" Write-Error $_ } if ((-not $Noun) -or (-not $Verb)) { continue } ## A hack to get a list of constructor cmdlets if($Verb -eq "New" -and (Test-Path Variable:ConstructorCmdletNames)) { $ConstructorCmdletNames.Value.Add( $Noun ) } $cmd = New-Object Management.Automation.CommandMetaData ([PSObject]) foreach ($p in $parameters) { $null = $cmd.Parameters.Add($p.Name, $p) } if ($AsScript) { #region Generate the Script Parameter Block $parameterBlock = [Management.Automation.ProxyCommand]::GetParamBlock($cmd) #endregion #region Generate the Help $oldOfs = $ofs $ofs = "" $helpBlock = New-Object Text.StringBuilder $parameterNames = "Parameter", "ForwardHelpTargetName", "ForwardHelpCategory", "RemoteHelpRunspace", "ExternalHelp", "Synopsis", "Description", "Notes", "Link", "Example", "Inputs", "Outputs", "Component", "Role", "Functionality" if ($help.Synopsis -and $help.Description) { foreach ($key in $help.Keys) { if ($parameterNames -notcontains $key) { Write-Error "Could not generate help for $t. The Help dictionary contained a key ($key) that is not a valid help section" break } } foreach ($kv in $help.GetEnumerator()) { switch ($kv.Key) { Parameter { foreach ($p in $kv.Value.GetEnumerator()) { if (-not $p) { continue } $null = $helpBlock.Append( " .Parameter $($p.Key) $($p.Value)") } } Example { foreach ($ex in $kv.Value) { $null = $helpBlock.Append( " .Example $ex") } } default { $null = $helpBlock.Append( " .$($kv.Key) $($kv.Value)") } } } } $helpBlock = "$helpBlock" if ($helpBlock) { $helpBlock = " <# $HelpBlock #> " } #endregion #region Generate Final Script Code @" function $Verb-$Noun { $HelpBlock $CmdletBinding param( $parameterBlock ) begin { $BeginBlocks } process { $ProcessBlocks } end { $EndBlocks } } "@ #endregion } elseif ($AsCSharp) { #region Generate the C# Parameter Block $usingBlock = New-Object Text.StringBuilder $null = $usingBlock.Append(" using System; using System.Collections; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; // using ShowUI; ") $propertyBlock = New-Object Text.StringBuilder $fieldBlock = New-Object Text.StringBuilder $null = $fieldBlock.Append(@" /// <summary> /// A Field to store the pipeline used to invoke the commands /// </summary> private Pipeline pipeline; "@) $defaultParameterPosition =0 $namespaces = "$usingBlock" -split ([Environment]::NewLine) | Where-Object { $_ } | ForEach-Object { $_.Trim().Replace("using ", "").Replace(";","") } $parameterNames = $parameters | Select-Object -ExpandProperty Name foreach ($p in $parameters) { if (-not $p) { continue } # declare the field $parameterName = $fieldName = $p.Name $fieldName = $fieldName.ToCharArray()[0].ToString().ToLower() + $fieldName.Substring(1) $PropertyName = $fieldName.ToCharArray()[0].ToString().ToUpper() + $fieldName.Substring(1) $parameterType = $p.ParameterType if (-not $parameterType) { $parameterType = [PSObject] } $parameterTypeFullName = $parameterType.Fullname $parameterNamespace = $parameterType.Namespace if ($namespaces -notcontains $parameterNamespace) { $null = $usingBlock.AppendLine("using $parameterNamespace;") $namespaces = "$usingBlock" -split ([Environment]::NewLine) | Where-Object { $_ } | ForEach-Object { $_.Trim().Replace("using ", "").Replace(";","") } } $fieldType = $p.Property $parameterAttributes = $p.Attributes | Where-Object { $_ -is [Management.Automation.ParameterAttribute] } | ForEach-Object { $attributeParts = @() $item = $_ if ($item.Position -ge 0) { $attributeParts+="Position=$($item.Position)" } if ($item.ParameterSetName -ne '__AllParameterSets') { $attributeParts+="ParameterSetName=$($item.ParameterSetName)" } if ($item.Mandatory) { $attributeParts+="Mandatory=true" } if ($item.ValueFromPipeline) { $attributeParts+="ValueFromPipeline=true" } if ($item.ValueFromPipelineByPropertyName) { $attributeParts+="ValueFromPipelineByPropertyName=true" } if ($item.ValueFromRemainingArguments) { $attributeParts+="ValueFromRemainingArguments=true" } if ($item.HelpMessage) { $attributeParts+="HelpMessage=@`"$($item.HelpMessage)`"" } if ($item.HelpMessageBaseName) { $attributeParts+="HelpMessageBaseName=@`"$($item.HelpMessageBaseName)`"" } if ($item.HelpMessageResourceId) { $attributeParts+="HelpMessageResourceId=@`"$($item.HelpMessageResourceId)`"" } $ofs = "," "[Parameter($attributeParts)]" } if (-not $parameterAttributes) { # In this case, the parameter is not mandatory, # and will be marked ValueFromPipelineByPropertyName and will assume the first default position $parameterAttributes += "[Parameter(Position=$defaultParameterPosition)]" $defaultParameterPosition++ } $ofs = [Environment]::NewLine $ParameterDeclaration = "$parameterAttributes" $null = $propertyBlock.Append(" /// <summary> /// Gets or sets the $PropertyName property, which holds the value for the $ParameterName /// </summary> $ParameterDeclaration public $parameterTypeFullName $PropertyName { get; set; } ") } #endregion #region Create the Begin/Process/End code chunks # The trick here is InvokeScript. # Each of the Begin/Process/End effectively becomes an InvokeScript, # with all of the values passed in as positional arguments $pNames= @("BoundParameters") + $parameterNames $ofs = ',$' $parameterDeclaration = "param(`$$pNames)" $beginBlocks = @($beginBlocks) $processBlocks = @($processBlocks) $endBlocks = @($endBlocks) $beginProcessingCode = "" if ($beginBlocks) { $ofs = [Environment]::NewLine $fullBeginBlock = " $parameterDeclaration $beginBlocks".Replace('"','""') $ofs =',' $beginProcessingCode = @" System.Collections.Generic.Dictionary<string,Object> BoundParameters = this.MyInvocation.BoundParameters; this.InvokeCommand.InvokeScript(@" $fullBeginBlock ", new Object[] { $pNames } ); "@ } $endProcessingCode = "" if ($endBlocks) { $ofs = [Environment]::NewLine $fullEndBlock = " $parameterDeclaration $endBlocks".Replace('"','""') $ofs =',' $EndProcessingCode = New-Object Text.StringBuilder $null = $EndProcessingCode.Append(@" System.Collections.Generic.Dictionary<string,Object> BoundParameters = this.MyInvocation.BoundParameters; PSLanguageMode languageMode = this.SessionState.LanguageMode; if (languageMode != PSLanguageMode.Full) { this.SessionState.LanguageMode=PSLanguageMode.FullLanguage; } pipeline.Commands.AddScript(@" $fullEndBlock ", true); foreach (System.Collections.Generic.KeyValuePair<string,Object> param in this.MyInvocation.BoundParameters) { pipeline.Commands[0].Parameters.Add(param.Key, param.Value); } try { this.WriteObject( pipeline.Invoke(), true); } catch (Exception ex) { ErrorRecord errorRec; if (ex is ActionPreferenceStopException) { ActionPreferenceStopException aex = ex as ActionPreferenceStopException; errorRec = aex.ErrorRecord; } else { errorRec = new ErrorRecord(ex, "EmbeddedProcessRecordError", ErrorCategory.NotSpecified, null); } if (errorRec != null) { this.WriteError(errorRec); } } if (languageMode != PSLanguageMode.FullLanguage) { this.SessionState.LanguageMode=languageMode; } "@) foreach ($param in $parameterNames) { $null = $EndProcessingCode.Append(@" this.SessionState..PSVariable.Remove("$param"); "@) } } $ProcessRecordCode="" if ($processBlocks) { $ofs = [Environment]::NewLine $fullProcessBlock = " $parameterDeclaration $processBlocks".Replace('"','""') $ofs =',' $ProcessRecordCode = @" System.Collections.Generic.Dictionary<string,Object> BoundParameters = this.MyInvocation.BoundParameters; PSLanguageMode languageMode = this.SessionState.LanguageMode; if (languageMode != PSLanguageMode.FullLanguage) { this.SessionState.LanguageMode=PSLanguageMode.FullLanguage; } pipeline.Commands.AddScript(@" $fullProcessBlock ", true); foreach (System.Collections.Generic.KeyValuePair<string,Object> param in this.MyInvocation.BoundParameters) { pipeline.Commands[0].Parameters.Add(param.Key, param.Value); } try { this.WriteObject( pipeline.Invoke(), true); } catch (Exception ex) { ErrorRecord errorRec; if (ex is ActionPreferenceStopException) { ActionPreferenceStopException aex = ex as ActionPreferenceStopException; errorRec = aex.ErrorRecord; } else { errorRec = new ErrorRecord(ex, "EmbeddedProcessRecordError", ErrorCategory.NotSpecified, null); } if (errorRec != null) { this.WriteError(errorRec); } } if (languageMode != PSLanguageMode.FullLanguage) { this.SessionState.LanguageMode=languageMode; } "@ } #endregion #region Generate the final cmdlet $namespaceID = Get-Random @" namespace AutoGenerateCmdlets$namespaceID { $usingBlock [Cmdlet("$Verb", "$Noun")] [OutputType(typeof($($BaseType.FullName)))] public class ${Verb}${Noun}Command : PSCmdlet { $fieldBlock $propertyBlock protected override void BeginProcessing() { pipeline = Runspace.DefaultRunspace.CreateNestedPipeline(); $BeginProcessingCode } protected override void ProcessRecord() { pipeline.Commands.Clear(); $ProcessRecordCode } protected override void EndProcessing() { $EndProcessingCode pipeline.Dispose(); } } } "@ #endregion } } } } |