# encoding: ascii
# api: powershell
# title: New-Struct 3
# description: A code-generating and emitting magic function for creating type-safe struct classes for use in PowerShell!
# version: 0.1
# type: function
# author: Joel Bennett
# license: CC0
# function: New-Struct
# x-poshcode-id: 3146
# x-archived: 2016-03-03T14:51:04
# x-published: 2012-01-05T20:58:00
#
# Now supports PowerShell 3, and can generate related struct types (where types are properties of one another).
#
function New-Struct {
#.Synopsis
# Creates Struct types from a list of types and properties
#.Description
# A wrapper for Add-Type to create struct types.
#.Example
# New-Struct Song {
# [string]$Artist
# [string]$Album
# [string]$Name
# [TimeSpan]$Length
# } -CreateConstructorFunction
#
# Description
# -----------
# Creates a "Song" type with strongly typed Artist, Album, Name, and Length properties, with a simple constructor and a constructor function
#.Example
# New-Struct @{
# >> Product = { [string]$Name; [double]$Price; }
# >> Order = { [Guid]$Id; [Product]$Product; [int]$Quantity }
# >> Customer = {
# >> [string]$FirstName
# >> [string]$LastName
# >> [int]$Age
# >> [Order[]]$OrderHistory
# >> }
# >> }
# >>
#
# Description
# -----------
# To create a series of related struct types (where one type is a property of another type), you need to use the -Types hashtable parameter set. That way, all of the types will compiled together at once, so the compiler will be able to find them all.
#
[CmdletBinding(DefaultParameterSetName="Multiple")]
param(
# The name of the TYPE you are creating. Must be unique per PowerShell session.
[ValidateScript({
if($_ -notmatch '^[a-z][a-z1-9_]*$') {
throw "'$_' is invalid. A valid name identifier must start with a letter, and contain only alpha-numeric or the underscore (_)."
}
return $true
})]
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName = "Single")]
[string]$Name
,
# A Scriptblock full of "[Type]$Name" definitions to show what properties you want on your Struct type
[Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName = "Single")]
[ScriptBlock]$Property
,
# A Hashtable in the form @{Name={Properties}} with multiple Names and Property Scriptblocks to define related structs (see example 2).
[Parameter(Position=0, Mandatory=$true, ParameterSetName = "Multiple")]
[HashTable]$Types
,
# Generate a New-StructName shortcut function for each New-Object StructName
[Alias("CTorFunction","ConstructorFunction")]
[Switch]$CreateConstructorFunction
,
# Output the defined type(s)
[Switch]$PassThru
)
begin {
if($PSCmdlet.ParameterSetName -eq "Multiple") {
$Structs = foreach($key in $Types.Keys) {
New-Object PSObject -Property @{Name=$key;Property=$Types.$key}
}
Write-Verbose ($Structs | Out-String)
$Structs | New-Struct -Passthru:$Passthru -CreateConstructorFunction:$CreateConstructorFunction
} else {
$code = "using System;`nusing System.Collections;`nusing System.Management.Automation;`n"
$function = ""
}
}
process {
if($PSCmdlet.ParameterSetName -ne "Multiple") {
$parserrors = $null
$tokens = [System.Management.Automation.PSParser]::Tokenize( $Property, [ref]$parserrors ) | Where-Object { "Newline","StatementSeparator" -notcontains $_.Type }
# CODE GENERATION MAGIKS!
$Name = $Name.ToUpper()[0] + $Name.SubString(1)
$ctor = @()
$setr = @()
$prop = @()
$parm = @()
$cast = @()
$hash = @()
$2Str = @()
$(while($tokens.Count -gt 0) {
$typeToken,$varToken,$tokens = $tokens
if($typeToken.Type -ne "Type") {
throw "Syntax error on line $($typeToken.StartLine) Column $($typeToken.Start). Missing Type. The Struct Properties block must contain only statements of the form: [Type]`$Name, see Get-Help New-Struct -Parameter Properties.`n$($typeToken | Out-String)"
} elseif($varToken.Type -ne "Variable") {
throw "Syntax error on line $($varToken.StartLine) Column $($varToken.Start). Missing Name. The Struct Properties block must contain only statements of the form: [Type]`$Name, see Get-Help New-Struct -Parameter Properties.`n$($typeToken | Out-String)"
}
$varName = $varToken.Content.ToUpper()[0] + $varToken.Content.SubString(1)
$varNameLower = $varName.ToLower()[0] + $varName.SubString(1)
try {
Write-Verbose "TypeToken: $($typeToken.Content) $varName"
if($PSVersionTable.PSVersion.Major -lt 3) {
$typeName = Invoke-Expression "[$($typeToken.Content)].FullName"
} else {
$typeName = Invoke-Expression "$($typeToken.Content).FullName"
}
} catch {
## It's probably a reference to another struct, so just put the name in
if($PSVersionTable.PSVersion.Major -lt 3) {
$typeName = $typeToken.Content
} else {
$typeName = $typeToken.Content -replace '\[(.*)\]','$1'
}
}
Write-Verbose "Type Name: $typeName $varName"
$prop += ' public {0} {1};' -f $typeName,$varName
$setr += ' {0} = {1};' -f $varName,$varNameLower
$ctor += '{0} {1}' -f $typeName,$varNameLower
$cast += ' if(input.Properties["{0}"] != null){{ output.{0} = ({1})input.Properties["{0}"].Value; }}' -f $varName,$typeName
$hash += ' if(hash.ContainsKey("{0}")){{ output.{0} = ({1})hash["{0}"]; }}' -f $varName,$typeName
$2Str += '"{0} = [{1}]\"" + {0}.ToString() + "\""' -f $varName, $typeName
if($CreateConstructorFunction) {
$parm += '[{0}]${1}' -f $typeName,$varName
}
})
$code += @"
public struct $Name {
$($prop -join "`n")
public $Name ($( $ctor -join ","))
{
$($setr -join "`n")
}
public static implicit operator $Name(Hashtable hash)
{
$Name output = new $Name();
$($hash -join "`n")
return output;
}
public static implicit operator $Name(PSObject input)
{
$Name output = new $Name();
$($cast -join "`n")
return output;
}
public override string ToString()
{
return "@{" + $($2Str -join ' + "; " + ') + "}";
}
}
"@
if($CreateConstructorFunction) {
$function += @"
Function global:New-$Name {
[CmdletBinding()]
param(
$( $parm -join ",`n" )
)
New-Object $Name -Property `$PSBoundParameters
}
"@
}
}}
end {
if($PSCmdlet.ParameterSetName -ne "Multiple") {
Write-Verbose "C# Code:`n$code"
Write-Verbose "PowerShell Code:`n$function"
Add-Type -TypeDefinition $code -PassThru:$Passthru -ErrorAction Stop
if($CreateConstructorFunction) {
Invoke-Expression $function
}
}}}