VBscript Public Property Set/Get equivalent in PowerShell - powershell

I'm trying to add elements to a Powershell variable with Add-Member.
I have no problem adding static properties with NoteProperty, and methods with ScriptMethod, like that :
$variable = New-Object PSObject
$variable | Add-Member NoteProperty Key "Value"
$variable | Add-Member ScriptMethod DoSomething { // code }
Now I'm stuck on this :
I want to add a property that has a getter and a setter and does a bunch of things via code block.
The VBScript equivalent would be this :
Class MyClass
Public Property Get Item(name)
// Code to return the value of Item "name"
End Property
Public Property Let Item(name,value)
// Code to set the value of Item "name" to value "value"
End Property
End Class
Note that the code sections I need to write do more than just set/get the value, they're more complex than that (set other related variables, access external data, etc...).
I failed to find anything this easy in PowerShell and ended up adding instead 2 scriptmethods, GetItem and SetItem.
What would be the best way to implement this get/let functionnality in a member of a PSObject in PowerShell ?
Thanks in advance

You should add -MemberType ScriptProperty and use -Value and -SecondValue:
# Make an object with the script property MyProperty
$variable = New-Object PSObject
# “internal” value holder
$variable | Add-Member -MemberType NoteProperty _MyProperty -Value 42
# get/set methods
$get = {
Write-Host "Getting..."
$this._MyProperty
}
$set = {
Write-Host "Setting..."
$this._MyProperty = $args[0]
}
# the script property
$variable | Add-Member -MemberType ScriptProperty MyProperty -Value $get -SecondValue $set
Test:
Write-Host "Original value: $($variable.MyProperty)"
$variable.MyProperty = 123
Write-Host "After assignment: $($variable.MyProperty)"
It prints:
Getting...
Original value: 42
Setting...
Getting...
After assignment: 123
Unfortunately I do not know how to make “protected/private” internal value holders like the note property _MyProperty in our example (or whether it is possible at all).
UPDATE: Apparently it’s the answer to what more or less the title asks. But the question is in fact about parameterized property, not just get/set methods implemented via script blocks. My attempt to use this type of property (ParameterizedProperty) with Add-Member has failed, perhaps it is not yet supported.

Related

How to dynamically add new properties to custom object in PowerShell

I have a custom object that contains device information that looks something like this.
name,model,sn
PC1,Elitebook 850, ABC123,
PC2,EliteDesk 600,123ABC
I have a function that retrieves threats detected by an antivirus product. These are returned as an array of objects. There are more properties than below but this is just an example
file,md5
bad.exe,adfdfdfd
evil.exe,fdfdffdf
I would like to add each member as properties to the custom object so the final output is similar to this.
name,model,sn,01_file,01_md5,02_file,02_md5
Currently, my script does this:
foreach($device in $devices){
$threats = Get-Threats $device
[pscustomobject] #{
name = $device.device_name
make = $device.make
sn = $device.sn
ThreatFileName = $threats.File -join ", "
Threat_md5 = $threats.md5 -join ", "
}
}
This works ok but I'd really like each object returned by the 'Get-Threats' function to be listed as its own set of properties. I need this to be generated dynamically because I don't know how many threats will be returned for each device.
Any thoughts on how to go about this?
You can add properties to objects at any time with the Add-Member cmdlet. Maybe start with an empty object and loop through the elements returned by Get-Threats, adding a member each time?
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-member?view=powershell-6
Edit: Example code to answer for reference.
$o = [pscustomobject]#{
MemberA='aaa'
MemberB='bbb'
MemberC='ccc'
MemberD='ddd'
}
"Before"
$o | ft
$o | Add-Member -MemberType NoteProperty -Name 'MemberE' -Value 'eee'
$o | Add-Member -MemberType NoteProperty -Name 'MemberF' -Value 'fff'
"After"
$o | ft
The answer from #krome got me pointed in the right direction although that answer wouldn't work for me as there could be multiple threats for each device.
I used the answer from #scobi on Dynamically get PSCustomObject property and values to arrive at this answer which meets my requirement that the new properties be generated dynamically.
foreach($device in $devices){
$threats = Get-Threats $device
if($null -ne $threats){
$i = 1
foreach($threat in $threats){
$threat | Get-Member -MemberType NoteProperty | % Name | %{
Add-Member -InputObject $device -NotePropertyName ("Threat"+$i.ToString() + "_" + $_) -NotePropertyValue $threat.$_ -Force
}
$i++
}
}
}
Write-Output $devices
I loop over each device in the devices array and call my
Get-Threats function.
The if statement prevents the loop from running for any devices
that don't have threats.
$i is used as my counter to increment the property name for each
threat found so the properties will all have unique names
I then loop over each threat found piping to Get-Member to retrieve
the property name and values
I use Add-Member to add additional properties for each threat found
to each device in the loop, using the counter to give each propery a unique name

