PowerShell Extended Types (includes a TYPES.XSD)   1 comment

One of my favorite features of PowerShell is the extended type system.  This system allows us to extend the .NET objects that are returned by the underlying .NET framework with bits of interesting stuff.  There’s two way to go about this.  First, by using the add-member cmdlet, it’s possible to add methods and properties to an instance of an object.  If we start with a "blank" object, we can create an object out of whole cloth.  Take a look at the following output
 
PS> get-stock|ft Symbol,Last,Change,@{l="ChangeP";f="{0:N2}";e={$_.ChangeP}} -auto
Symbol     Last Change ChangeP
——     —- —— ——-
MSFT      28.74  -0.72   -2.51
SCO        2.77  -0.02   -0.72
^DJI   12767.57   2.56    0.02
INFY      59.84   0.23    0.38
SUNW       6.29  -0.02   -0.32
 
I have a little script that collects the stock quotes for a number of companies.   (I have a special formatting file for the output, but that’s another blog).  Here’s the script, you can see how it takes advantage of the extendable type system. 
 
$SYMS = "MSFT","SCO","^DJI","INFY","SUNW"
$wc = new-object net.webclient
foreach ( $SYM in $SYMS )
{
    $yahoo = "
http://finance.yahoo.com/d/quotes.csv?s="
    $url = "${yahoo}${SYM}&f=sl1d1t1c1ohgv&e=.csv"
    $string = $wc.DownloadString($url)
    if ( $string )
    {
        trap { continue }
        $stock = $string.replace("`"","").replace("N/A","0").Trim().split(",")
        $obj = new-object System.Management.Automation.PSObject
        $obj.psobject.typenames[0] = "Custom.Stock"
        $obj | add-member NoteProperty Symbol  ([string]$stock[0])
        $obj | add-member NoteProperty Last    ([double]$stock[1])
        $obj | add-member NoteProperty Date    ([datetime]$stock[2])
        $obj | add-member NoteProperty Time    ([datetime]$stock[3])
        $obj | add-member NoteProperty Change  ([double]$stock[4])
        $obj | add-member NoteProperty ChangeP ([double]$stock[4]/[double]$stock[1] * 100)
        $obj | add-member NoteProperty Open    ([double]$stock[5])
        $obj | add-member NoteProperty High    ([double]$stock[6])
        $obj | add-member NoteProperty Low     ([double]$stock[7])
        $obj | add-member NoteProperty Volume  ([int]$stock[8])
        $obj | add-member NoteProperty InPort  ($pf -contains $SYM)
        $obj
    }
}
 
So, that’s a way to use the add-member cmdlet to dynamically extend an object.   This could be done with any object, in this example, I’m creating an object out of nothing, but you can do the same thing with any object.  Here’s another example, where I interact with some performance counters, specifically the idle time.
 
PS> $idle = get-idle
PS> $idle
CPUCount         : 2
Percent          : 100
CategoryName     : Process
CounterHelp      : % Processor Time is the percentage of elapsed time that all
                   of process threads used the processor to execution instructi
                   ons. An instruction is the basic unit of execution in a comp
                   uter, a thread is the object that executes instructions, and
                    a process is the object created when a program is run. Code
                    executed to handle some hardware interrupts and trap condit
                   ions are included in this count.
CounterName      : % Processor Time
CounterType      : Timer100Ns
InstanceLifetime : Global
InstanceName     : Idle
ReadOnly         : True
MachineName      : JIMTRUD4
RawValue         : 10053571562500
Site             :
Container        :
 
PS> $idle.getidle()     # I’ll call my custom script method!
99
PS> $idle.getidle()
88                                        # the reason this fell so much is that I put some load on the system
PS> $idle.getidle()
90
Here’s the script – I’m sure it could be written better, but that’s not the point.
 
param ( $systems = @( $env:computername ))
$PerfCnt = "System.Diagnostics.PerformanceCounter"
$PerfCat = "System.Diagnostics.PerformanceCounterCategory"
foreach($system in $systems )
{
    $Info = "Process","% Processor Time","Idle",$system
    $obj = new-object $PerfCnt $Info
    $pcc = new-object $PerfCat Processor,$system
    $idColCol = $pcc.ReadCategory()
    [int]$CPUCount = $idColcol[‘% idle time’].keys.count – 1
    $per = $obj.nextvalue() / $CPUCount
    # for some reason, we need to sleep here and then check again
    # I haven’t bothered to find out why
    sleep 1
    [int]$per = $obj.nextvalue() / $CPUCount
    # add some members to the the performance counter
    $obj | add-member NoteProperty CPUCount $CPUCount
    $obj | add-member NoteProperty Percent $per
    $obj | add-member ScriptMethod GetIdle {
        $this.Percent = [int]($this.NextValue() / $this.CPUCount)
        $this.Percent
        }
    # emit the object
    $obj
}
 
Viola!  I’ve extended the instances of the PerformanceCounter objects created in this script
 
However, there is another way to extend an object instance.  You can create a blob of XML (in a file) and then load that file into your session with the update-typedata cmdlet – whammo! – everytime you create an instance of a specific object, it will have your custom extensions.   We have a number of these extensions in the standard release to ease using the shell.  The best case in point is the difference between System.Array and System.Collections.ArrayList.  "Length" is the property in System.Array that provides the count of the elements of the array, but System.Collections.ArrayList uses "Count".  We extended the System.Array type with a "Count" property that is an alias to the Length property which actually exists.  This way, regardless of whether you’ve got an array or arraylist, "Count" will work!  Here’s the blob of XML that does the trick.
 
<Types>
 <Type>
  <Name>System.Array</Name>
  <Members>
   <AliasProperty>
    <Name>Count</Name>
    <ReferencedMemberName>Length</ReferencedMemberName>
   </AliasProperty>
  </Members>
 </Type>
<Types>
 
Once you have this bit of XML in a file, you can use the update-typedata cmdlet to add the blob to your environment.  Let’s make our own little extension so you can see how it works.
 
PS> cat mynewtype.ps1xml
<Types>
 <Type>
  <Name>System.Array</Name>
  <Members>
   <AliasProperty>
    <Name>HappyAlias</Name>
    <ReferencedMemberName>Length</ReferencedMemberName>
   </AliasProperty>
  </Members>
 </Type>
</Types>
 
As you can see, it’s pretty simple.  Now let’s load it:
 
PS> update-typedata mynewtype.ps1xml
PS> ,(1,2,3,4)|gm
   TypeName: System.Object[]
Name               MemberType    Definition
—-               ———-    ———-
Count              AliasProperty Count = Length
HappyAlias         AliasProperty HappyAlias = Length
SyncRoot           Property      System.Object SyncRoot {get;}
PS> ,(1,2,3,4).happyalias
4
PS>
We’ve extended the array type!  However, figuring out what is possible isn’t documented anywhere, so it’s a little tricky to create these.  We allow all sorts of extensions; a bunch of different property extensions, methods (both script and code). With this in mind, I created an XSD that allows me to create types extensions much more easily.  Now I can edit my type extensions in Visual Studio and they nearly write themselves.  Note that this XSD may have some errors, and as time goes on, I’ll correct it as I can.  However, in the mean time, it’s better than a poke in the eye with a sharp stick.
 
I’m also working on an XSD for our formatting – stay tuned for that
 
 
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="
http://www.w3.org/2001/XMLSchema">
  <xs:element name="Name" type="xs:string" />
  <xs:complexType name="NoteProperty">
      <xs:all>
        <xs:element ref="Name" />
        <xs:element name="Value" type="xs:string" />
      </xs:all>
    </xs:complexType>
  <xs:complexType name="AliasProperty">
      <xs:all>
        <xs:element ref="Name" />
        <xs:element name="ReferencedMemberName" type="xs:string" />
      </xs:all>
    </xs:complexType>
  <xs:complexType name="ScriptMethod">
      <xs:all>
        <xs:element ref="Name" />
        <xs:element name="Script" type="xs:string" />
      </xs:all>
    </xs:complexType>
  <xs:complexType name="ScriptProperty">
      <xs:sequence>
        <xs:element minOccurs="1" maxOccurs="1" ref="Name" />
        <xs:element minOccurs="0" maxOccurs="1" name="GetScriptBlock" type="xs:string" />
        <xs:element minOccurs="0" maxOccurs="1" name="SetScriptBlock" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
 
  <xs:complexType name="CodeReference">
      <xs:all>
        <xs:element name="TypeName"/>
        <xs:element name="MethodName"/>
      </xs:all>
    </xs:complexType>
 
  <xs:complexType name="CodeMethod">
      <xs:sequence>
        <xs:element name="Name" type="xs:string"/>
        <xs:element name="CodeReference" type="CodeReference"/>
      </xs:sequence>
    </xs:complexType>
  <xs:complexType name="CodeProperty">
      <xs:all>
        <xs:element name="Name" type="xs:string" />
        <xs:element minOccurs="0" maxOccurs="1" name="GetCodeReference" type="CodeReference" />
        <xs:element minOccurs="0" maxOccurs="1" name="SetCodeReference" type="CodeReference" />
      </xs:all>
    </xs:complexType>
  <xs:complexType name="PropertySet">
      <xs:sequence>
       <xs:element ref="Name" />
       <xs:element name="ReferencedProperties" />
      </xs:sequence>
    </xs:complexType>
  <xs:complexType name="Members">
     <xs:sequence>
      <xs:choice maxOccurs="unbounded">
       <xs:element name="NoteProperty" type="NoteProperty" />
       <xs:element name="AliasProperty" type="AliasProperty" />
       <xs:element name="ScriptProperty" type="ScriptProperty" />
       <xs:element name="CodeProperty" type="CodeProperty" />
       <xs:element name="ScriptMethod" type="ScriptMethod" />
       <xs:element name="CodeMethod" type="CodeMethod" />
       <xs:element name="MemberSet" type="MemberSet" />
       <xs:element name="PropertySet" type="PropertySet" />
      </xs:choice>
     </xs:sequence>
   </xs:complexType>
 
  <xs:complexType name="MemberSet">
      <xs:all>
        <xs:element name="Name" type="xs:string"/>
        <xs:element name="Members" type="Members" />
      </xs:all>
  </xs:complexType>
 
  <xs:element name="Types">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="Type">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Name" type="xs:string" />
              <xs:element name="Members" type="Members" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
 
I’m sorry about the length – i should learn to stop typing.
 
 
Advertisements

Posted February 19, 2007 by jtruher3 in PowerShell

One response to “PowerShell Extended Types (includes a TYPES.XSD)

Subscribe to comments with RSS.

  1. awesome! thanks for the XSD!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: