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.StubCommandThe 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 IPAddressThe 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:\TempThe 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 $functionBodyThe 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-ExpressionExample 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