Top Posts
Categories
- Really excited to see our new feature AWS Glue Data Quality is now available for people to try in preview! linkedin.com/posts/tomet_jo… 3 months ago
IT Notes from the Powertoe – Tome Tanasovski
I was recently reading an article that I’ll refrain from linking to for fear of a flame war. It infuriated me because it referred to PowerShell as a procedural/functional language that is antiquated and more like shell scripting. I reflected on this for a while and I came to the realization that if I were to look at 99.99999% of the code out there I may think the same thing. I also realized that there are reasons that PowerShell code appears procedural instead of object oriented.
This post will do a few things
Let’s describe a dog.
A dog has a name, color, and is some size.
$dogclass = new-object psobject -Property @{ color = $null name = $null size = $null }
While it may not be needed because you can instantiate the object without doing so (via psobject.copy()), object-oriented developers love their constructors. It’s also a good place to add some validation for your class. For example, in the below constructor we’ll restrict the size to either small, medium, or large.
function DogClass { param( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$false)] [string]$color, [Parameter(Mandatory=$false)] [ValidateSet('Small','Medium','Large')] [String]$size ) $dog = $DogClass.psobject.copy() $dog.name = $name $dog.color = $color $dog.size = $size $dog }
Now you can start using your class
08:31:50 PS C:\> $lucy = DogClass -name Lucy -color brown -size large 08:45:07 PS C:\> $lucy color name size ----- ---- ---- brown Lucy large
A dog performs certain functions. For example, a dog is known to pee. We can modify our dog class with a method by using Add-Member. Also note that you can use the $this special variable to access the object that is invoking the method.
$DogClass |Add-Member -MemberType ScriptMethod -Name "Pee" -Value { "A warm refreshing pee trickles out of {0}" -f $this.name }
With the method created, you can instantiate Lucy again from the modified class and access her new method.
08:50:50 PS C:\> $lucy = DogClass -name Lucy -color brown -size large 08:52:30 PS C:\> $lucy.Pee() A warm refreshing pee trickles out of Lucy
Accessor functions help you protect the properties of an object. As in Perl and Python, there is no real protected or private property available to you. Therefore, you can use the same convention that these other languages use to supply an underscore prefix to any private methods or properties. It doesn’t actually prevent people from using it, but it’s a clear sign that they shouldn’t use it. For example, if we wanted to make the size property private, we would modify the class to look like this. Note: I’m adding the scriptmethod we created in one step using the -PassThru parameter of Add-Member.
$DogClass = new-object psobject -Property @{ color = $null name = $null _size = $null } |Add-Member -PassThru -MemberType ScriptMethod -Name "Pee" -Value { "A warm refreshing pee trickles out of {0}" -f $this.name }
With the new _size property, it’s easy enough to modify our constructor to use the new property, but what if you want to control how people set or get the property of size. You can create an accessor function to do this. Basically, this is just a method that will set or return the data from _size.
$dogclass |Add-Member -PassThru -MemberType ScriptMethod -Name Size -Value { param( [Parameter(Mandatory=$false, Position=0)] $Size ) if ($size) { $this._size = $size } else { $this._size } }
Now, we can access the data in _size by using the accessor.
$lucy = DogClass -name Lucy -color brown -size large "The dog is {0}" -f $lucy.Size()
We can also set the _size using the same function.
$lucy.Size('medium')
It’s important to note that I lost the property validator I have in the constructor. ScriptMethods break if you have an optional parameter that also has a validator. There are two ways to handle this. Either you add validation to the Size() method or you create two accessor functions, SetSize() and GetSize(). Both are acceptable and both are very easy to implement. Here’s an example of implementing your own validatior
A nice way to allow access to the pseudo private properties is to use the ScriptProperty member instead. This provides you with a way to perform a scripted action for a get, but it also allows you to do validation on a set. The difference is that it allows you to use the equal sign instead of a method to set the property.
Here’s what a ScriptProperty looks like. The first ScriptBlock is the get function and the second ScriptBlock is the set function.
$dogclass |Add-Member -MemberType ScriptProperty -name Size -Force -Value { $this._size } { param( $Size ) if (@('small','medium','large') -contains $size) { $this._size = $size } else { throw "This is not a valid size. A size must be small, medium, or large" } }
I’m using Force above to override the Size member I already created. In order to use the new property
In order to use this property, I can now do the following with my instantiated instance of Lucy.
PS C:\> $lucy.Size = 'blah' Exception setting "Size": "This is not a valid size. A size must be small, medium, or large" At line:1 char:1 + $lucy.Size = 'blah' + ~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], SetValueInvocationException + FullyQualifiedErrorId : ScriptSetValueRuntimeException PS C:\> $lucy.Size = 'medium' PS C:\> $lucy.Size medium
A nice thing about this technique is that your custom formatters will be able to see the property. In other words, it can be used in Select, Format-Table, Format-List, etc.
PowerShell doesn’t have real inheritance. However, the functionality of inheritance is very easily achieved. If you’re not familiar with inheritance, it’s pretty straightforward. Many times a class will have subclasses. For instance, a Scottish Terrier is a subclass of dog that is always small, black, and prefers to chase rats (rather than hunt like a beagle or keep your feet warm like a pug). Therefore, we can create a Scotty class that is based on the dog class, but has these properties set by default and has a method for hunting vermin.
Because the definition of our class is simply an object, we can copy and modify it to create a new class that can be used later by other things.
$ScottyClass = $DogClass.psobject.copy() $ScottyClass.color = 'black' $ScottyClass._size = 'small'
One thing to note when creating an inherited class in PowerShell using this technique is that you need to recreate your constructors. This is probably the only unfortunate part of the pseudo-class inheritance that PowerShell offers. It basically forces override whether you want it or not on the constructor.
function New-Scotty { param( [Parameter(Mandatory=$true)] [String]$name ) $dog = $ScottyClass.psobject.copy() $dog.name = $name $dog }
Now we can use our new class:
$George = New-Scotty George
In the case of the Scotty class where we want to add a new method for catching rats, there’s nothing special that needs to be done. New methods are treated the same as on the base class. However, if you want to override a method that exists on the parent class, the technique is identical, but you’ll need to supply the force parameter to Add-Member. For example, if we want to override the Pee() method for the dog, we could do the following.
$ScottyClass|Add-Member -Force -MemberType ScriptMethod -Name "Pee" -Value { "The Scotty sniffs the tree. A warm refreshing pee trickles out of {0}" -f $this.name }
Bruce Payette has a great set of code in PowerShell in Action 2.0 about extending PowerShell to wrap the OO-ability of the language in something that looks cleaner. The end result is you can define a class like this:
DogClass { property name property size method Pee { "Oh yeah"} } $dog = new DogClass
While doing research for this post, I realized that I had never noticed this section of the book. We really need to make this a publicly available extension. It’s pretty simple to do, but I don’t want to paste his code due to copyright reasons. Actually, if you’re the kind of person who has been looking for OO in PowerShell and have never read Bruce’s book, you probably should because it will answer a whole lot more about PowerShell than just this for you.
The PowerShell team added a shortcut way to do all of the above. Modules exist in their own closure. Because of this, they may contain their own scoped variables that can be stored within the module itself. There is a parameter on New-Module called -AsCustomObject that will convert your defined scriptblock module into a PowerShell object with note properties and script methods. Here’s an example of the Dog class using this technique.
function New-Dog { param( [Parameter(Mandatory=$true)] [string]$Name, [Parameter(Mandatory=$false)] [ValidateSet('small', 'medium','large', $null)] [string]$size, [Parameter(Mandatory=$false)] [string]$color ) New-Module -Argumentlist @($name,$size,$color) -AsCustomObject { param( [Parameter(Mandatory=$true)] [string]$Name, [Parameter(Mandatory=$false)] [ValidateSet('small', 'medium','large', $null)] [string]$size, [Parameter(Mandatory=$false)] [string]$color ) function Pee { "A warm refreshing pee trickles out of {0}" -f $Name } Export-ModuleMember -Function Pee -Variable Name, Size, Color } }
If we look at the members that this creates, you’ll see it looks very similar to what we’ve been doing up until now.
$Lucy = New-Dog -Name Lucy $Lucy |Get-Member TypeName: System.Management.Automation.PSCustomObject Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() color NoteProperty System.String color= Name NoteProperty System.String Name=blah size NoteProperty System.String size= Pee ScriptMethod System.Object Pee();
So what we’ve been looking at is modern object oriented programming. However, it’s rarely seen in the wild with PowerShell even though it’s very simple to implement. Why is that exactly? Sure, there are plenty of beginners out there who weren’t even developers until now, but I believe it’s deeper than that. I personally know that this exists. However, I’ve only used it on one project.
The reality is that PowerShell lives and breaths objects through everything. Also, these objects are very flexible because they can be extended or modified on the fly. Functions can take objects as input and the type of Input provided by strongly typed objects can more easily be achieved with strongly type parameters. I see the param() block of a function as something that describes the properties of an object that I expect to see.
Now this is PowerShell to me. This is a paradigm shift in the way we do object-oriented programming. To me, when my peers ask me why I’m so PowerShell crazy, this is the answer. It looks like it’s functional, but it is far from that.
function New-Dog { param( [Parameter(Mandatory=$true)] [string]$Name, [Parameter(Mandatory=$false)] [ValidateSet('small', 'medium','large', $null)] [string]$size, [Parameter(Mandatory=$false)] [string]$color ) New-Object psobject -property @{ Name = $Name Size = $Size Color = $color } } function Invoke-Pee { param( [Parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] [String]$Name ) PROCESS { "A warm refreshing pee trickles out of {0}" -f $Name } } New-Dog -Name Lucy |Invoke-Pee A warm refreshing pee trickles out of Lucy
Not only do I have a dog that can pee, but I also can use the same Invoke-Pee function for anything that has a name property. It can also invoke-pee on a number of dogs without having to create a loop. If I really needed to be sure it was a dog, I could take the entire psobject as a parameter and validate that it had the properties that make it a dog. Perhaps even write the Test-Dog function to do that.
Finally, I would personally wrap the above in a module named Dog and use a prefix more like New-Dog and Invoke-DogPee. Then it’s easy to import the module and inspect what methods are available using Get-Command -Module Dog. Again, this looks and smells like it’s functional, but it’s all objects and encapsulation, and it’s amazing!
Excellent! I really enjoyed that. I haven’t given this much thought, but I never thought of PowerShell as anything but very object oriented.
This is probably just me quibbling over semantics, but I think that in order to be an object-oriented language, it would have to support polymorphism and inheritance. Though you can do a lot with PSObjects to make them behave as though they were objects of a new, custom type, it’s not quite the same thing. PowerShell’s a huge consumer of .NET objects and classes, but I don’t see it as being a .NET language in its own right (unless you count the ability to embed .NET code via Add-Type.)
However it’s classified, PowerShell is awesome. “antiquated and more like shell scripting?” Not any shell scripting I’ve ever done! The ability to pass live objects around, not to mention access the entire .NET Framework, COM ecosystem and Win32 API from a script or command line is amazing. If it’s possible to do something on a Windows computer at all, you can do it with PowerShell.
I agree, but I’ll argue it as long as Perl 5 is on this list: http://en.wikipedia.org/wiki/List_of_object-oriented_programming_languages.
Perl5 doesn’t give much of what you are asking for. Without Moose Perl is a bunch of smoke and mirrors with magic hashes 😛
Actually, that page only has one resource and I have no idea why they are reputable.
The reality is that there are two classifications of OOP (according to Wikipedia).
Here’s a link to the description of OOP prototype-based programming http://en.wikipedia.org/wiki/Prototype-based_programming
I would also argue that PowerShell could make an amazing development platform if it was taken seriously. In my opinion, it could be (and maybe already is) C#’s stripped down and flexible language: Java->Scala, C++->Golang, Javascript->Node.js. It just makes sense to me when you look at what those languages are solving and how they are doing it (maybe node should be exluded). Why are those considered development platforms, but PowerShell is not? Let’s not even talk about how serious people are about Python. We could debate this one for a while, but let’s just say that PS could use some love and pushing if people were going to use it to develop apps. PS could also be implemented on Linux to help it along on this front – that would be nice.
It goes without saying, but I genuinely love the language. I want to use it for everything because it’s so damned easy to do really great things right once you know what right is.
There is one thing I have missed in PowerShell. Static Variables. I think you can do such with the use of scope and the AllScope option
” Items that have the AllScope property become part of any child
scopes that you create, although they are not retroactively inherited by
parent scopes. ”
Active Directory, for Example, allows circular references.
I use a Variable with the AllScope option in recursive Functions, to have a variable, to track recursion loops and prevent endless cicular references.
Following a not working conceptual code to do such:
# outer (recursive) Function
Function Get-ADGroupMembersRecursive {
param($ADGroup)
# inner recursive Function
Function InnerRecursion {
param($ADGroup)
# test if group was allready visited
If(-not ($StaticGroupTrackerArray -contains $Member)) {
# group was not visited, do recursive call
InnerRecursion -ADGroup $Member
}
# display Group Members
Get-ADGroupMember $Member
}
# create ‘Static’ Variable to track allready visited groups
New-Variable -Name StaticGroupTrackerArray -Value @() -Option AllScope
# intial call to the inner realy recursive Function
InnerRecursion -ADGroup $ADGroup
}
Isn’t that the beauty of Powershell? It can be “just” procedural/functional when needed and object oriented when that is called for.
So now, finally I found again the very very good articles from Ian Davis.
I highly recoment to read his whole Blog!
Prototypal Inheritance Using PowerShell (in more parts)
http://codepyre.com/2012/08/prototypal-inheritance-using-powershell/
Ad-hoc Polymorphism (Function Overloading) in PowerShell
http://codepyre.com/2012/08/ad-hoc-polymorphism-in-powershell/
greets
Peter Kriegel
founder member of the German speaking PowerShell Community
http://www.PowerShell-Group.eu
I have been researching how to properly use Powershell objects for days and *finally* I came across your post. Brilliantly explained… Thank you.
Great post. Prototype based objects can be helpful to make code reusable when creating custom cmdlets for complicated and repeatable tasks. As for it being like some other shells, that is not a bad thing. I like BASH in *nix environments, and you can do the same kinds of things in BASH (for example). It’s all about how good a developer (or administrator) you are and what you need your code to do (or redo multiple times). For really simple things, I use procedural script. For the more complex, then I break most things down into reusable functions. For even more complex things, I will make more use of objects.
powershell is multi-paradigm. Yes, it’s known to be OOP, because that’s a hot new feature. But it’s also imperative, functional and reflective.