Skip to content

Instantly share code, notes, and snippets.

@JustinGrote
Last active October 20, 2021 00:14
Show Gist options
  • Save JustinGrote/d3cc6baf626bb04bf6d965c3df8a6371 to your computer and use it in GitHub Desktop.
Save JustinGrote/d3cc6baf626bb04bf6d965c3df8a6371 to your computer and use it in GitHub Desktop.
#requires -version 7.2
using namespace system.collections.generic
using namespace system.reflection
function record {
param (
[Parameter(Mandatory)][String]$Name,
[Parameter(Mandatory)][hashtable]$Properties,
[switch]$ReadOnly
)
[text.stringbuilder]$recordDef = ''
$Properties.Values.foreach{
$namespace = $_ -is [Type] ? $PSItem.Namespace : $PSItem.Gettype().NameSpace
[void]$recordDef.AppendLine("using $namespace;")
}
if ($ReadOnly) {$ReadOnlyString = ' readonly'}
[string]$recordDefTemplate = "public$ReadOnlyString record struct $Name ({0});"
$primitiveTypes = @(
[bool]
[byte]
[sbyte]
[char]
[decimal]
[double]
[float]
[int]
[uint]
[long]
[ulong]
[short]
[ushort]
)
[string]$propertyDef = $Properties.GetEnumerator().foreach{
if ($_.value -is [Type]) {
'{0} {1}' -f $PSItem.Value.Name, $PSItem.Name
} elseif ($_.value -is [string]) {
'{0} {1} = "{2}"' -f 'string', $PSItem.Name, $PSItem.Value
} elseif ($_.value.gettype() -in $primitiveTypes) {
'{0} {1} = {2}' -f $_.value.gettype().Name, $PSItem.Name, $PSItem.Value
} else {
throw "{0}: {1} is a non-primitive type and is not supported to have a default value via this command. You may still define a non-default value by specifying '{0} = [{1}]' instead" -f $_.name, $_.value.gettype().Fullname
}
} -join ', '
[void]$recordDef.AppendLine(($recordDefTemplate -f $propertyDef))
Write-Debug "Adding Type $Name using type definition: `n$recordDef"
Add-Type -TypeDefinition $recordDef
}
#Simple Record definition
record forecast @{
lowTemp = [string]
highTemp = [string]
conditions = [string]
}
#Creation Method 1
$myForecast = [forecast]::new(36,45,'sunny')
#Creation Method 2
[forecast]$myForecast2 = @{
lowTemp = 36
highTemp = 45
conditions = 'cloudy'
}
#Creation Method 3
$myForecast3 = [forecast]@{
lowTemp = 88
highTemp = 150
conditions = 'ON FIRE'
}
#These have a fancy ToString already built in:
"$myForecast"
#Output: forecast { conditions = sunny, highTemp = 45, lowTemp = 35 }
#Lets try them as a function parameter
function Get-Weather ([forecast]$foreCast) {
"Forecast is {0}/{1} with {2} conditions" -f $foreCast.lowTemp, $foreCast.highTemp, $foreCast.conditions
}
#I can use a hashtable in place of the type and not get an error, it will cast safely
Get-Weather @{
lowTemp = 30
highTemp = 40
conditions = 'sunny'
}
#Output: Forecast is 30/40 with sunny conditions
#I can use a completely different object and as long as the same properties are used, it will work
Get-Weather ([PSCustomObject]@{
lowTemp = 30
highTemp = 40
conditions = 'sunny'
})
#Output: Forecast is 30/40 with sunny conditions
#Different properties will fail however so this is still "safe"
Get-Weather ([PSCustomObject]@{
lowTemp = 30
highTemp = 40
conditions = 'sunny'
wrongproperty = 'nope'
})
#Error: Cannot process argument transformation on parameter 'foreCast'. Cannot convert value '@{lowTemp=30; highTemp=40; conditions=sunny; wrongproperty=nope}' to type 'forecast'. Error: 'Cannot convert the '@{lowTemp = 30; highTemp = 40; conditions = sunny; wrongproperty = nope }' value of type 'System.Management.Automation.PSCustomObject" to type "forecast"."
#Alternative Record definition which will detect the type of value defaults
record defaultForecast @{
lowtemp = 35
hightemp = 45
conditions = 'sunny'
}
[string][defaultForecast]::new()
#Output: defaultForecast { conditions = sunny, highTemp = 45, lowTemp = 35 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment