--- title: 'PowerShell Read Only Class Properties' date: 2017-07-19T11:15:47+01:00 showDateUpdated: true lastmod: 2022-01-11T14:37:54+01:00 draft: false categories: ['PowerShell'] tags: ['class', 'read-only', 'properties'] # lastmod: 2021-12-23T11:15:47+01:00 # showDateUpdated: true # custom overrides for pages # showDate: false # showAuthor: false # showWordCount: false # showReadingTime: false # showEdit: false # sharingLinks: [null] --- {{< note >}} If you're not familiar with Powershell classes I suggest you reading this awesome blog article from Michael Willis [Powershell v5 Classes & Concepts](https://xainey.github.io/2016/powershell-classes-and-concepts/). It covers everything your need to know about classes. {{< /note >}} ## Background So lets start with some thoughts about the underlying situation. We assume there is a complex class structure and we don't want others to mess with our data. Or maybe we need to protect our data but still need to expose it. Therefore we can't define our class like the following example: ```powershell Class DeathStar { [String]$Class = 'Space battle station' [Int]$Width = '160000' [String[]]$HyperDriveRating = @('Class 4', 'Class 20') $Crew = @{ ImperialNavy = 342953 Stormtroopers = 25984 } [String]$Ready = $null DeathStar () { $this.Ready = $true } } ``` With this class definition we can easily create an instance and take a look at the default displayed properties: ```console ~> $DeathStarOne = [DeathStar]::New() ~> $DeathStarOne Class : Space battle station Width : 160000 HyperDriveRating : {Class 4, Class 20} Crew : {Stormtroopers, ImperialNavy} Ready : True ``` Furthermore the rebellion could manipulate sensitive data of our death star if we would publish the death start class like this. Let's do this with the `Ready` property and the available crew members: ```console ~> $DeathStarOne.Ready = $false ~> $DeathStartOne Class : Space battle station Width : 160000 HyperDriveRating : {Class 4, Class 20} Crew : {Stormtroopers, ImperialNavy} State : False ~> $DeathStarOne.Crew.ImperialNavy = 0 ~> $DeathStarOne.Crew Name Value ---- ----- Stormtroopers 0 ImperialNavy 342953 ``` Although Powershell doesn't have real readonly class properties, we can mimic them in two elegant ways: - Class methods as getter and setter functions - Script properties with getter and setter functions. Both of them rely to the same concept of using C# like getter and setter functions. But reading and writing the values differs. ## Using class methods To isolate our sensitive data we first need to mark the sensitive properties as `hidden`. Hidden properties won't get listed as object members unless we use the `Get-Member` function with the `-Force` switch. Additionally I like adding an underscore _(`_`)\_ as prefix to my variable names. We use the properties with underscores alter on. ```powershell Class DeathStar { [String]$Class = 'Space battle station' [Int]$Width = '160000' [String[]]$HyperDriveRating = @('Class 4', 'Class 20') hidden $_Crew = @{ ImperialNavy = 342953 Stormtroopers = 25984 } hidden [String]$_Ready = $null DeathStar () { $this._Ready = $true } } ``` With the customized class definition we check again our default output and members: ```console ~> $DeathStarOne = [DeathStar]::New() ~> $DeathStarOne | Format-List Class : Space battle station Width : 160000 HyperDriveRating : {Class 4, Class 20} ``` ```console ~> $DeathStarOne | Get-Member TypeName: DeathStar Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() Class Property string Class {get;set;} HyperDriveRating Property string[] HyperDriveRating {get;set;} Width Property int Width {get;set;} ``` ```console ~> $DeathStarOne | Get-Member -Force TypeName: DeathStar Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() Class Property string Class {get;set;} HyperDriveRating Property string[] HyperDriveRating {get;set;} Width Property int Width {get;set;} pstypenames CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] pstypenames{... psadapted MemberSet psadapted {Class, Width, HyperDriveRating, get_Class, set_Class, get_Width, set_Width, get_HyperDriveRating, set_HyperDriveRating, get__Crew, set__Cre... psbase MemberSet psbase {Class, Width, HyperDriveRating, get_Class, set_Class, get_Width, set_Width, get_HyperDriveRating, set_HyperDriveRating, get__Crew, set__Crew, ... psextended MemberSet psextended {} psobject MemberSet psobject {BaseObject, Members, Properties, Methods, ImmediateBaseObject, TypeNames, get_Members, get_Properties, get_Methods, get_ImmediateBaseObject,... Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() get_Class Method string get_Class() get_HyperDriveRating Method string[] get_HyperDriveRating() get_Width Method int get_Width() get__Crew Method System.Object get__Crew() get__Ready Method string get__Ready() set_Class Method void set_Class(string ) set_HyperDriveRating Method void set_HyperDriveRating(string[] ) set_Width Method void set_Width(int ) set__Crew Method void set__Crew(System.Object ) set__Ready Method void set__Ready(string ) ToString Method string ToString() Class Property string Class {get;set;} HyperDriveRating Property string[] HyperDriveRating {get;set;} Width Property int Width {get;set;} _Crew Property System.Object _Crew {get;set;} _Ready Property string _Ready {get;set;} ``` Next step is to add individual getter and setter methods to access the hidden variable. In our scenario we don't need the setter part, because that's the action we want to restrict. But to show you the syntax I added the `SetCrew` method. ```powershell Class DeathStar { [String]$Class = 'Space battle station' [Int]$Width = '160000' [String[]]$HyperDriveRating = @('Class 4', 'Class 20') hidden $_Crew = @{ ImperialNavy = 342953 Stormtroopers = 25984 } hidden [string]$_Ready = $null DeathStar () { $this._Ready = $true } [string] GetReady () { return $this._Ready } [string] GetCrew ([string]$Type) { return $this._Crew.$Type } SetCrew ([string]$Type, [Int]$Value) { Write-Warning 'Apology accepted, Captain Needa.' $this._Crew.$Type = $Value } } ``` If you take a look at the members again, you get now a list off all _public_ properties and methods we've created. ```console ~> $DeathStarOne | Get-Member TypeName: DeathStar Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetCrew Method string GetCrew(string Type) GetHashCode Method int GetHashCode() GetReady Method string GetReady() GetType Method type GetType() SetCrew Method void SetCrew(string Type, int Value) ToString Method string ToString() Class Property string Class {get;set;} HyperDriveRating Property string[] HyperDriveRating {get;set;} Width Property int Width {get;set;} ``` That's it. Now you can access to your data while using the methods `GetCrew()`, `SetCrew()` and `GetReady()`. Keep in mind you can always isolate properties with methods like this as an kind of interface. {: .box-warning} **WARNING:** If you work with complex classes containing a huge amount of public and hidden properties, this could get a problem. Because the way how you get and set values depends now on either it's a property or methods. So you could loose track of all members and how to work with them. ## Using Script Properties Script properties are special class members which executes `get` or `set` methods depending on the action. You can add script properties to the well known _PSCustomObject_ or classes. Therefore you can use the `Add-Member` function as well. Here is a simple example with a _PSCustomObject_ to show the syntax: ```powershell $MyXWing = [PSCustomObject]@{ 'Model' = 'T-65' 'Class' = 'Starfighter' 'Crew' = @{ 'Pilot' = 1 'Astromech Droid' = 1 } '_Ready' = $true } $MyXWing | Add-Member -Name 'Ready' -MemberType ScriptProperty -Value { # Getter return $this._Ready } -SecondValue { # Setter Write-Warning 'This is a readonly property!' } ``` ```console ~> $MyXWing | Format-List Model : T-65 Class : Starfighter Crew : {Pilot, Astromech Droid} _Ready : True Ready : True ~> $MyXWing.Ready True ~> $MyXWing.Ready = $false WARNING: This is a readonly property! ``` If we want to use the script property in a class we have to create it in the class constructor. The class constructor knows the keyword `$this` which refers to te current object. ```powershell Class DeathStar { [String]$Class = 'Space battle station' [Int]$Width = '160000' [String[]]$HyperDriveRating = @('Class 4', 'Class 20') $Crew = @{ ImperialNavy = 342953 Stormtroopers = 25984 } hidden [String]$_Ready = $null DeathStar () { $this._Ready = $true $this | Add-Member -MemberType ScriptProperty -Name 'Ready' -Value { # Getter return $this._Ready } -SecondValue { # Setter Write-Warning 'This is a readonly property!' } } } ``` ```console ~> $DeathStarOne = [DeathStar]::New() ~> $DeathStarOne Ready : True Class : Space battle station Width : 160000 HyperDriveRating : {Class 4, Class 20} Crew : {Stormtroopers, ImperialNavy} ~> $DeathStarOne | Get-Member | Format-Table TypeName: DeathStar Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() Class Property string Class {get;set;} Crew Property System.Object Crew {get;set;} HyperDriveRating Property string[] HyperDriveRating {get;set;} Width Property int Width {get;set;} Ready ScriptProperty System.Object Ready {get=... ``` And that's exactly what we wanted. We have hidden the the property `_Ready` and exposed a script property called `Ready`. We can now get or set the values of to the script property like we would do it with a _normal_ property. ## Final Conclusion I personally like using _script properties_. But I take is on step further and create all public properties with a separate method: ```powershell hidden AddPublicMember() { $Members = $this | Get-Member -Force -MemberType Property -Name '_*' ForEach ($Member in $Members) { $PublicPropertyName = $Member.Name -replace '_', '' # Define getter part $Getter = "return `$this.{0}" -f $Member.Name $Getter = [ScriptBlock]::Create($Getter) # Define setter part $Setter = "Write-Warning 'This is a readonly property.'" $Setter = [ScriptBlock]::Create($Setter) $AddMemberParams = @{ Name = $PublicPropertyName MemberType = 'ScriptProperty' Value = $Getter SecondValue = $Setter } $this | Add-Member @AddMemberParams } } ``` This avoids making errors if I work with multiple constructors. Without a method like `AddPublicMember` you are forced to define each public property in every constructor method. All you have to do is to add the `AddPublicMember`method to your class definition and call it in every constructor. Finally our death start class looks like this: ```powershell Class DeathStar { [String]$Class = 'Space battle station' [Int]$Width = '160000' [String[]]$HyperDriveRating = @('Class 4', 'Class 20') $Crew = @{ ImperialNavy = 342953 Stormtroopers = 25984 } hidden [String]$_Ready = $null hidden AddPublicMember() { $Members = $this | Get-Member -Force -MemberType Property -Name '_*' ForEach ($Member in $Members) { $PublicPropertyName = $Member.Name -replace '_', '' # Define getter part $Getter = "return `$this.{0}" -f $Member.Name $Getter = [ScriptBlock]::Create($Getter) # Define setter part $Setter = "Write-Warning 'This is a readonly property.'" $Setter = [ScriptBlock]::Create($Setter) $AddMemberParams = @{ Name = $PublicPropertyName MemberType = 'ScriptProperty' Value = $Getter SecondValue = $Setter } $this | Add-Member @AddMemberParams } } DeathStar () { $this.AddPublicMember() $this._Ready = $true } DeathStar ([int]$ImperialNavy, [int]$Stormtroopers) { $this.AddPublicMember() $this.Crew.ImperialNavy = $ImperialNavy $this.Crew.StormTroopers = $Stormtroopers $this._Ready = $true } } ```