Friday, July 1, 2011

PowerShell Custom Object Factory

I needed to create custom objects with specific properties for programming against a WPF GUI, because WPF programming is simplest when it binds properties of controls to properties of an object. But I loathed the thought of writing a function to create a custom object, and having to duplicate the parameter name as a Property name in the new-object call. I also wanted to have default values for the object’s Properties.

The following solution is a template – Use it when you need a New-BlahBlahObject function. Simply add the parameters you want to the Param() block, and this function will hand you back a PSObject with the Parameters turned into Properties. As a bonus, the ParameterSetName property allows you to use this as a kind of object factory – depending on which arguments you supply, different Properties will get created, and the value of the ParameterSetName will tell you which properties are present.

You can do a lot more customization before the call to new-object in the last line of the function, by modifying the $p2 string. You can also add functions to the object before you return it, like Add, so you can “add” two of these custom objects together.

[Author’s note: If anybody know how to add a “code” widget to a Blogger Blog, please tell me so in a comment!. The following formatting is terrible… I hope word-wrap doesn’t prevent cut’n’paste from working]

function New-CustomDemoObject {
  [CmdletBinding(DefaultParametersetName='All')]
  Param (
   [parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $Workbook = 'T1.xlsx'
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][switch] $isVisible
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][switch] $isKeepOpen
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][string] $SortDirection = 'Ascending'
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][string[]] $DatabaseConnectionStrings = @('connecstr1','connectstr2')
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $ParameterWithDefaultArray = @('connecstr5','connectstr6')
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][hashtable] $CommandStringsHash  = @{cmd1=@{cmdstr='a command';vo=1;so=2};cmd2=@{cmdstr='b command';vo=2;so=1}}
  ,[parameter(ParameterSetName='PathToDir',ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $PathToDir = 'C:/Logs'
  ,[parameter(ParameterSetName='PathToZip',ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $PathToZip = 'C:/Logs/Backups'
  ,[parameter(ParameterSetName='PathToZip',ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $PathInZip = './'
  ,[parameter(Position=0, ValueFromRemainingArguments=$true)] $RemainingArgs
  )
  # Snippet that creates an object from the parameters
  # Create a list of the common parameters as a RegExp match string
  $cp = iex 'function gcp{[CmdletBinding()]Param()$p1=@();((gci Function:$($pscmdlet.MyInvocation.MyCommand)).Parameters).Keys|%{$p1+=$_};$p1|%{"^$_`$"}};[string]::join("|",(gcp))'
  # Get a list of this function's parameters, eliminate the commonparameters, make a hash with the parameter name and type
  $p1=@{};((gci Function:$($pscmdlet.MyInvocation.MyCommand)).Parameters).Keys|?{$_ -notmatch $cp}|%{$p1.$_=((gci Function:$($pscmdlet.MyInvocation.MyCommand)).Parameters).$_.ParameterType.Name}
  # Iterate this function's real parameters, turn switches to bools, remove ActionPreference (and you can do whatever else you want to in this loop)
  $p2=$p1.Clone();$p1.Keys|%{$key=$_;switch ($p1.$_) {'SwitchParameter' {$p2.$key='Bool';break}'ActionPreference' {$p2.Remove($key);break}}}
  # Add the parameterset as a property (Tells you which parameters are present), and create a new object using the hash of argument names and values
  # This version of the last line creates strongly-typed Properties, but won't handle Parameters/Properties of type arrary or hashtable
  # new-object psObject -Property (&(iex $('{@{ParameterSet=[string]'+ "'$($PsCmdlet.ParameterSetName)';" +[string]::join(";",($p2.Keys|%{("{0}="-f $_+'['+$p2.$_+']'+'"$'+$_ +'"')}))+'}}')))
  # This version of the last line creates untyped Properties, and does handle hashtables and arrays. see also this blog https://jamesone111.wordpress.com/2011/01/11/why-it-is-better-not-to-use-powershell-parameter-validation/
  new-object psObject -Property (&(iex $('{@{ParameterSet=[string]'+"'$($PsCmdlet.ParameterSetName)';"+[string]::join(";",($p2.Keys|%{("{0}="-f $_+"`$$_")}))+'}}')))
}

$newobj1 = New-CustomDemoObject # If none of the specific ParamterSet arguments are supplied, the default ParamterSetName 'All' causes all parameters to be created on the PsObject as empty Properties
$newobj2 = New-CustomDemoObject -Workbook 'another.xlsx' -PathToDir 'D:\Logs'
$newobj3 = New-CustomDemoObject -isVisible -PathToZip 'D:\Logs\Backups'
#$newobj4 = New-CustomDemoObject -Workbook 'another.xlsx' -PathToZip 'D:\Logs\Backups' -PathToDir 'D:\Logs' # Uncomment this - you will see an error about paramterset cannot be resolved

# Type $newobj1 ( and $newobj2, and  $newobj3) at the comamnd prompt to see the objects that got created

No comments:

Post a Comment