Introduction
I am working on a top secret PowerShell project at the moment. One of the functions that I am writing returns a list of FileInfo objects. These objects get then get passed into another function, which analyzes file names and extensions and dispatches different actions in a wildcard enabled switch statement. I am using PSUnit as unit testing framework.
To make the second function more testable, I wanted to avoid using FileInfo objects as parameters and came up with the idea to clone the FileInfo objects and use only the parameters that my script would actually use. My function now takes as input a list of custom PSObjects instead of FileInfo objects. This makes the function much more testable and the overall design a little more decoupled.
Figure 1: A Plain Old Chameleon
In this Post
The subject of this post is an Advanced PowerShell function that converts any object into a custom PowerShell object. The cloning only considers the property names that are passed in as a parameter to the function. The article covers the following PowerShell features:
- Advanced Functions
- Parameter Sets
- Add-Member ScriptMethod
As an additional feature of my function I wanted first to preserve the type name of the original object. This way I would later be able to recreate the original object based on the type name and, in the case of the FileInfo object, the FullName property. But then I thought that I could actually go the whole nine yards by telling the new clone how to convert itself back to its original object. For this I created a ScriptMethod member that knows how to revert the cloned object back to a FileInfo object. The ScriptMethod member of course is called “Revert”.
TDD and Polymorphism
I found the technique of creating partial clones and getting rid of the dependency to an actual file quite helpful doing TDD (Test Driven Development) in PowerShell. Also I also discovered that it is quite easy to use Polymorphism in PowerShell by specializing a common Member via a function parameter. In this case the Revert ScriptMethod member can do anything necessary to convert the clone back to its original source object.
PowerShell Script
function Clone-AsStub()
{
[CmdletBinding(DefaultParameterSetName="Full")]
param
(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
[PSObject] $ObjectToClone,
[Parameter(Position=1, Mandatory=$false, ValueFromPipeline=$false)]
[ValidateScript( {$_ -ne $Null -and $_.Length -gt 0})]
[string[]] $PropertiesToClone,
[Parameter(Position=2, Mandatory=$false, ValueFromPipeline=$false)]
[switch] $PreserveTypeInfo,
[Parameter(Position=3, Mandatory=$false, ValueFromPipeline=$false, ParameterSetName="Magic")]
[ScriptBlock] $RevertScriptBlock
)
process
{
$PropertyTable = @{}
foreach($Prop in $PropertiesToClone)
{
$PropertyTable[$Prop] = $ObjectToClone.$Prop
}
if($PreserveTypeInfo)
{
$PropertyTable["TypeName"] = $ObjectToClone.GetType().FullName
}
$Clone = New-Object -TypeName "System.Management.Automation.PSObject" `
-Property $PropertyTable
if( $PsCmdlet.ParameterSetName -eq "Magic")
{
$Clone = Add-Member -InputObject $Clone `
-MemberType "ScriptMethod" `
-Name "Revert" `
-Value $RevertScriptBlock `
-PassThru
}
return $Clone
}
}
$FI = get-Item "C:\Spices.txt"
$FI
$FI.GetType().Fullname
$Result = $FI | Clone-AsStub -PropertiesToClone "Name", "FullName", "Extension", "LastWriteTime"
$Result
$Result = $FI | Clone-AsStub -PropertiesToClone "Name", "FullName", "Extension", "LastWriteTime" `
-PreserveTypeInfo
$Result
$Result = $FI | Clone-AsStub -PropertiesToClone "Name", "FullName", "Extension", "LastWriteTime" `
-PreserveTypeInfo `
-RevertScriptBlock {Get-Item -Path $This.FullName}
$Result
$FI = $Result.Revert()
$Result | Get-member
$FI.GetType().FullName
Download
The script file can be downloaded here: CloneAsStubStudy.zip
Ausblick
All I can say is that I am still having fun writing solutions in PowerShell.