Is there a proper way to create a scriptproperty in a Powershell 5 class

In Powershell versions prior to 5, objects could be created "on the go" with New-Object, and could be extended with Add-Member. Powershell 5 introduced classes, but it seems they only allow basic properties and methods. Is there any proper way one can emulate a scriptproperty (IE. a property that is seen as a property, not as a method, but is still calculated on the go)?
The following code gives the wanted result thanks to a hack in the constructor. Can this hack be avoided?
class fakescriptproperty {
hidden [datetime] _currenttime() { return ( Get-Date ) }
fakescriptproperty() {
Add-Member -InputObject $this -MemberType ScriptProperty `
-Name currenttime -Value { $this._currenttime() }
}
}
$test = New-Object -TypeName fakescriptproperty
$test.currenttime
not sure I understand the problem here. This is how I create, extend and access custom objects; let me know if you're after something else.
# Creating new object with key/value hashtable
$customObject = New-Object psobject -Property #{key1 = "value" ; key2 = "value2"}
# Adding member to custom object
$customObject | Add-Member -MemberType NoteProperty -Name key3 -Value value3
# This will return value3
$customObject.key3
========================================================================
Edit - I'm not familiar with classes. In PowerShell 5 it is possible to extend custom objects with a ScriptProperty member and specify a scriptblock that is calculated on the go (no hackery required).
$test = New-Object psobject
$test | Add-Member -Name currentime -MemberType ScriptProperty -Value {Get-Date}
$test.currentime
It is possible to specify a -SecondValue scriptblock that handles setting the property (keep running into StackOverflowException when testing this)

Using a Powershell noteproperty as a text string in a variable

I've used Invoke-Restmethod to download some data, which Powershell stores in a PSCustomObject, in a property called data.
I need to use the value of one of the items in the returned data as a variable for another command. I have managed to select-object -expand my way down to the following output from Get-Member:
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
id NoteProperty System.Int32 id=999
What I need to do is grab the value of the ID noteproperty - 999 - and pass that as part of a string to a new variable, eg:
$newVar = "sometext" + 999 + "moretext"
No amount of select-string or out-string etc is helping. Scripting is not exactly my strong point so I'm not sure I'm even articulating what I want properly - apologies if this is the case!
Any assistance much appreciated
I'm not sure exactly what your code and looks like, so I created the following static approximation from the description:
$data = New-Object PSCustomObject
$data | Add-Member -Type NoteProperty -Name Id -Value 999
$restResponse = New-Object PSCustomObject
$restResponse | Add-Member -Type NoteProperty -Name data -Value $data
Please clarify if this is not a match. You can get the Id value as follows
$restResponse.data.Id
Assign it to another variable
$newVar = "sometext" + $restResponse.data.Id + "moretext"
$newVar
And if your REST response is a collection of data objects, iterate through them
$restResponse.data | Foreach-Object { "sometext" + $_.Id + "moretext" }
I would go for for using $output | select *,#{n='test';e={[string]$_.test}} -exclude properties test
if the exclude is not active it will complain about it already exists. Mostly I use the select expression to manipulate data realtime instead of psCustomObject for such simple task

Returning complex object with PowerShell functions?

Most of out of the box PowerShell commands are returning "complex" objects.
for example, Get-Process returns an array of System.Diagnostics.Process.
All of the function I've ever wrote was returning either a simple type, or an existing kind of object.
If I want to return a home made objects, what are guidelines ?
For example, imagine I have an object with these attributes: Name, Age. In C# I would have wrote
public class Person {
public string Name { get; set; }
public uint Age { get; set; }
}
What are my options for returning such object from a PowerShell function ?
As my goal is to create PowerShell module, should I move to a c# module instead of a ps1 file ?
should I compile such objects in a custom DLL and reference it in my module using LoadWithPartialName ?
Should I put my class in a powershell string, then dynamically compile it ?
you can use this syntax:
New-Object PSObject -Property #{
Name = 'Bob'
Age = 32
}
There are a few options for creating your own object with custom properties. It is also possible to extend existing objects with additional properties. The most explicit method is to use the Add-Member cmdlet:
$test = New-Object PSCustomObject
Add-Member -InputObject $test -Name Name -Value 'Bob' -MemberType NoteProperty
Add-Member -InputObject $test -Name Age -Value 32 -MemberType NoteProperty
You can also use the fact that objects can be extended combined with the fact that Select-Object can implicitly add properties to objects in the pipeline:
$test = '' | Select #{n='Name'; e={'Bob'}}, #{n='Age'; e={32}}
Personally I prefer the first method since it is more explicit and less hand waving magic, but at the end of the day it's a scripting language so whatever works is the right way to do it.

How do I create a custom type in PowerShell for my scripts to use?

I would like to be able to define and use a custom type in some of my PowerShell scripts. For example, let's pretend I had a need for an object that had the following structure:
Contact
{
string First
string Last
string Phone
}
How would I go about creating this so that I could use it in function like the following:
function PrintContact
{
param( [Contact]$contact )
"Customer Name is " + $contact.First + " " + $contact.Last
"Customer Phone is " + $contact.Phone
}
Is something like this possible, or even recommended in PowerShell?
Prior to PowerShell 3
PowerShell's Extensible Type System didn't originally let you create concrete types you can test against the way you did in your parameter. If you don't need that test, you're fine with any of the other methods mentioned above.
If you want an actual type that you can cast to or type-check with, as in your example script ... it cannot be done without writing it in C# or VB.net and compiling. In PowerShell 2, you can use the "Add-Type" command to do it quite simmple:
add-type #"
public struct contact {
public string First;
public string Last;
public string Phone;
}
"#
Historical Note: In PowerShell 1 it was even harder. You had to manually use CodeDom, there is a very old function new-struct script on PoshCode.org which will help. Your example becomes:
New-Struct Contact #{
First=[string];
Last=[string];
Phone=[string];
}
Using Add-Type or New-Struct will let you actually test the class in your param([Contact]$contact) and make new ones using $contact = new-object Contact and so on...
In PowerShell 3
If you don't need a "real" class that you can cast to, you don't have to use the Add-Member way that Steven and others have demonstrated above.
Since PowerShell 2 you could use the -Property parameter for New-Object:
$Contact = New-Object PSObject -Property #{ First=""; Last=""; Phone="" }
And in PowerShell 3, we got the ability to use the PSCustomObject accelerator to add a TypeName:
[PSCustomObject]#{
PSTypeName = "Contact"
First = $First
Last = $Last
Phone = $Phone
}
You're still only getting a single object, so you should make a New-Contact function to make sure that every object comes out the same, but you can now easily verify a parameter "is" one of those type by decorating a parameter with the PSTypeName attribute:
function PrintContact
{
param( [PSTypeName("Contact")]$contact )
"Customer Name is " + $contact.First + " " + $contact.Last
"Customer Phone is " + $contact.Phone
}
In PowerShell 5
In PowerShell 5 everything changes, and we finally got class and enum as language keywords for defining types (there's no struct but that's ok):
class Contact
{
# Optionally, add attributes to prevent invalid values
[ValidateNotNullOrEmpty()][string]$First
[ValidateNotNullOrEmpty()][string]$Last
[ValidateNotNullOrEmpty()][string]$Phone
# optionally, have a constructor to
# force properties to be set:
Contact($First, $Last, $Phone) {
$this.First = $First
$this.Last = $Last
$this.Phone = $Phone
}
}
We also got a new way to create objects without using New-Object: [Contact]::new() -- in fact, if you kept your class simple and don't define a constructor, you can create objects by casting a hashtable (although without a constructor, there would be no way to enforce that all properties must be set):
class Contact
{
# Optionally, add attributes to prevent invalid values
[ValidateNotNullOrEmpty()][string]$First
[ValidateNotNullOrEmpty()][string]$Last
[ValidateNotNullOrEmpty()][string]$Phone
}
$C = [Contact]#{
First = "Joel"
Last = "Bennett"
}
Creating custom types can be done in PowerShell.
Kirk Munro actually has two great posts that detail the process thoroughly.
Naming Custom Objects
Defining Default Properties for Custom Objects
The book Windows PowerShell In Action by Manning also has a code sample for creating a domain specific language to create custom types. The book is excellent all around, so I really recommend it.
If you are just looking for a quick way to do the above, you could create a function to create the custom object like
function New-Person()
{
param ($FirstName, $LastName, $Phone)
$person = new-object PSObject
$person | add-member -type NoteProperty -Name First -Value $FirstName
$person | add-member -type NoteProperty -Name Last -Value $LastName
$person | add-member -type NoteProperty -Name Phone -Value $Phone
return $person
}
This is the shortcut method:
$myPerson = "" | Select-Object First,Last,Phone
Steven Murawski's answer is great, however I like the shorter (or rather just the neater select-object instead of using add-member syntax):
function New-Person() {
param ($FirstName, $LastName, $Phone)
$person = new-object PSObject | select-object First, Last, Phone
$person.First = $FirstName
$person.Last = $LastName
$person.Phone = $Phone
return $person
}
Surprised no one mentioned this simple option (vs 3 or later) for creating custom objects:
[PSCustomObject]#{
First = $First
Last = $Last
Phone = $Phone
}
The type will be PSCustomObject, not an actual custom type though. But it is probably the easiest way to create a custom object.
There is the concept of PSObject and Add-Member that you could use.
$contact = New-Object PSObject
$contact | Add-Member -memberType NoteProperty -name "First" -value "John"
$contact | Add-Member -memberType NoteProperty -name "Last" -value "Doe"
$contact | Add-Member -memberType NoteProperty -name "Phone" -value "123-4567"
This outputs like:
[8] » $contact
First Last Phone
----- ---- -----
John Doe 123-4567
The other alternative (that I'm aware of) is to define a type in C#/VB.NET and load that assembly into PowerShell for use directly.
This behavior is definitely encouraged because it allows other scripts or sections of your script work with an actual object.
Here is the hard path to create custom types and store them in a collection.
$Collection = #()
$Object = New-Object -TypeName PSObject
$Object.PsObject.TypeNames.Add('MyCustomType.Contact.Detail')
Add-Member -InputObject $Object -memberType NoteProperty -name "First" -value "John"
Add-Member -InputObject $Object -memberType NoteProperty -name "Last" -value "Doe"
Add-Member -InputObject $Object -memberType NoteProperty -name "Phone" -value "123-4567"
$Collection += $Object
$Object = New-Object -TypeName PSObject
$Object.PsObject.TypeNames.Add('MyCustomType.Contact.Detail')
Add-Member -InputObject $Object -memberType NoteProperty -name "First" -value "Jeanne"
Add-Member -InputObject $Object -memberType NoteProperty -name "Last" -value "Doe"
Add-Member -InputObject $Object -memberType NoteProperty -name "Phone" -value "765-4321"
$Collection += $Object
Write-Ouput -InputObject $Collection
Here's one more option, which uses a similar idea to the PSTypeName solution mentioned by Jaykul (and thus also requires PSv3 or above).
Example
Create a TypeName.Types.ps1xml file defining your type. E.g. Person.Types.ps1xml:
<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>StackOverflow.Example.Person</Name>
<Members>
<ScriptMethod>
<Name>Initialize</Name>
<Script>
Param (
[Parameter(Mandatory = $true)]
[string]$GivenName
,
[Parameter(Mandatory = $true)]
[string]$Surname
)
$this | Add-Member -MemberType 'NoteProperty' -Name 'GivenName' -Value $GivenName
$this | Add-Member -MemberType 'NoteProperty' -Name 'Surname' -Value $Surname
</Script>
</ScriptMethod>
<ScriptMethod>
<Name>SetGivenName</Name>
<Script>
Param (
[Parameter(Mandatory = $true)]
[string]$GivenName
)
$this | Add-Member -MemberType 'NoteProperty' -Name 'GivenName' -Value $GivenName -Force
</Script>
</ScriptMethod>
<ScriptProperty>
<Name>FullName</Name>
<GetScriptBlock>'{0} {1}' -f $this.GivenName, $this.Surname</GetScriptBlock>
</ScriptProperty>
<!-- include properties under here if we don't want them to be visible by default
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
</Members>
</MemberSet>
-->
</Members>
</Type>
</Types>
Import your type: Update-TypeData -AppendPath .\Person.Types.ps1xml
Create an object of your custom type: $p = [PSCustomType]#{PSTypeName='StackOverflow.Example.Person'}
Initialise your type using the script method you defined in the XML: $p.Initialize('Anne', 'Droid')
Look at it; you'll see all properties defined: $p | Format-Table -AutoSize
Type calling a mutator to update a property's value: $p.SetGivenName('Dan')
Look at it again to see the updated value: $p | Format-Table -AutoSize
Explanation
The PS1XML file allows you to define custom properties on types.
It is not restricted to .net types as the documentation implies; so you can put what you like in '/Types/Type/Name' any object created with a matching 'PSTypeName' will inherit the members defined for this type.
Members added through PS1XML or Add-Member are restricted to NoteProperty, AliasProperty, ScriptProperty, CodeProperty, ScriptMethod, and CodeMethod (or PropertySet/MemberSet; though those are subject to the same restrictions). All of these properties are read only.
By defining a ScriptMethod we can cheat the above restriction. E.g. We can define a method (e.g. Initialize) which creates new properties, setting their values for us; thus ensuring our object has all the properties we need for our other scripts to work.
We can use this same trick to allow the properties to be updatable (albeit via method rather than direct assignment), as shown in the example's SetGivenName.
This approach isn't ideal for all scenarios; but is useful for adding class-like behaviors to custom types / can be used in conjunction with other methods mentioned in the other answers. E.g. in the real world I'd probably only define the FullName property in the PS1XML, then use a function to create the object with the required values, like so:
More Info
Take a look at the documentation, or the OOTB type file Get-Content
$PSHome\types.ps1xml for inspiration.
# have something like this defined in my script so we only try to import the definition once.
# the surrounding if statement may be useful if we're dot sourcing the script in an existing
# session / running in ISE / something like that
if (!(Get-TypeData 'StackOverflow.Example.Person')) {
Update-TypeData '.\Person.Types.ps1xml'
}
# have a function to create my objects with all required parameters
# creating them from the hash table means they're PROPERties; i.e. updatable without calling a
# setter method (note: recall I said above that in this scenario I'd remove their definition
# from the PS1XML)
function New-SOPerson {
[CmdletBinding()]
[OutputType('StackOverflow.Example.Person')]
Param (
[Parameter(Mandatory)]
[string]$GivenName
,
[Parameter(Mandatory)]
[string]$Surname
)
([PSCustomObject][Ordered]#{
PSTypeName = 'StackOverflow.Example.Person'
GivenName = $GivenName
Surname = $Surname
})
}
# then use my new function to generate the new object
$p = New-SOPerson -GivenName 'Simon' -Surname 'Borg'
# and thanks to the type magic... FullName exists :)
Write-Information "$($p.FullName) was created successfully!" -InformationAction Continue