A stub command or module is intended for use with tools like Pester.
When Pester to creates a mock the original command must be available. If a command or module is not available a function might be written to resemble the original command.
This module is used to decrease the work required to define a fabricated function by creating a stub from the original.
A stub might be used where:
- A development environment cannot (or should not) install a command or module.
- A build server cannot (or should not) install a command or module required.
Install-Module -Name Indented.StubCommand
The stub command includes the following:
- CmdletBinding attribute declaration
- OutputType attribute declaration
- Param block
- Dynamic param block
- (optional) custom function body
The param block is fabricated using the ProxyCommand class.
The dynamic param block is re-built to expose the parameter names (along with attributes).
If a command defines a parameter to be of a fixed .NET type, and the .NET type is not ordinarily available a stub type is created.
A list of known assemblies is included with this module. If a type is defined within a known, widely available, assembly it is not recreated.
A stub module creates stub commands and types from the content of a module.
The following command can be used to create a stub of the Test-Path command.
New-StubCommand (Get-Command Test-Path)
The generated stub is shown (without help content) below.
function Test-Path {
[OutputType([System.Boolean])]
param (
[Parameter(ParameterSetName='Path', Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]
${Path},
[Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${LiteralPath},
[string]
${Filter},
[string[]]
${Include},
[string[]]
${Exclude},
[Alias('Type')]
[Microsoft.PowerShell.Commands.TestPathType]
${PathType},
[switch]
${IsValid},
[Parameter(ValueFromPipelineByPropertyName=$true)]
[pscredential]
[System.Management.Automation.CredentialAttribute()]
${Credential}
)
dynamicparam {
$parameters = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# OlderThan
$attributes = New-Object System.Collections.Generic.List[Attribute]
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attributes.Add($attribute)
$parameter = New-Object System.Management.Automation.RuntimeDefinedParameter("OlderThan", [System.Nullable`1[System.DateTime]], $attributes)
$parameters.Add("OlderThan", $parameter)
# NewerThan
$attributes = New-Object System.Collections.Generic.List[Attribute]
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attributes.Add($attribute)
$parameter = New-Object System.Management.Automation.RuntimeDefinedParameter("NewerThan", [System.Nullable`1[System.DateTime]], $attributes)
$parameters.Add("NewerThan", $parameter)
return $parameters
}
}
The following command re-creates the TestPathType enumeration.
New-StubType "Microsoft.PowerShell.Commands.TestPathType"
With the generated enum:
Add-Type @'
namespace Microsoft.PowerShell.Commands
{
public enum TestPathType : int
{
Any = 0,
Container = 1,
Leaf = 2
}
}
'@
Stub types are created using the same command.
New-StubType IPAddress
The result is a class which presents the constructors, fields, properties, and Create or Parse static methods.
namespace System.Net
{
public class IPAddress
{
// Constructor
public IPAddress(System.Int64 newAddress) { }
public IPAddress(System.Byte[] address, System.Int64 scopeid) { }
public IPAddress(System.Byte[] address) { }
// Property
public System.Int64 Address { get; set; }
public System.Net.Sockets.AddressFamily AddressFamily { get; set; }
public System.Int64 ScopeId { get; set; }
public System.Boolean IsIPv6Multicast { get; set; }
public System.Boolean IsIPv6LinkLocal { get; set; }
public System.Boolean IsIPv6SiteLocal { get; set; }
public System.Boolean IsIPv6Teredo { get; set; }
public System.Boolean IsIPv4MappedToIPv6 { get; set; }
// Static methods
public static System.Net.IPAddress Parse(System.String ipString)
{
return new IPAddress();
}
// Fabricated constructor
private IPAddress() { }
public static IPAddress CreateTypeInstance()
{
return new IPAddress();
}
}
}
The following generates stubs for all commands and types in the module ActiveDirectory.
New-StubModule -FromModule ActiveDirectory -Path C:\Temp
The following generates stubs with a function body for all commands.
$functionBody = {
throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand
}
New-StubModule -FromModule ActiveDirectory -Path C:\Temp -FunctionBody $functionBody
The following generates stubs and for all commands the types starting with
Microsoft.ActiveDirectory.Management
are replaced with System.Object
.
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'
}
)
Examples of already created stub modules using this module are available:
https://github.com/indented-automation/Indented.StubCommand/tree/master/examples
You can inject any function body, as a scriptblock, into the generated commands.
The scriptblock cannot contain a Param or DynamicParam block, but it can contain begin-process-end blocks.
If specifying a function body to New-StubModule, all generated functions will have an identical body.
Example of creating a wrapper function for Write-Debug which directs debug output to a log file if a global variable is present:
$CustomBody = {
if ($Global:LOGFILE) {
$Message | Out-File $Global:LOGFILE -Append
} else {
Microsoft.PowerShell.Utility\Write-Debug $Message
}
}
New-StubCommand (Get-Command Write-Debug) -FunctionBody $CustomBody | Invoke-Expression
Example of creating a wrapper function that executes commands through an API:
$CustomBody = {
Invoke-RestMethod 'https://scriptrunner.contoso.com/invoke/' -Headers @{
scriptname = $MyInvocation.MyCommand.Name
scriptparameters = $PSBoundParameters
auth_token = $(Get-CorpToken)
}
}
New-StubModule CorpScriptLibrary -FunctionBody $CustomBody