PowerShell for N00bs 2: Power Under the Hood

"Hello, World!" 

That's how you write "Hello, World" in PowerShell.  No, this isn't a flashback to PowerShell for N00bs 1.  Back then, we skimmed over some interesting concepts.  Specifically:

  • PowerShell is Object Oriented.
  • "Hello, World" (and every object) has a ToString() method.

Object Oriented isn't very obvious to fuctional / procedural scripters (like us).  We're more used to .BAT and .CMD files where data is simply data, and to get information about that data, such as "How long is this string?" or "Where's the first occurence of the letter 'a' in that string?" is implemented by stand-alone functions that expect a string as input and will return an interger.

An object, as we said before, encapsulates:

  • data
  • properties of that data
  • fucntionality

Rather than turn this into a theoretical discussion of what is or is not OO, let's just use it and see if that helps us grok it. 

PSH> $aString = "Hello, World!"
PSH> $aString.ToString()
Hello, World!

We asserted earlier that every object has a ToString() method.  A 'method' is just a function that's created with the chunk of data, er, object.  To shove a string through its ToString() method isn't a big deal: we get out what we put in.  Let's try something we know will generate different output than input.

PSH> $aString.Length
13

That's a little more interesting.  We might be interested only in lines longer than 80 characters, for example.  Or we may want to use the line length as an index for SubString()... Oops, getting ahead of ourselves here.  The Length member doesn't have (parenthesis).  It doesn't do anything to the data - it's just data about the data (metadata, if you want to impress people.)  This is a 'property' of the data.  'Members' of the string object we created are just the collection of methods and properties.

This leads to the key question for this post: how do we find out what's available?

PSH> Get-Member -InputObject $aString

   TypeName: System.String

Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone()
CompareTo Method System.Int32 CompareTo(Object value),...
Contains Method System.Boolean Contains(String value)
CopyTo Method System.Void CopyTo(Int32 sourceIndex,...
EndsWith Method System.Boolean EndsWith(String value)...
Equals Method System.Boolean Equals(Object obj), Sy...
GetEnumerator Method System.CharEnumerator GetEnumerator()
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
GetTypeCode Method System.TypeCode GetTypeCode()
get_Chars Method System.Char get_Chars(Int32 index)
get_Length Method System.Int32 get_Length()
IndexOf Method System.Int32 IndexOf(Char value), Sys...
IndexOfAny Method System.Int32 IndexOfAny(Char[] anyOf)...
Insert Method System.String Insert(Int32 startIndex...
IsNormalized Method System.Boolean IsNormalized(), System...
LastIndexOf Method System.Int32 LastIndexOf(Char value),...
LastIndexOfAny Method System.Int32 LastIndexOfAny(Char[] an...
Normalize Method System.String Normalize(), System.Str...
PadLeft Method System.String PadLeft(Int32 totalWidt...
PadRight Method System.String PadRight(Int32 totalWid...
Remove Method System.String Remove(Int32 startIndex...
Replace Method System.String Replace(Char oldChar, C...
Split Method System.String[] Split(Params Char[] s...
StartsWith Method System.Boolean StartsWith(String valu...
Substring Method System.String Substring(Int32 startIn...
ToCharArray Method System.Char[] ToCharArray(), System.C...
ToLower Method System.String ToLower(), System.Strin...
ToLowerInvariant Method System.String ToLowerInvariant()
ToString Method System.String ToString(), System.Stri...
ToUpper Method System.String ToUpper(), System.Strin...
ToUpperInvariant Method System.String ToUpperInvariant()
Trim Method System.String Trim(Params Char[] trim...
TrimEnd Method System.String TrimEnd(Params Char[] t...
TrimStart Method System.String TrimStart(Params Char[]...
Chars ParameterizedProperty System.Char Chars(Int32 index) {get;}
Length Property System.Int32 Length {get;}

Yuck.  That's a lot to swallow.  Relax, we won't cover each one.  We're interested in the simple fact that we have a way to inspect the members for a given String.  It's useful to get a listing of names, but the definitions are lacking.  The '...' means the line length (or $line.length if we want to get some practice thinking OO) is greater than 80 characters, so PowerShell is being nice (read: a pain) and truncating the line so the output looks nice.

Oh, and we're being lazy here by typing -i instead of -InputObject.  PowerShell is being nice (read: amazingly well thought out) and expanding -i to -InputObject because it's the only parameter to Get-Member that begins with 'i.'  If we had a cmdlet or function that could accept -InputObject and -IDontKnow, -i is no longer unambiguous.  Now, we need to specify '-in' or '-id'.  Okay, enough of that digression.

The reason PowerShell is inserting the '...' is because it, by default, formats any list (such as an object's members) to a table, pretty-printed for your command window.  Pretty useless, in this case.  Well, formatting it as a table is the default, but we can change it.

PSH> Get-Member -i $aString | Format-List

This specifies the output formatter to be a list.  It's still pretty-printed somewhat, but at least each member's definition is listed in full.  So we've taken a long list and made it even longer.  Great.  And I just wanted to look at the, let's say, Replace() member.  Do I put the replacement string first, or the string to be replaced first?  Is it

PSH> $aString.Replace("goodbye", "hello")

or

PSH> $aString.Replace("hello", "goodbye")

Of course we can try it, but for purposes of this exercise, let's see if we can get the definition for the Replace() method by itself.  Maybe if we limit it to only one member, it won't format it as a table.

PSH> Get-Member -i $aString | Where-Object { $_.name -eq "replace" }

PSH> Get-Member -i $aString -Name replace # thanks to /\/\o\/\/ @ https://thepowershellguy.com

   TypeName: System.String

Name MemberType Definition
---- ---------- ----------
Replace Method System.String Replace(Char oldChar, Char newChar), System...

Same silly ellipsis, but we isolated the Replace() method.  Now, let's bring it together with the Format-List to see if we can cudgel PowerShell into doing what we want.

PSH> Get-Member -i $aString -Name "replace" | Format-List

TypeName : System.String
Name : Replace
MemberType : Method
Definition : System.String Replace(Char oldChar, Char newChar), System.String R
eplace(String oldValue, String newValue)

Okay, target string first, then replacement string.  Still, there's an annoying linebreak at the ... 80 character mark.  (Thank you, PowerShell.  No, really.  Not.)  For our last trick, let's try to get the definition by itself, without that linebreak.

PSH> (gm -InputObject $aString -Name 'replace').Definition
System.String Replace(Char oldChar, Char newChar), System.String Replace(String oldValue, String newValue)

Yes.

To review, we learned three things:

  • We can use Get-Member to inspect any object for its member set (methods and properties.)
  • We can use Where-Object to isolate an object from a list based on its name.
  • We can override the default output formatter to overcome PowerShell's insistence on truncating lines.