How do I issue a hash table key with a point character, such as FlatAppearance.BorderSize
, so that it is correct?
$Button = [System.Windows.Forms.Button] @{
# This is not a valid entry
FlatAppearance.BorderSize = 0
}
$Button = [System.Windows.Forms.Button] @{
# This is not a valid entry too
"FlatAppearance.BorderSize" = 0
}
$Button = [System.Windows.Forms.Button] @{
# This is not a valid entry too too
FlatAppearance = @{ BorderSize = 0 }
}
Of course, I can write it like that
$Button = [System.Windows.Forms.Button] @{}
$Button.FlatAppearance.BorderSize = 0
However, it is more convenient to write inside the hash table. But how? Thanks
Since the Button
type's .FlatAppearance
property is of type FlatButtonAppearance
, there's no good solution in this case, because the FlatButtonAppearance
type has no public, parameterless constructor.
If it did, you would be able to write:
using namespace System.Windows.Forms
$Button = [Button] @{
# !! This is how you would generally do it, but
# !! IN THIS CASE IT DOESN'T WORK, due to lack of an appropriate constructor.
FlatAppearance = [FlatButtonAppearance] @{ BorderSize = 0 }
}
The above syntax is akin to C#'s object initializer syntax, explained in the next section.
For a cast to a type literal ([...]
) from a hashtable (@{ ... }
) or preexisting [pscustomobject]
instance[1] to perform implicit construction (implicit creation of an instance) and then multi-property initialization to work, the following prerequisites must be met:
The target type must have a constructor (possibly among others[2]) that is:
The names of the type's public properties must match the hashtable entries' keys and the entries' values must be have the same or a compatible type as the target properties.
This allows PowerShell to create an instance behind the scenes simply by calling new SomeType()
([SomeType]::new()
in PowerShell syntax), followed by assigning the public properties' values from the hashtable entries of the same name.
Note: Such casts, available in PowerShell v3+, are in effect syntactic sugar for calling the
New-Object
cmdlet with the -Property
parameter; only the latter works in v2.
Aside from consulting a type's documentation, you can easily inspect a type's constructors in PowerShell by calling the PowerShell-supplied static ::new
method without parentheses (()
):
PS> [System.Windows.Forms.FlatButtonAppearance]::new
# NO OUTPUT, which means the type has no public constructors at all.
# [ProcessStartInfo] has several public constructors, among them
# a public parameterless one, so you *can* initialize it by hashtable.
PS> [System.Diagnostics.ProcessStartInfo]::new
OverloadDefinitions
-------------------
System.Diagnostics.ProcessStartInfo new()
System.Diagnostics.ProcessStartInfo new(string fileName)
System.Diagnostics.ProcessStartInfo new(string fileName, string arguments)
To determine a type's public, writeable instance properties:
PS> [System.Windows.Forms.FlatButtonAppearance].GetProperties('Public, Instance') |
? CanWrite | Select-Object Name, PropertyType
Name PropertyType
---- ------------
BorderSize System.Int32
BorderColor System.Drawing.Color
CheckedBackColor System.Drawing.Color
MouseDownBackColor System.Drawing.Color
MouseOverBackColor System.Drawing.Color
[1] For instance, this is useful for objects deserialized from JSON, which become [pscustomobject]
instances; e.g.: $obj = '{ "Text": "Submit" }' | ConvertFrom-Json; $button = [Button] $obj
[2] Edge case: There mustn't also be a single-parameter public constructor with one of the following parameter types, because it would bind the cast operand as-is:
[object]
or@{ ... }
): [hashtable]
or [System.Collections.IDictionary]
[pscustomobject]
instance: [psobject]
or [pscustomobject]
[3] Conveniently, if you define a type (class) without declaring any constructor, you get a public parameterless one by default; e.g., in the following PowerShell example class Foo
implicitly provides such a constructor:
class Foo { [string] $Bar }; $foo = [Foo] @{ Bar = 'None' }