A property referencing another property in a PowerShell Class - class

Is it possible to have a property in a class reference another? For example, something like this. I've tried a few ways and I'm now not sure if I can do this...:
class TestClass {
[string]
$SQLInstanceName
[string]
$Server = "$($env:COMPUTERNAME)\$SQLInstanceName"
[string]myResult()
{
return $this.Server
}
}
....Thanks

Yes. Here it is implemented in your class definition:
class TestClass {
[string]
$SQLInstanceName
hidden $_Server = $($this | Add-Member ScriptProperty 'Server' `
{
# get
"$($env:COMPUTERNAME)\$($this.SQLInstanceName)"
}
)
[string]myResult()
{
return $this.Server
}
}
To see this working, new up an instance and assign a value to SQLInstanceName.
$c = [TestClass]::new()
$c.SQLInstanceName = 'MyDB'
Then invoking
$c.Server
$c.myResult()
results in
ComputerName\MyDB
ComputerName\MyDB

You should be using $this if you want to refer to a non-static property/method in the same object just like you have done with your myResult() method. Also your current sample has no default value or constructor so the SQLInstanceName is blank so just adding $this, without setting the variable, might give you misleading results. The following example might be something to consider but it is flawed.
class TestClass {
[string]$SQLInstanceName = "Test"
[string]$Server = "$($env:COMPUTERNAME)\$($this.SQLInstanceName)"
[string]myResult()
{
return $this.Server
}
}
$tcobject = New-Object TestClass
$tcobject.myResult()
However this does not work if you change the SQLInstanceName property since you are just setting default values. Classes in v5 don't really have get and set truly implemented in the same way as a .Net class so you would have to roll your own solution for that as discussed in this answer but also on this blog about v5 classes in general.
So a simple solution would work like this to get what I think you want.
class TestClass {
[string]
$SQLInstanceName = "Test"
[string]
$Server = $env:COMPUTERNAME
[string]myResult()
{
return "$($this.Server)\$($this.SQLInstanceName)"
}
}
$tcobject = New-Object TestClass
$tcobject.SQLInstanceName = "server\prod"
$tcobject.myResult()
This would be a design choice but I would not be trying to dynamically change one property based on the value of another in this case. Since you are using a value of them combined a simple method could work.

Related

how to add objects in arraylist in powershell class

i want to add items to arraylist object inside the class. how to do it. below is powershell code. it doesnt allow. how to achive this with
1. array declared at global level
2. array declared at class level
$logArrayGlobal = [System.Collections.ArrayList]::new()
class LogManager {
$logArrayClass = [System.Collections.ArrayList]::new()
LogManager()
{
$logArrayGlobal.Add("sada")
$this.logArrayClass
}
}
PowerShell will allow you to write to non-local variables from within a class method body if you explicitly specify the variables scope:
$logArrayGlobal = [System.Collections.ArrayList]::new()
class LogManager {
$logArrayClass = [System.Collections.ArrayList]::new()
LogManager()
{
$global:logArrayGlobal.Add("sada")
}
}

How to use delegates inside a static PowerShell class?

I want to use a func delegate in my static PowerShell 5.0 class:
I had issues to find a way to assign other static class methods for my delegate.
This code is working but not very convinient.
Is there a better way to use a delegate here ?
And I need to instanciate my static! class, only to get the type.
I tried the outcommented line, how you would do it with .NET types, but it's not working for my own class.
How can I get the type of my static class here more elegant ?
And, BTW, GetMethod() did not accecpt the BindingFlags parameter, why ?
class Demo
{
hidden static [object] Method_1([string] $myString)
{
Write-Host "Method_1: $myString"
return "something"
}
hidden static [object] Method_2([string] $myString)
{
Write-Host "Method_2: $myString"
return $null
}
hidden static [object] TheWrapper([string]$wrappedMethod, [string] $parameter)
{
# do a lot of other stuff here...
#return [System.Type]::GetType("Demo").GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
return [Demo]::new().GetType().GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
}
static DoWork()
{
Write-Host ([Demo]::TheWrapper('Method_1', 'MyMessage'))
[Demo]::TheWrapper('Method_2', 'c:\my_file.txt')
}
}
[Demo]::DoWork()
You don't need to create an instance of [demo] since [demo] is the actual type of the class. Also, you can write the delegate type more simply as [Func[string,object]]. This simplifies the body of TheWrapper method to
return [Demo].GetMethod($wrappedMethod).CreateDelegate([Func[string, object]]).Invoke($parameter)
but a much simpler way to do this in PowerShell is to get the method by passing its name to the '.' operator then invoking the result:
return [demo]::$wrappedMethod.Invoke($parameter)
In PowerShell, the right-hand side of the '.' operator doesn't need to be a constant. You can use an expression that results in the name of the method (or property) to retrieve.

Set a property for a PowerShell class on Instantiation

Is it possible to have the value of a property of a PowerShell class defined on instantiation without using a constructor?
Let's say there's a cmdlet that will return Jon Snow's current status (alive or dead). I want that cmdlet to assign that status to a property in my class.
I can do this using a constructor, but I'd like this to happen regardless of which constructor is used, or even indeed if one is used at all.
function Get-JonsCurrentStatus {
return "Alive"
}
Class JonSnow {
[string]
$Knowledge
[string]
$Status
#Constructor 1
JonSnow()
{
$this.Knowledge = "Nothing"
$this.Status = Get-JonsCurrentStatus
}
#Constructor 2
JonSnow([int]$Season)
{
if ($Season -ge 6)
{
$this.Knowledge = "Still nothing"
$this.Status = Get-JonsCurrentStatus #I don't want to have to put this in every constructor
}
}
}
$js = [JonSnow]::new()
$js
Unfortunately, you cannot call other constructors in the same class with : this() (though you can call a base class constructor with : base())[1]
Your best bet is a workaround with a (hidden) helper method:
function Get-JonsCurrentStatus {
return "Alive"
}
Class JonSnow {
[string]
$Knowledge
[string]
$Status
# Hidden method that each constructor must call
# for initialization.
hidden Init() {
$this.Status = Get-JonsCurrentStatus
}
#Constructor 1
JonSnow()
{
# Call shared initialization method.
$this.Init()
$this.Knowledge = "Nothing"
}
#Constructor 2
JonSnow([int]$Season)
{
# Call shared initialization method.
$this.Init()
if ($Season -ge 6)
{
$this.Knowledge = "Still nothing"
}
}
}
$js = [JonSnow]::new()
$js
[1] The reason for this by-design limitation, as provided by a member of the PowerShell team is:
We did not add : this() syntax because there is a reasonable alternative that is also somewhat more intuitive syntax wise
The linked comment then recommends the approach used in this answer.
You can initialise class properties on instantiation this way:
$jon = new-object JonSnow -Property #{"Status" = Get-JonsCurrentStatus; "Knowledge" = "Nothing"}

How to create a read-only property in a PowerShell (> 5.0) class

With the introduction of PowerShell class since version 5.0, I was wondering if it is possible to create a read-only property and how to do it. The property value is populated from the internal logic and the user can read it but not set it.
Consider the following code to create a class and let's say I want to make the property "Year" as read-only :
class Car {
[string] $Maker;
[string] $Model;
[string] $Year;
[int] $Odometer ;
[void] Drive([int] $NbOfKM) {
$this.Odometer += $NbOfKM;
}
Car(){
}
Car([int] $Odometer){
$this.$Odometer = $Odometer;
}
}
Unfortunately there is no getter/setter support in PowerShell.
What you can do though is use the hidden keyword to hide the property and only provide a method to get the value, but only set it internally. Then the "setter" becomes less discoverable.
I don't have enough reputation to comment on Stuart's answer. However, this builds on his answer, because the workaround is to override the setter method.
I've found that you can implement getters and setters in an inelegant way by using ScriptProperty. Two relevant posts:
Powershell class implement get set property,
https://powershell.org/forums/topic/class-in-powershell-5-that-run-custom-code-when-you-set-property/.
I prefer the second, because it puts the dynamic code into the constructor. An example from my code:
class MyClass {
MyClass () {
$this.PSObject.Properties.Add(
(New-Object PSScriptProperty 'ReadOnlyProperty', {$this._ROF})
)
}
hidden [string]$_ROF = 'Readonly'
}
The constructor for PSScriptProperty takes a name and then one or two scriptblocks; the first is the getter, the second (if provided) is the setter. https://msdn.microsoft.com/en-us/library/system.management.automation.psscriptproperty(v=vs.85).aspx
Downsides of this approach:
You have to manually create your backing field (I called mine _ROF)
It's ugly to define your properties dynamically like this. You can't have a declaration of the property in the class body, because you are adding it dynamically (essentially, with Add-Member)
You end up with a ScriptProperty instead of a Property
If you try to assign to your read-only property, you get a SetWithoutSetterFromScriptProperty exception instead of a PropertyAssignmentException.
This is a class that has ReadOnly Properties
Class ReadOnlyExample{
ReadOnlyExample([String]$ComputerName, [ipaddress]$Ip){
$PSBoundParameters.Keys | ForEach-Object{
$Key = $_
$Value = $PSBoundParameters[$Key]
$This | add-member -MemberType ScriptProperty -Name $Key -Value { return $Value }.GetNewClosure()
}
}
}
The output would be
$Test = [ReadOnlyExample]::new('localhost', '127.0.0.1')
$Test
ComputerName Ip
------------ --
localhost 127.0.0.1
If I try and change it
$Test.ComputerName = "HEY"
SetValueException: example:Example:1:1
Line |
1 | $Test.ComputerName = "HEY"
| ~~~~~~~~~~~~~~~~~~~~~~~~~~
| Set accessor for property "ComputerName" is unavailable.
The main important parts are the MemberType needs to be ScriptProperty and Scriptblock needs it use the GetNewClosure() method
GetNewClosure() says to store the value of the variable into the Scriptblock.
You can read more at GetNewClosure()

Using Generic.List with custom type as a return type for function is not working

Edited #3.
I managed to get it working. I need to load all dependencies in the correct order in the main script file. not from the class file so I will vote it to close this post.
I am using powershell 5.0 on Windows 10. Using List (e.g. $list = New-Object System.Collections.Generic.List``1[CustomClass]) works in most of the cases. But got the error when I used it as a return type.
The code below doesn't work.
class CustomClass1 {
[System.Collections.Generic.List``1[CustomClass]]GetColumns([string]$tableType){
$list = New-Object System.Collections.Generic.List``1[CustomClass]
return $list
}
}
Edited: #1
I tried this code below but didn't work as well.
[System.Collections.Generic.List``1[CustomClass]]GetColumns([string]$tableType) {
$list = New-Object System.Collections.Generic.List``1[CustomClass]
$c= New-Object CustomClass
$list.Add($c)
return ,$list
}
Edited: #2
I push my test scripts in this repo https://github.com/michaelsync/powershell-scripts/tree/master/p5Class
CustomClass.ps1
class CustomClass {
[string]$ColumnName
}
CustomClass1.ps1
. ".\CustomClass.ps1"
class CustomClass1 {
[System.Collections.Generic.List``1[CustomClass]]GetColumns(){
$list = New-Object System.Collections.Generic.List``1[CustomClass]
$c = New-Object CustomClass
$list.Add($c)
return $list
}
}
Test.ps1
. ".\CustomClass1.ps1"
$c1 = New-Object CustomClass1
$c1.GetColumns()
If I put all classes in one file, it works. I think it has something to do with the way the ps1 files are being loaded. (Thanks #jesse for the tip. )
But if I use normal type such as string, int and etc, it works.
class CustomClass1 {
[System.Collections.Generic.List``1[string]]GetColumns([string]$tableType){
$list = New-Object System.Collections.Generic.List``1[string]
return $list
}
}
It also works when I assign the generic list with custom class.
$list = New-Object System.Collections.Generic.List``1[CustomClass]
$c = New-Object CustomClass
$list.Add($c)
Is that the known issue that we can't return the generic list with custom class type?
Your error "Unable to find type [CustomType]" indicates that there is an issue in the order in which types are loaded, or that you've missed loading a dependency (be it a script or an assembly) completely.
Check that before your functions are used, all the scripts and assemblies are loaded.