indented-automation / indented.stubcommand Goto Github PK
View Code? Open in Web Editor NEWLicense: MIT License
License: MIT License
Something in how it surfaces parameters I assume.
new-stubcommand -commandname test-connection -functionbody {'this works!'}
New-StubCommand : Cannot compare "System.Management.Automation.ParameterMetadata" because it is not IComparable.
At C:\Users\jgrote\Documents\WindowsPowerShell\Modules\Indented.StubCommand\1.4.0\Indented.StubCommand.psm1:347 char:40
- ... Get-Command $CommandName | New-StubCommand @PSBoundParameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- CategoryInfo : InvalidOperation: (:) [Write-Error], RuntimeException
- FullyQualifiedErrorId : NotIcomparable,New-StubCommand
Verify the enum handler correctly parses enums where a value has two names.
Nice to have: Make enums look pretty.
Example:
Get-Recipient user
Has WarningAction as the first positional parameter instead of the correct identity
Cannot bind parameter 'WarningAction'. Cannot convert value "user" to type "System.Management.Automation.ActionPreference". Error: "Unable to match the identifier name satinder to a valid enumerator name. Specify one of the following enumerator names and try again:
SilentlyContinue, Stop, Continue, Inquire, Ignore, Suspend"
At C:\Users\jgrote\AppData\Local\Temp\tmp_llmiwitj.mxx\tmp_llmiwitj.mxx.psm1:29118 char:29
+ @clientSideParameters `
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-Command], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.InvokeCommandCommand
While get-recipient -identity user
works fine.
The stub cmdlet Move-ADDirectoryServer
passes a string in the parameter Site
. But the parameter Site
is of the type Microsoft.ActiveDirectory.Management.ADReplicationSite
which is a stub type.
When using an assert like below, the ToString()
method does not return anything, neither does $Site.Name
which is a property of the type Microsoft.ActiveDirectory.Management.ADReplicationSite
.
Example of assert that fails.
Assert-MockCalled -CommandName Move-ADDirectoryServer -Times 1 -ParameterFilter {
$Site.ToString() -eq $correctSiteName
}
The stub cmdlet looks like this.
function Move-ADDirectoryServer {
<#
.SYNOPSIS
Move-ADDirectoryServer [-Identity] <ADDirectoryServer> [-Site] <ADReplicationSite> [-WhatIf] [-Confirm] [-AuthType <ADAuthType>] [-Credential <pscredential>] [-Server <string>] [<CommonParameters>]
#>
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=219321')]
param ( )
dynamicparam {
$parameters = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# AuthType
$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("AuthType", [Microsoft.ActiveDirectory.Management.ADAuthType], $attributes)
$parameters.Add("AuthType", $parameter)
# Credential
$attributes = New-Object System.Collections.Generic.List[Attribute]
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attributes.Add($attribute)
$attribute = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute
$attributes.Add($attribute)
$attribute = New-Object System.Management.Automation.CredentialAttribute
$attributes.Add($attribute)
$parameter = New-Object System.Management.Automation.RuntimeDefinedParameter("Credential", [System.Management.Automation.PSCredential], $attributes)
$parameters.Add("Credential", $parameter)
# Identity
$attributes = New-Object System.Collections.Generic.List[Attribute]
$attribute = New-Object System.Management.Automation.ValidateNotNullAttribute
$attributes.Add($attribute)
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attribute.Position = 0
$attribute.Mandatory = $True
$attribute.ValueFromPipeline = $True
$attributes.Add($attribute)
$parameter = New-Object System.Management.Automation.RuntimeDefinedParameter("Identity", [Microsoft.ActiveDirectory.Management.ADDirectoryServer], $attributes)
$parameters.Add("Identity", $parameter)
# Server
$attributes = New-Object System.Collections.Generic.List[Attribute]
$attribute = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute
$attributes.Add($attribute)
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attributes.Add($attribute)
$parameter = New-Object System.Management.Automation.RuntimeDefinedParameter("Server", [System.String], $attributes)
$parameters.Add("Server", $parameter)
# Site
$attributes = New-Object System.Collections.Generic.List[Attribute]
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attribute.Position = 1
$attribute.Mandatory = $True
$attributes.Add($attribute)
$attribute = New-Object System.Management.Automation.ValidateNotNullAttribute
$attributes.Add($attribute)
$parameter = New-Object System.Management.Automation.RuntimeDefinedParameter("Site", [Microsoft.ActiveDirectory.Management.ADReplicationSite], $attributes)
$parameters.Add("Site", $parameter)
return $parameters
}
end {
throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand
}
}
The stub type ADReplicationSite looks like this.
public class ADReplicationSite
{
// Constructor
public ADReplicationSite() { }
public ADReplicationSite(System.String identity) { }
public ADReplicationSite(System.Guid guid) { }
public ADReplicationSite(Microsoft.ActiveDirectory.Management.ADObject identity) { }
public ADReplicationSite(Microsoft.ActiveDirectory.Management.ADDirectoryServer directoryServer) { }
// Property
public System.String DistinguishedName { get; set; }
public System.String Name { get; set; }
public System.String ObjectClass { get; set; }
public System.Nullable<System.Guid> ObjectGuid { get; set; }
public System.Collections.ICollection PropertyNames { get; set; }
public System.Collections.Generic.ICollection<System.String> AddedProperties { get; set; }
public System.Collections.Generic.ICollection<System.String> RemovedProperties { get; set; }
public System.Collections.Generic.ICollection<System.String> ModifiedProperties { get; set; }
public System.Int32 PropertyCount { get; set; }
public Microsoft.ActiveDirectory.Management.ADPropertyValueCollection Item { get; set; }
}
So the above assert failed because ToString()
does not exist in the stub type, and $Site.Name does not contain the string value passed to the parameter
Site, of the cmdlet Move-ADDirectoryServer, because there are no logic in the stub class
ADReplicationSite` to handle that.
If I add logic to the class ADReplicationSite
constructor that takes a string as argument, and change the test code to assert on $Site.Name
then the assert works. I could have added the ToString()
method to return the Site.Name
property, but that is more uncertain if that is what the real ToString()
method does.
Changed logic in the class ADReplicationSite
. Just showing the relevant parts.
public class ADReplicationSite
{
// Constructor
...
public ADReplicationSite(System.String identity) { this.Name = identity; }
...
// Property
...
public System.String Name { get; set; }
...
}
Changed logic in the assert in the test.
Assert-MockCalled -CommandName Move-ADDirectoryServer -Times 1 -ParameterFilter {
$Site.Name -eq $correctSiteName
}
The OutputType attribute trusts the name property to hold a .NET type name. This assumption is flawed as the OutputType attribute may use a string.
This problem is exhibits when creating a stub of a module based on a WMI class (cdxml module) such as the Storage module.
OutputType must fill .NET types from the Type property. If Type is empty, use Name as a string.
FEATURE: Simulate PS type conversion for type stubs
Constructors should more accurately reflect the constructors on the source object
Static create methods should be re-implemented.
Static parse methods should be re-implemented.
As the internal logic behind a methods and constructors cannot be reasonably implemented supporting these is more of an effort to ensure parameter type binding is reasonably accurately simulated.
FEATURE: Nested or sub classes should be supported
The stub type should be able to determine if a class is a sub-class
The outer and inner types should be fabricated
Incomplete recreation is demonstrated by:
Add-Type -TypeDefinition '
public class outerClass
{
public class innerClass {
public string Inner = "inner";
public innerClass() { }
}
public string Outer = "outer";
public outerClass() { }
}
'
New-StubType ([outerClass+innerClass])
The example created type
if (-not ("Microsoft.Dism.Commands.LogLevel" -as [Type])) {
Add-Type '
namespace Microsoft.Dism.Commands
{
public enum LogLevel : int
{
Errors = 0,
Warnings = 1,
WarningsInfo = 2
}
}
'
}
may return warnings saying "the generated type does not define public methods or properties" (translated to english). This may disturb pester results.
if (-not ("Microsoft.Dism.Commands.LogLevel" -as [Type])) {
Add-Type '
namespace Microsoft.Dism.Commands
{
public enum LogLevel : int
{
Errors = 0,
Warnings = 1,
WarningsInfo = 2
}
}
' -IgnoreWarnings
}
Does not return this warning.
Scenario: I use Requires to dictate a specific version of a required module
Each stub module must include a manifest describing the source module version
Properties, fields, and values referencing pointer types should be re-written as IntPtr (KDS module).
FEATURE: Generated functions should contain a copy of help content
Help content should include:
Synopsis
Parameter specific help
Inclusion of help content in a stub should be optional (at command level)
FEATURE: I want to create an instance of a stub object
The instance should include a reasonable copy of the public properties and fields from the source object
Fabricated properties do not need to be backed by a field
Including property members may increase the number of required types. Whether or not there is benefit in this needs to be explored.
Initially I property types derived from a foreign assembly (which is not already stubbed) will be replaced with "object".
My use case is writing proxy commands, but I think this applies to other use cases.
Consider the invocation "Get-WmiObject -Class Win32_ComputerSystem"
Microsoft.PowerShell.Management\Get-WmiObject returns with this call. The generated stub command for Get-WmiObject throws "Parameter set cannot be resolved using the specified named parameters."
I think this is because of DefaultParameterSetName not being implemented.
I ran the following command
New-StubModule -FromModule ActiveDirectory -Path C:\projects\stub -FunctionBody {
throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand
}
When using the stubs in the unit tests, the type Microsoft.ActiveDirectory.Management.Commands.ValidateNotNullOrEmptyADEntityAttribute
was missing (not sure why). So I generated that with
New-StubType -Type Microsoft.ActiveDirectory.Management.Commands.ValidateNotNullOrEmptyADEntityAttribute
Which gave me the following result, which I just copied into the correct namespace in the stub module (the namespace already existed and contained enums).
Add-Type -IgnoreWarnings -TypeDefinition @'
namespace Microsoft.ActiveDirectory.Management.Commands
{
public class ValidateNotNullOrEmptyADEntityAttribute
{
// Constructor
public ValidateNotNullOrEmptyADEntityAttribute() { }
// Property
public System.Object TypeId { get; set; }
}
}
'@
When running the tests again I get the following error when Pester tries to use the mock, when Pester is building the dynamic parameters.
MethodException: Cannot find an overload for "Add" and the argument count: "1".
CmdletInvocationException: Cannot find an overload for "Add" and the argument count: "1".
ParameterBindingException: Cannot retrieve the dynamic parameters for the cmdlet. Cannot find an overload for "Add" and the argument count: "1".
The same error can be reproduced by the following code.
Add-Type -IgnoreWarnings -TypeDefinition @'
namespace Microsoft.ActiveDirectory.Management.Commands
{
public class ValidateNotNullOrEmptyADEntityAttribute
{
// Constructor
public ValidateNotNullOrEmptyADEntityAttribute() { }
// Property
public System.Object TypeId { get; set; }
}
}
'@
$attributes = New-Object System.Collections.Generic.List[Attribute]
$attribute = New-Object Microsoft.ActiveDirectory.Management.Commands.ValidateNotNullOrEmptyADEntityAttribute
$attributes.Add($attribute)
What is missing from the stub type output for it to work to be added into an array?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.