Indented.StubCommand.psm1
using namespace System.Management.Automation using namespace System.Management.Automation.Internal using namespace System.Reflection using namespace System.Text class ScriptBuilder { # # Properties # [String] $IndentCharacters = ' ' Hidden [Int32] $indentCount = 0 Hidden [String] $line = '' Hidden [StringBuilder] $stringBuilder = (New-Object StringBuilder) # # Public methods # [ScriptBuilder] Append([String]$String) { $this.line += $String return $this } [ScriptBuilder] AppendFormat([String]$String, [Object]$Value) { return $this.AppendFormat($String, @($Value)) } [ScriptBuilder] AppendFormat([String]$String, [Object]$Value1, [Object]$Value2) { return $this.AppendFormat($String, @($Value1, $Value2)) } [ScriptBuilder] AppendFormat([String]$String, [Object[]]$Values) { $this.line += $String -f $Values return $this } [ScriptBuilder] AppendLine() { return $this.AppendLine('') } [ScriptBuilder] AppendLine([String]$String) { $this.line += $String if ($this.line[-1] -in ')', '}' -and $this.ShouldChangeIndent()) { $this.indentCount-- } $this.stringBuilder.AppendFormat('{0}{1}', ($this.IndentCharacters * $this.indentCount), $this.line). AppendLine() if ($this.line[-1] -in '(', '{' -and $this.ShouldChangeIndent()) { $this.indentCount++ } $this.line = '' return $this } [ScriptBuilder] AppendLines([String[]]$Lines) { foreach ($scriptLine in $Lines) { $this.AppendLine($scriptLine.Trim()) } return $this } [ScriptBuilder] AppendScript([String]$Script) { foreach ($scriptLine in $Script -split '\r?\n') { $this.AppendLine($scriptLine.Trim()) } return $this } [String] ToString() { return $this.stringBuilder.ToString() } # # Private methods # Hidden [Int32] CountCharacter([String]$String, [Char]$Character) { $count = 0 foreach ($char in $String.GetEnumerator()) { if ($char -eq $Character) { $count++ } } return $count } Hidden [Char] GetCompliment([Char]$Character) { $value = switch ($Character) { '(' { ')' } ')' { '(' } '{' { '}' } '}' { '{' } default { $null } } return $value } Hidden [Boolean] ShouldChangeIndent() { if ($this.CountCharacter($this.line, $this.line[-1]) -gt $this.CountCharacter($this.line, $this.GetCompliment($this.line[-1]))) { return $true } return $false } } function GetTypeName { <# .SYNOPSIS Get the full name of a type. .DESCRIPTION Get the full name of a type. .NOTES Change log: 31/05/2017 - Chris Dent - Created. #> [OutputType([String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [Type]$Type ) process { if ($Type.IsNestedPublic) { $Type.FullName.Replace('+', '.') } elseif ($Type.IsGenericType) { $genericTypeName = $Type.GetGenericTypeDefinition().FullName -replace '`\d+' '{0}<{1}>' -f $genericTypeName, (($Type.GenericTypeArguments | GetTypeName) -join ',') } else { $Type.FullName } } } function TestIsForeignAssembly { <# .SYNOPSIS Test whether or not the assembly can be considered native. .DESCRIPTION This command compares a named assembly with a list of assemblies in a text file. The comparison is used to determine whether or not a given type needs to be recreated in a stub using an empty class. The list is generated using: [AppDomain]::CurrentDomain.GetAssemblies().FullName | Sort-Object .NOTES Change log: 07/04/2017 - Chris Dent - Improved use of script level variable. 05/04/2017 - Chris Dent - Created. #> [OutputType([Boolean])] param ( # The assembly name to test against the list. [String]$AssemblyName, # A static list of assemblies read from var\assemblyList. [String[]]$AssemblyList = $Script:assemblyList ) if ($null -eq $AssemblyList) { $AssemblyList = $Script:assemblyList = Get-Content "$psscriptroot\var\assemblyList.txt" } if ($AssemblyList -contains $AssemblyName) { return $false } return $true } function Get-StubRequiredType { <# .SYNOPSIS Gets the list of types required by a set of commands. .DESCRIPTION The list of required types includes: Types defined for parameters attached to PowerShell commands. Types required to satisfy exposed public properties. Types required to satisfy Create or Parse methods. Type list expansion is limited to two levels. The second level of classes (not directly required by a parameter) will have type names assigned to members rewritten. .INPUTS System.Management.Automation.CommandInfo .NOTES Change log: 11/05/2017 - Chris Dent - Created. #> [CmdletBinding()] [OutputType('StubTypeInfo')] param ( # Resolve the list of types required by the specified command. [Parameter(Mandatory, Position = 1, ValueFromPipeline)] [CommandInfo]$CommandInfo ) begin { $primaryTypes = @{} } process { foreach ($parameter in $CommandInfo.Parameters.Values) { if (-not $primaryTypes.Contains($parameter.ParameterType)) { $primaryTypes.Add($parameter.ParameterType, $null) } } foreach ($outputTypeAttribute in $CommandInfo.OutputType) { if ($outputTypeAttribute.Type -and -not $primaryTypes.Contains($outputTypeAttribute.Type)) { $primaryTypes.Add($outputTypeAttribute.Type, $null) } } # Replace array types $keys = $primaryTypes.Keys | ForEach-Object { $_ } foreach ($type in $keys) { if ($type.BaseType -eq ([Array])) { $primaryTypes.Remove($type) $elementType = $type.GetElementType() if (-not $primaryTypes.Contains($elementType)) { $primaryTypes.Add($elementType, $null) } } } } end { # Remove types defined in native assemblies from the list $primaryTypes.Keys | Group-Object { $_.Assembly.FullName } | Where-Object { TestIsForeignAssembly $_.Name } | ForEach-Object { $_.Group } | Select-Object @{n='Type'; e={ $_ }}, @{n='IsPrimary'; e={ $true }} | Add-Member -TypeName 'StubTypeInfo' -PassThru # Generate a list of secondary types $secondaryTypes = @{} $primaryTypes.Keys | ForEach-Object { $type = $_ $instanceMembers = $type.GetMembers([BindingFlags]'Public,Instance') | Where-Object MemberType -in 'Field', 'Constructor', 'Property' foreach ($member in $instanceMembers) { switch ($member.MemberType) { 'Field' { $member.FieldType } 'Constructor' { $member.GetParameters().ParameterType } 'Property' { $member.PropertyType } } } $staticMethods = $type.GetMethods([BindingFlags]'Public,Static') | Where-Object { $_.Name -in 'Create', 'Parse' -and $_.ReturnType.Name -eq $type.Name } foreach ($method in $staticMethods) { $method.GetParameters().ParameterType } } | ForEach-Object { if ($_.BaseType -eq [Array]) { $_.GetElementType() } elseif ($_.IsGenericType) { $_.GenericTypeArguments } else { $_ } } | Where-Object { $_ -and -not $primaryTypes.Contains($_) -and -not $secondaryTypes.Contains($_) } | ForEach-Object { $secondaryTypes.Add($_, $null) } $secondaryTypes.Keys | Group-Object { $_.Assembly.FullName } | Where-Object { TestIsForeignAssembly $_.Name } | ForEach-Object { $_.Group } | Select-Object @{n='Type'; e={ $_ }}, @{n='IsPrimary'; e={ $false }} | Add-Member -TypeName 'StubTypeInfo' -PassThru } } function New-StubCommand { <# .SYNOPSIS Create a new partial copy of a command. .DESCRIPTION New-StubCommand recreates a command as a function with param block and dynamic param block (if used). .INPUTS System.Management.Automation.CommandInfo .EXAMPLE New-StubCommand Test-Path Create a stub of the Test-Path command. .EXAMPLE Get-Command -Module AppLocker | New-StubCommand Create a stub of all commands in the AppLocker module. .NOTES Change log: 10/08/2019 - Johan Ljunggren - Added parameter ReplaceTypeDefinition 10/05/2017 - Chris Dent - Added automatic help insertion. 03/04/2017 - Chris Dent - Created. #> # This command does not change state. [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'FromPipeline')] [OutputType([String])] param ( # Generate a stub of the specified command name. [Parameter(Position = 0, Mandatory, ParameterSetName = 'FromString')] [String]$CommandName, # Generate a stub of the specified command. [Parameter(ValueFromPipeline, ParameterSetName = 'FromPipeline')] [CommandInfo]$CommandInfo, # Request generation of type statements to satisfy parameter binding. [Switch]$IncludeTypeDefinition, # Allow population of generated stub command with a custom function body. [ValidateScript({ if ($null -ne $_.Ast.ParamBlock -or $null -ne $_.Ast.DynamicParamBlock) { throw (New-Object ArgumentException ("FunctionBody scriptblock cannot contain Param or DynamicParam blocks")) } else {$true} })] [scriptblock]$FunctionBody, # Allow types on parameters to be replaced by another type. [System.Collections.Hashtable[]]$ReplaceTypeDefinition ) begin { if ($pscmdlet.ParameterSetName -eq 'FromString') { $null = $psboundparameters.Remove('CommandName') Get-Command $CommandName | New-StubCommand @PSBoundParameters } else { $commonParameters = ([CommonParameters]).GetProperties().Name $shouldProcessParameters = ([ShouldProcessParameters]).GetProperties().Name } } process { if ($pscmdlet.ParameterSetName -eq 'FromPipeline') { try { $script = New-Object ScriptBuilder if ($IncludeTypeDefinition) { $null = $script.AppendLine((Get-StubRequiredType -CommandInfo $CommandInfo | New-StubType)) } $null = $script.AppendFormat('function {0} {{', $CommandInfo.Name). AppendLine() # Write help $helpContent = Get-Help $CommandInfo.Name -Full if ($helpContent.Synopsis) { $null = $script.AppendLine('<#'). AppendLine('.SYNOPSIS'). AppendFormat(' {0}', $helpContent.Synopsis.Trim()). AppendLine() foreach ($parameter in $CommandInfo.Parameters.Keys) { if ($parameter -notin $commonParameters -and $parameter -notin $shouldProcessParameters) { $parameterHelp = ($helpcontent.parameters.parameter | Where-Object { $_.Name -eq $parameter }).Description.Text if ($parameterHelp) { $paragraphs = $parameterHelp.Split("`n", [StringSplitOptions]::RemoveEmptyEntries) $null = $script.AppendFormat('.PARAMETER {0}', $parameter). AppendLine() foreach ($paragraph in $paragraphs) { $null = $script.AppendFormat(' {0}', $paragraph). AppendLine() } } } } $null = $script.AppendLine('#>'). AppendLine() } # Write CmdletBinding if ($cmdletBindingAttribute = [ProxyCommand]::GetCmdletBindingAttribute($CommandInfo)) { $null = $script.AppendLine($cmdletBindingAttribute) } # Write OutputType foreach ($outputType in $CommandInfo.OutputType) { $null = $script.Append('[OutputType(') if ($outputType.Type) { $null = $script.AppendFormat('[{0}]', $outputType.Type) } else { $null = $script.AppendFormat("'{0}'", $outputType.Name) } $null = $script.AppendLine(')]') } # Write param if ($CommandInfo.CmdletBinding -or $CommandInfo.Parameters.Count -gt 0) { $null = $script.Append('param (') if ($param = [ProxyCommand]::GetParamBlock($CommandInfo)) { foreach ($line in $param -split '\r?\n') { if ($psboundparameters.ContainsKey('ReplaceTypeDefinition')) { foreach ($type in $ReplaceTypeDefinition) { if ($line -match ('\[{0}\]' -f $type.ReplaceType)) { $line = $line -replace $type.ReplaceType, $type.WithType } } } $null = $script.AppendLine($line.Trim()) } } else { $null = $script.Append(' ') } $null = $script.AppendLine(')') } $newStubDynamicParamArguments = @{ CommandInfo = $CommandInfo } if ($psboundparameters.ContainsKey('ReplaceTypeDefinition')) { $newStubDynamicParamArguments['ReplaceTypeDefinition'] = $ReplaceTypeDefinition } if ($dynamicParams = New-StubDynamicParam @newStubDynamicParamArguments) { # Write dynamic params $null = $script.AppendScript($dynamicParams) } # Insert function body, if specified if ($null -ne $FunctionBody) { if ($null -ne $FunctionBody.Ast.BeginBlock) { $null = $script.AppendLine(($FunctionBody.Ast.BeginBlock)) } if ($null -ne $FunctionBody.Ast.ProcessBlock) { $null = $script.AppendLine(($FunctionBody.Ast.ProcessBlock)) } if ($null -ne $FunctionBody.Ast.EndBlock) { if ($FunctionBody.Ast.EndBlock -imatch '\s*end\s*{') { $null = $script.AppendLine(($FunctionBody.Ast.EndBlock)) } else { # Simple scriptblock does not explicitly specify that code is in end block, so we add the block decoration $null = $script.AppendLine('end {') $null = $script.AppendLine(($FunctionBody.Ast.EndBlock)) $null = $script.AppendLine('}') } } } # Close the function $null = $script.AppendLine('}') $script.ToString() } catch { Write-Error -ErrorRecord $_ } } } } function New-StubDynamicParam { <# .SYNOPSIS Creates a new script representation of a set of dynamic parameters. .DESCRIPTION Creates a new script representation of a set of dynamic parameters. The dynamic parameter set includes any attributes bound to individual parameters. .INPUTS System.Management.Automation.CommandInfo .EXAMPLE Get-Command Get-Item | New-StubDynamicParam Creates a copy of the dynamic param block used by Get-Item. .NOTES Change log: 10/08/2019 - Johan Ljunggren - Added parameter ReplaceTypeDefinition 04/04/2017 - Chris Dent - Created. #> # This command does not change state. [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] [OutputType([String])] param ( # Generate a dynamic param block for the specified command. [Parameter(Mandatory, ValueFromPipeline)] [CommandInfo]$CommandInfo, # Allow types on parameters to be replaced by another type. Also removes any parameter attribute using the type. [System.Collections.Hashtable[]]$ReplaceTypeDefinition ) process { $script = New-Object ScriptBuilder $dynamicParams = $CommandInfo.Parameters.Values.Where{ $_.IsDynamic } if ($dynamicParams.Count -gt 0) { $null = $script.AppendLine(). AppendLine('dynamicparam {'). AppendLine('$parameters = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary'). AppendLine() foreach ($dynamicParam in $dynamicParams) { $null = $script.AppendFormat('# {0}', $dynamicParam.Name). AppendLine(). AppendLine('$attributes = New-Object System.Collections.Generic.List[Attribute]'). AppendLine() foreach ($attribute in $dynamicParam.Attributes) { if ($psboundparameters.ContainsKey('ReplaceTypeDefinition')) { $skipAttribute = $false foreach ($type in $ReplaceTypeDefinition) { if ($attribute.TypeId.FullName -match $type.ReplaceType) { $skipAttribute = $true # There can only be one type, so continuing. continue } } if ($skipAttribute) { continue } } $ctor = $attribute.TypeId.GetConstructors()[0] $null = $script.AppendFormat('$attribute = New-Object {0}', $attribute.TypeId.FullName) $arguments = foreach ($parameter in $ctor.GetParameters()) { if ($null -ne $attribute.($parameter.Name)) { switch ($parameter.ParameterType) { ([String]) { "'{0}'" -f $attribute.($parameter.Name) } ([String[]]) { "'" + ($attribute.($parameter.Name) -join "', '") + "'" } ([ScriptBlock]) { "{{{0}}}" -f $attribute.($parameter.Name) } default { $attribute.($parameter.Name) } } } } if ($null -eq $arguments) { $null = $script.AppendLine() } else { $null = $script.AppendFormat('({0})', $arguments -join ', '). AppendLine() } # Parameter named parameter handler if ($attribute.TypeId.Name -eq 'ParameterAttribute') { $default = New-Object Parameter foreach ($property in $attribute.PSObject.Properties) { if ($property.Value -ne $default.($property.Name)) { $value = switch ($property.TypeNameOfValue) { 'System.String' { '"{0}"' -f $property.Value } 'System.Boolean' { '${0}' -f $property.Value.ToString() } default { $property.Value } } $null = $script.AppendFormat('$attribute.{0} = {1}', $property.Name, $value). AppendLine() } } } # ValidatePattern named parameter handler if ($attribute.TypeId.Name -eq 'ValidatePatternAttribute') { if ($attribute.Options -ne 'IgnoreCase') { $null = $script.AppendFormat('$attribute.Options = "{0}"', $attribute.Options.ToString()). AppendLine() } } $null = $script.AppendLine('$attributes.Add($attribute)'). AppendLine() } $parameterType = $dynamicParam.ParameterType.ToString() if ($psboundparameters.ContainsKey('ReplaceTypeDefinition')) { foreach ($type in $ReplaceTypeDefinition) { if ($parameterType -match $type.ReplaceType) { $parameterType = $parameterType -replace $type.ReplaceType, $type.WithType # There can only be one type, so continuing. continue } } } $null = $script.AppendFormat('$parameter = New-Object System.Management.Automation.RuntimeDefinedParameter("{0}", [{1}], $attributes)', $dynamicParam.Name, $parameterType). AppendLine(). AppendFormat('$parameters.Add("{0}", $parameter)', $dynamicParam.Name). AppendLine(). AppendLine() } $null = $script.AppendLine('return $parameters'). AppendLine('}') } return $script.ToString() } } function New-StubModule { <# .SYNOPSIS Create a new stub module. .DESCRIPTION A stub module contains: All exported commands provided by a module. A copy of any enumerations used by the module from non-native assemblies. A stub of any .NET classes consumed by the module from non-native assemblies. .EXAMPLE New-StubModule -FromModule DnsClient Create stub of the DnsClient module. .EXAMPLE New-StubModule -FromModule ActiveDirectory -Path C:\Temp -ReplaceTypeDefinition @( @{ ReplaceType = 'System\.Nullable`1\[Microsoft\.ActiveDirectory\.Management\.\w*\]' WithType = 'System.Object' }, @{ ReplaceType = 'Microsoft\.ActiveDirectory\.Management\.Commands\.\w*' WithType = 'System.Object' }, @{ ReplaceType = 'Microsoft\.ActiveDirectory\.Management\.\w*' WithType = 'System.Object' } ) Creates a stub module of all the cmdlets and types in the module ActiveDirectory replacing specific types with another type. ReplaceTypeDefinition takes an array of hashtables. The hashtable must have two properties; the property ReplaceType contain the type name that should be replaced with the type name specified in the property WithType. The property ReplaceType supports regular expression. .NOTES Change log: 10/08/2019 - Johan Ljunggren - Added parameter ReplaceTypeDefinition 05/04/2017 - Chris Dent - Created. #> # This command does not change state. [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] [OutputType([String])] param ( # The name of a module to recreate. [Parameter(Mandatory)] [String]$FromModule, # Save the new definition in the specified directory. [String]$Path, # Allow population of generated stub command with a custom function body. Every function in the module will have the same body. [ValidateScript({ if ($null -ne $_.Ast.ParamBlock -or $null -ne $_.Ast.DynamicParamBlock) { throw (New-Object ArgumentException ("FunctionBody scriptblock cannot contain Param or DynamicParam blocks")) } else {$true} })] [ScriptBLock]$FunctionBody, # By default, New-StubModule uses the Module parameter of Get-Command to locate commands to stub. ForceSourceFilter makes command discovery dependent on the Source property of commands returned by Get-Command. [Switch]$ForceSourceFilter, # Allow types on parameters to be replaced by another type. [System.Collections.Hashtable[]]$ReplaceTypeDefinition ) try { $erroractionpreference = 'Stop' if (Test-Path $FromModule) { $FromModule = Import-Module $FromModule -PassThru | Select-Object -ExpandProperty Name } # Support wildcards in the FromModule parameter. $GetCommandSplat = @{} if (-not $ForceSourceFilter) { $GetCommandSplat.Add('Module', $FromModule) } Get-Command @GetCommandSplat | Where-Object { -not $ForceSourceFilter -or ($ForceSourceFilter -and $_.Source -eq $FromModule) } | Group-Object Source | ForEach-Object { $moduleName = $_.Name if ($psboundparameters.ContainsKey('Path')) { $filePath = Join-Path $Path ('{0}.psm1' -f $moduleName) $null = New-Item $filePath -ItemType File -Force } # Header '# Name: {0}' -f $moduleName if (-not $ForceSourceFilter) { '# Version: {0}' -f (Get-Module $moduleName).Version } '# CreatedOn: {0}' -f (Get-Date -Format 'u') '' # Types $_.Group | Get-StubRequiredType | New-StubType # Commands $StubCommandSplat = @{} if ($psboundparameters.ContainsKey('FunctionBody')) { $StubCommandSplat = @{FunctionBody = $FunctionBody} } if ($psboundparameters.ContainsKey('ReplaceTypeDefinition')) { $StubCommandSplat['ReplaceTypeDefinition'] = $ReplaceTypeDefinition } $_.Group | New-StubCommand @StubCommandSplat } | ForEach-Object { if ($psboundparameters.ContainsKey('Path')) { $_ | Out-File $filePath -Encoding UTF8 -Append } else { $_ } } } catch { throw } } function New-StubType { <# .SYNOPSIS Generates a class or enum definition. .DESCRIPTION Builds a type definition which represents a class or type which is used to constrain a parameter. .INPUTS System.Type .EXAMPLE New-Stubtype ([IPAddress]) Create a stub representing the System.Net.IPAddress class. .NOTES Change log: 04/04/2017 - Chris Dent - Created. 31/05/2017 - Chris Dent - Nested type handling. #> # This command does not change state. [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] [OutputType([String])] param ( # Generate a stub of the specified type. [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)] [Type]$Type, # If a type is flagged as secondary, member types are rewritten as object to end the type dependency chain. [Parameter(ValueFromPipelineByPropertyName, DontShow)] [Boolean]$IsPrimary = $true, # Exclude the Add-Type wrapper statements. [Switch]$ExcludeAddType ) begin { $definedStubTypes = [Ordered]@{} } process { if ($Type -and -not $definedStubTypes.Contains($Type)) { $stubType = [PSCustomObject]@{ Type = $Type Namespace = $Type.Namespace Definition = $null } $script = New-Object ScriptBuilder #if ($Type.Namespace -ne 'System' -and $Type.Namespace) { # $null = $script.AppendLine(). # AppendFormat('namespace {0}', $Type.Namespace). # AppendLine(). # AppendLine('{') #} if ($Type.MemberType -eq 'NestedType') { if ($definedStubTypes.Contains($Type.DeclaringType)) { $parentClassDefinition = $definedStubTypes[$Type.DeclaringType].Definition } else { $parentClassDefinition = New-StubType $Type.DeclaringType -ExcludeAddType } # "Open" the parent class to allow the nested class to be injected (nested types are not automatically fabricated). $parentClassDefinition = $parentClassDefinition.Trim() -replace '\}$' $null = $script.AppendLines($parentClassDefinition.Split("`n")) } if ($Type.BaseType -eq [Enum]) { if ($Type.CustomAttributes.Count -gt 0 -and $Type.CustomAttributes.Where{ $_.AttributeType -eq [FlagsAttribute] }) { $null = $script.AppendLine('[System.Flags]') } $underlyingType = [Enum]::GetUnderlyingType($Type) $typeName = switch ($underlyingType.Name) { 'Byte' { 'byte' } 'SByte' { 'sbyte' } 'Int16' { 'short' } 'UInt16' { 'ushort' } 'Int32' { 'int' } 'UInt32' { 'uint' } 'Int64' { 'long' } 'UInt64' { 'ulong' } } $null = $script.AppendFormat('public enum {0} : {1}', $Type.Name, $typeName). AppendLine(). AppendLine('{') $names = [Enum]::GetNames($Type) for ($i = 0; $i -lt $names.Count; $i++) { $null = $script.AppendFormat( '{0} = {1}', $names[$i], [Convert]::ChangeType( [Enum]::Parse($Type, $names[$i]), $underlyingType ) ) if ($i -ne $values.Count - 1) { $null = $script.Append(',') } $null = $script.AppendLine() } $null = $script.AppendLine('}') } else { $null = $script.AppendFormat('public class {0}', $Type.Name). AppendLine(). AppendLine('{') if ($IsPrimary) { $members = $Type.GetMembers([BindingFlags]'Public,Instance') | Where-Object MemberType -in 'Field', 'Constructor', 'Property' | Group-Object MemberType | Sort-Object { switch ($_.Name) { 'Field' { 1 } 'Constructor' { 2 } 'Property' { 3 } } } foreach ($memberSet in $members) { $null = $script.AppendFormat('// {0}', $memberSet.Name). AppendLine() $null = switch ($memberSet.Name) { 'Field' { foreach ($field in $memberSet.Group) { $script.AppendFormat('public {0} {1};', (GetTypeName $field.FieldType), $field.Name). AppendLine() } break } 'Constructor' { foreach ($constructor in $memberSet.Group) { $script.AppendFormat('public {0}', $Type.Name) $parameters = foreach ($parameter in $constructor.GetParameters()) { '{0} {1}' -f (GetTypeName $parameter.ParameterType), $parameter.Name } $script.AppendFormat('({0}) {{ }}', $parameters -join ', '). AppendLine() } break } 'Property' { foreach ($property in $memberSet.Group) { $script.AppendFormat('public {0} {1}', (GetTypeName $property.PropertyType), $property.Name). Append(' { get; set; }'). AppendLine() } break } } $null = $script.AppendLine() } # Parse and Create static methods [MethodInfo[]]$methods = $Type.GetMethods([BindingFlags]'Public,Static') | Where-Object { $_.Name -in 'Create', 'Parse' -and $_.ReturnType.Name -eq $Type.Name } if ($methods.Count -gt 0) { $null = $script.AppendLine('// Static methods') foreach ($method in $methods) { $null = $script.AppendFormat('public static {0} {1}', (GetTypeName $method.ReturnType), $method.Name) $parameters = foreach ($parameter in $method.GetParameters()) { '{0} {1}' -f (GetTypeName $parameter.ParameterType), $parameter.Name } $null = $script.AppendFormat('({0})', $parameters -join ', '). AppendLine(). AppendLine('{'). AppendFormat('return new {0}();', $Type.Name). AppendLine(). AppendLine('}') } $null = $script.AppendLine() } # If the type does not implement a constructor which does not require arguments if (-not $Type.GetConstructor(@())) { $null = $script.AppendLine('// Fabricated constructor'). AppendFormat('private {0}() {{ }}', $Type.Name). AppendLine() # Add a CreateTypeInstance static method $null = $script.AppendFormat('public static {0} CreateTypeInstance()', $Type.Name). AppendLine(). AppendLine('{'). AppendFormat('return new {0}();', $Type.Name). AppendLine(). AppendLine('}') } } else { $null = $script.AppendLine('public bool IsSecondaryStubType = true;'). AppendLine(). AppendFormat('public {0}() {{ }}', $Type.Name). AppendLine() } $null = $script.AppendLine('}') } if ($Type.MemberType -eq 'NestedType') { $null = $script.AppendLine('}') } #if ($Type.Namespace -ne 'System' -and $Type.Namespace) { # $null = $script.AppendLine('}') #} $stubType.Definition = $script.ToString().Trim() if ($Type.MemberType -eq 'NestedType') { $definedStubTypes.($Type.DeclaringType) = $stubType $definedStubTypes.$Type = $null } else { $definedStubTypes.$Type = $stubType } } } end { if ($definedStubTypes.Count -gt 0) { $script = New-Object ScriptBuilder if (-not $ExcludeAddType) { $null = $script.AppendLine("Add-Type -IgnoreWarnings -TypeDefinition @'") } $definedStubTypes.Values | Group-Object Namespace | Sort-Object Name | ForEach-Object { if ($_.Name) { $null = $script.AppendFormat('namespace {0}', $_.Name). AppendLine(). AppendLine('{') } $_.Group | Sort-Object { $_.Type.FullName } | ForEach-Object { $null = $script.AppendLines($_.Definition -split '\r?\n'). AppendLine() } if ($_.Name) { $null = $script.AppendLine('}'). AppendLine() } } if (-not $ExcludeAddType) { $null = $script.AppendLine("'@") } return $script.ToString() } } } function InitializeModule { Register-ArgumentCompleter -CommandName New-StubModule -ParameterName FromModule -ScriptBlock { Get-Module | Select-Object -ExpandProperty Name } Register-ArgumentCompleter -CommandName New-StubCommand -ParameterName CommandName -ScriptBlock { Get-Command -CommandType Function, Cmdlet | Select-Object -ExpandProperty Name } } InitializeModule |