James w's profileTime is an illusion. Lun...PhotosBlogListsMore Tools Help

Blog


    6/22/2009

    PowerShell and the Service Manager Type Environment

    Service Manager, like Operations Manager, has a dynamic type system.  ManagementPacks define types, called classes, which define the actual data that you want to keep track of.  You declare a class which may or may not be based on another class and you can add properties to your class, and manipulate it in a number of ways.  Once you've imported your management pack, you'll have access to that new class, and can create instances of that class.  There are two discrete categorizations of types.  The first is a simple class, and the more complex is called a type projection.  In this blog, we'll discuss how to find out what Service Manager stores as simple classes and how to discover them.

    It's important to note that these are not .NET types, these are types as defined in the Service Manager environment.  When you actually create instances of these types, they are returned as instances of the .NET type of Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject.  That object has a property called PropertyCollection which is where the actual data is held.  I'll talk more about that in a future blog, but for now, what you should know is that Service Manager has it's own types which are created in Management Packs.

    To get access to the list of classes that have been imported to the Service Manager environment, we’ll use the EnterpriseManagementGroup object again (see my previous posts).  The EnterpriseManagementGroup object has an interface named "EntityTypes", which has a number of methods that we'll investigate.  First, we'll create an EnterpriseManagementGroup, and take a look at the EntityTypes interface.

    PS> $EMG = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup localhost
    PS> $EMG.EntityTypes|get-member
    
    
       TypeName: Microsoft.EnterpriseManagement.EntityTypeManagement
    
    Name                       MemberType Definition
    ----                       ---------- ----------
    Equals                     Method     bool Equals(System.Object obj)
    GetCategories              Method     System.Collections.Generic.IList[Micro...
    GetCategory                Method     Microsoft.EnterpriseManagement.Configu...
    GetCategoryList            Method     System.Collections.Generic.List[Micros...
    GetChildEnumerations       Method     System.Collections.Generic.IList[Micro...
    GetClass                   Method     Microsoft.EnterpriseManagement.Configu...
    GetClasses                 Method     System.Collections.Generic.IList[Micro...
    GetClassesList             Method     System.Collections.Generic.List[Micros...
    GetEnumeration             Method     Microsoft.EnterpriseManagement.Configu...
    GetEnumerationList         Method     System.Collections.Generic.List[Micros...
    GetEnumerations            Method     System.Collections.Generic.IList[Micro...
    GetHashCode                Method     int GetHashCode()
    GetRelationshipClass       Method     Microsoft.EnterpriseManagement.Configu...
    GetRelationshipClasses     Method     System.Collections.Generic.IList[Micro...
    GetRelationshipClassesList Method     System.Collections.Generic.List[Micros...
    GetTopLevelEnumerations    Method     System.Collections.Generic.IList[Micro...
    GetType                    Method     type GetType()
    GetTypeProjection          Method     Microsoft.EnterpriseManagement.Configu...
    GetTypeProjectionList      Method     System.Collections.Generic.List[Micros...
    GetTypeProjections         Method     System.Collections.Generic.IList[Micro...
    ToString                   Method     string ToString()

    The methods break up in two big groups: The first group returns all various different objects that you can define in a management pack (those objects which have to do with types, anyway). That list is:

    GetCategories
    GetClasses
    GetEnumerations
    GetRelationshipClasses
    GetTopLevelEnumerations
    GetTypeProjections
    

    The other methods on this interface take arguments and return a reduced set of results based on the arguments. It is important to note that the Service Manager environment caches most of this configuration information, so when we get all of them, it doesn't result in lots of trips to the database. That's good news from a performance perspective.  From a PowerShell perspective, it means that we can grab all the data and use the PowerShell filtering capabilities rather than bothering with the other available methods.

    If we want to see all the different classes that Service Manager knows about, we can do that easily:

    PS> $EMG.EntityTypes.GetClasses() 
    
    
    PropertyCollection     : {DisplayName}
    Base                   :
    Hosted                 : False
    Singleton              : False
    Extension              : False
    OptimizationCollection : {}
    FullTextSearchable     :
    XmlTag                 : ClassType
    Abstract               : True
    Accessibility          : Public
    ManagementGroup        : WIN-752HJBSX24M
    ManagementGroupId      : 386f5f57-9f7a-3c6b-1f53-ccc02d6206d4
    Name                   : System.Entity
    Id                     : ac5cddfc-a96a-ee99-745d-ec74845f53f6
    DisplayName            : Object
    Description            : All objects
    LanguageCode           : ENU
    Comment                :
    Status                 : Unchanged
    LastModified           : 5/14/2009 11:56:48 PM
    TimeAdded              : 5/14/2009 11:56:48 PM
    
    PropertyCollection     : {}
    Base                   : ManagementPackElementUniqueIdentifier=ac5cddfc-a96a-ee
                             99-745d-ec74845f53f6
    Hosted                 : False
    Singleton              : False
    Extension              : False
    . . .

    Wow! There's a lot here - let's find out how many:

    PS> $Classes = $EMG.EntityTypes.GetClasses()
    PS> $Classes.Count 231

    We should determine the .NET type as well, as we can use this when we create our formatting instructions

    PS> $classes[0].gettype().fullname
    Microsoft.EnterpriseManagement.Configuration.ManagementPackClass

    With a result as large as this, it makes more sense to see as a table, rather than a list and I’ll select the first 5, just to cut down on space, so now we have:

    PS> $Classes|select-object -first 5| format-table Abstract,Name,DisplayName -au
    Abstract Name                       DisplayName
    -------- ----                       -----------
        True System.Entity              Object
        True System.AdminItem           Admin Item
       False System.Announcement.Config Config
       False System.Announcement.Item   Announcement
        True System.Collections         Collections
    

    We can see that this supports the PowerShell filters as well, let’s find all the types which pertain to printers.  The first approach would be to select only those objects whose name matches print!

    PS> $EMG.EntityTypes.GetClasses()|?{$_.name –match "print"}|
    >> format-table -auto Abstract,Name,DisplayName
    >>

    Abstract Name DisplayName
    -------- ---- -----------
    True System.Printer Printers
    False Microsoft.AD.Printer Active Directory Printers

    The objects returned work well with the PowerShell environment.  We can invoke the other methods on the EntityTypes interface:

    PS> $emg.EntityTypes.GetCategories().Count
    285
    PS> $emg.EntityTypes.GetCategories()|select-object -first 5|format-table Name,DisplayName -auto
    
    Name                                                                  DisplayName
    ----                                                                  -----------
    ServiceManager.ActivityManagement.EditActivity.Task.FlagCategory                 
    ServiceManager.ActivityManagement.Library.ApprovalEnumVisibleCategory            
    ServiceManager.ActivityManagement.Library.DecisionEnumVisibleCategory            
    ServiceManager.ActivityManagement.Library.DecisionEnumCategory                   
    ServiceManager.ActivityManagement.Library.ApprovalEnumCategory                   
    
    
    PS> $emg.EntityTypes.GetClasses().Count
    231
    PS> $emg.EntityTypes.GetClasses()|select-object -first 5|format-table Name,DisplayName -auto
    
    Name                       DisplayName 
    ----                       ----------- 
    System.Entity              Object      
    System.AdminItem           Admin Item  
    System.Announcement.Config Config      
    System.Announcement.Item   Announcement
    System.Collections         Collections 
    
    
    PS> $emg.EntityTypes.GetEnumerations().Count
    414
    PS> $emg.EntityTypes.GetEnumerations()|select-object -first 5|format-table Name,DisplayName -auto
    
    Name                                                        DisplayName          
    ----                                                        -----------          
    ActivityAreaEnum.Messaging.Client                           Client               
    ServiceManager.ConfigurationManagement.WindowsPrintersTasks Windows Printer Tasks
    IncidentSourceEnum.DCM                                      SCCM (DCM)           
    System.Knowledge.CategoryEnum.Software                                           
    ActivityStageEnum.Develop                                   Develop              
    
    
    PS> $emg.EntityTypes.GetRelationshipClasses().Count
    118
    PS> $emg.EntityTypes.GetRelationshipClasses()|select-object -first 5|format-table Name,DisplayName -auto
    
    Name                                DisplayName                     
    ----                                -----------                     
    System.ComputerPrimaryUser          Computer Primary User           
    System.ConfigItemContainsConfigItem Config Item Contains Config Item
    System.ConfigItemHasFileAttachment  Config Item Has File Attachment 
    System.ConfigItemImpactsCustomers   Config Item Impacts Customers   
    System.ConfigItemOwnedByUser        Config Item Owned By User       
    
    
    PS> $emg.EntityTypes.GetTopLevelEnumerations().Count
    72
    PS> $emg.EntityTypes.GetTopLevelEnumerations()|select-object -first 5|format-table Name,DisplayName -auto
    
    Name                                                                          DisplayName         
    ----                                                                          -----------         
    System.Internal.ManagementPack                                                Management Pack     
    System.WorkItem.ActionLogEnum                                                 Action Log Enum     
    ChangeManagement.CreateTask                                                   Create Task         
    Microsoft.EnterpriseManagement.ServiceManager.UI.Authoring.AllObjectTemplates All Object Templates
    System.Knowledge.CategoryEnum                                                                     
    
    
    PS> $emg.EntityTypes.GetTypeProjections().Count
    43
    PS> $emg.EntityTypes.GetTypeProjections()|select-object -first 5|format-table Name,DisplayName -auto
    
    Name DisplayName
    ---- -----------
                    

    It looks like we have reasonable output for everything except TypeProjections.  By looking at the count, I can tell that we have some sort of results, so let’s take a closer look at the object:

    PS> $emg.EntityTypes.GetTypeProjections()[0]
    
    Key                                     Value
    ---                                     -----
    SyncStatus                              {}
    

    That’s not terribly useful, perhaps get-member will help me.

    PS> $emg.EntityTypes.GetTypeProjections()|get-member
    
    
       TypeName: Microsoft.EnterpriseManagement.Configuration.ManagementPackTypeProjection
    
    Name                MemberType            Definition
    ----                ----------            ----------
    CreateNavigator     Method                System.Xml.XPath.XPathNavigator Cr...
    Equals              Method                bool Equals(System.Object obj)
    GetCategories       Method                System.Collections.Generic.IList[M...
    GetDisplayString    Method                Microsoft.EnterpriseManagement.Con...
    GetEnumerator       Method                System.Collections.Generic.IEnumer...
    GetFolders          Method                Microsoft.EnterpriseManagement.Con...
    GetHashCode         Method                int GetHashCode()
    GetImageReferences  Method                System.Collections.Generic.IEnumer...
    GetKnowledgeArticle Method                Microsoft.EnterpriseManagement.Con...
    GetManagementPack   Method                Microsoft.EnterpriseManagement.Con...
    GetType             Method                type GetType()
    Reconnect           Method                System.Void Reconnect(Microsoft.En...
    ToString            Method                string ToString()
    WriteXml            Method                System.Void WriteXml(System.Xml.Xm...
    Item                ParameterizedProperty Microsoft.EnterpriseManagement.ITy...
    Accessibility       Property              Microsoft.EnterpriseManagement.Con...
    Alias               Property              System.String Alias {get;}
    Comment             Property              System.String Comment {get;set;}
    ComponentCollection Property              System.Collections.Generic.IList`1...
    Description         Property              System.String Description {get;set;}
    DisplayName         Property              System.String DisplayName {get;set;}
    Id                  Property              System.Guid Id {get;}
    LanguageCode        Property              System.String LanguageCode {get;set;}
    LastModified        Property              System.DateTime LastModified {get;...
    ManagementGroup     Property              Microsoft.EnterpriseManagement.Ent...
    ManagementGroupId   Property              System.Guid ManagementGroupId {get;}
    Name                Property              System.String Name {get;}
    Parent              Property              Microsoft.EnterpriseManagement.ITy...
    Status              Property              Microsoft.EnterpriseManagement.Con...
    TargetConstraint    Property              Microsoft.EnterpriseManagement.Con...
    TargetEndpoint      Property              Microsoft.EnterpriseManagement.Con...
    TargetType          Property              Microsoft.EnterpriseManagement.Con...
    TimeAdded           Property              System.DateTime TimeAdded {get;set;}
    Type                Property              Microsoft.EnterpriseManagement.Con...
    TypeProjection      Property              Microsoft.EnterpriseManagement.Con...
    XmlTag              Property              System.String XmlTag {get;}
    

    Oho!  it looks like this object has an enumerator.  This means that when PowerShell attempts to format the object, it will call the enumerator and format the enumerated contents (rather than the object).  We can suppress this in PowerShell by specifying the –EXPAND parameter with format-table:

    PS> $emg.EntityTypes.GetTypeProjections()|select-object -first 5|
    >> format-table -Expand coreonly Name,DisplayName -au
    >>
    
    Name                                                          DisplayName
    ----                                                          -----------
    Microsoft.SystemCenter.LinkingFramework.SyncStatus.Projection
    System.LinkingFramework.DataConnector.Projection
    OpsMgrConnector.Config.Projection
    System.NotificationChannel.SMTP.ProjectionType                SMTP Projection Type
    System.User.Projection                                        User Projection
    

    That’s better! Although I have a feeling that this really isn’t a good long term solution, perhaps we’ll deal with this in a future posting.

    Now that we have a set of these methods we can invoke, it’s a great opportunity to build a PowerShell V2 Module.  We can easily convert these bits of script into functions and aggregate those functions into a module.  All I need to do is put my module (as a .psm1 file) in the right place (see PowerShell V2 documentation on Modules for more help) and call import-module!

    Here’s the script file:

    # MODULE VARIABLES
    $SMDIR = "C:\Program Files\Microsoft System Center\Service Manager 2010"
    $SMDLL = "${SMDIR}\SDK Binaries\Microsoft.EnterpriseManagement.Core.dll"
    $EMGTYPE = "Microsoft.EnterpriseManagement.EnterpriseManagementGroup"
    
    # Before anything load the Service Manager core dll
    [reflection.assembly]::LoadFile( $SMDLL ) | out-null
    
    # MODULE FUNCTIONS
    # Create an EnterpriseManagementGroup object
    function New-EMG
    {
       param ( $ComputerName = "localhost" ) 
       new-object $EMGTYPE $ComputerName
    }
    
    # Return the Categories
    function Get-SMCategory
    {
        param ( $CategoryName )
        if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
        $EMG.EntityTypes.GetCategories()|?{$_.Name -match $CategoryName }
    }
    # Return the Classes
    function Get-SMClass
    {
        param ( $ClassName )
        if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
        $EMG.EntityTypes.GetClasses()|?{$_.Name -match $ClassName }
    }
    # Return the RelationshipClasses
    function Get-SMRelationshipClass
    {
        param ( $RelationshipClassName )
        if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
        $EMG.EntityTypes.GetRelationshipClasses()|?{$_.Name -match $RelationshipClassName }
    }
    # Return the TopLevelEnumerations
    function Get-SMTopLevelEnumeration
    {
        param ( $TopLevemEnumerationName )
        if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
        $EMG.EntityTypes.GetTopLevelEnumerations()|?{$_.Name -match $TopLevemEnumerationName }
    }
    # Return the TypeProjections
    function Get-SMTypeProjection
    {
        param ( $TypeProjetionName )
        if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
        $EMG.EntityTypes.GetTypeProjections()|?{$_.Name -match $TypeProjetionName }
    }
    # We want to have an EMG in our environment!
    $GLOBAL:EMG = new-EMG

    Notice also that I added a parameter to the functions so I can pass in a string and reduce the result without having to always add my own where-object pipeline.  Notice further that I created a global instance of the EnterpriseManagementGroup, that way I can use it outside of the module.  I’ll use this as the contents for my EntityTypes.PSM1 file and import the module. 

      You should note that this assumes an installation on the Server machine only.  If you want to run these on a machine where only the console is installed, you’ll need to load the SMDLL a little differently.   You would need to do the following instead:

      [Reflection.Assembly]::LoadWithPartialName("Microsoft.EnterpriseManagement.Core")

      And you will also need to provide the name of the Service Manager server system when you create your EnterpriseManagementGroup object by calling new-EMG:

      $GLOBAL:EMG = new-EMG servername

    On to the module!

    PS> get-childitem C:\users\Administrator\Documents\WindowsPowerShell\Modules\EntityTypes
    
    
        Directory: C:\users\Administrator\Documents\WindowsPowerShell\Modules\EntityTypes
    
    
    Mode                LastWriteTime     Length Name
    ----                -------------     ------ ----
    -a---         6/19/2009   2:27 PM       1726 EntityTypes.psm1
    
    
    PS> get-module -list
    
    ModuleType Name                      ExportedCommands
    ---------- ----                      ----------------
    Script     EntityTypes               {}
    Manifest   BitsTransfer              {}
    Manifest   PSDiagnostics             {}
    
    
    PS> import-module entitytypes
    

    and we can see that my commands are all present!

    PS> get-module entitytypes|fl
    
    
    Name              : entitytypes
    Path              : C:\Users\Administrator\Documents\WindowsPowerShell\Modules\entitytypes\entitytypes.psm1
    Description       :
    ModuleType        : Script
    Version           : 0.0
    NestedModules     : {}
    ExportedFunctions : {Get-Category, Get-Class, Get-RelationshipClass, Get-TopLevelEnumeration...}
    ExportedCmdlets   : {}
    ExportedVariables : {}
    ExportedAliases   : {}
    

    And I can use them just like a cmdlet:

    PS> get-class system.user$|ft abstract,name,displayname -au
    
    Abstract Name        DisplayName
    -------- ----        -----------
        True System.User Users
    

    In my next post, I’ll discuss the Service Manager instance space and create cmdlets to use against the actual data that we store in Service Manager.

    6/1/2009

    Even more with Management Packs

    Last post, I went through the process of exporting management packs, so we can see what they do and what they define.  In this post, I'll discuss importing management packs.  In order to get any benefit from a Management Pack (MP), it needs to be added to the Service Manager platform.  The process of adding an MP to the system is called "Importing".   For this example, we'll use just a very simple management pack (the contents aren't really interesting, but to go through the exercise we need one of these).

    <ManagementPack ContentReadable="true" SchemaVersion="1.1" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
               xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <Manifest>
        <Identity>
          <ID>Simple.ManagementPack</ID>
          <Version>7.0.5622.0</Version>
        </Identity>
        <Name>A Simple ManagementPack</Name>
        <References>
          <Reference Alias="System">
            <ID>System.Library</ID>
            <Version>7.0.5622.0</Version>
            <PublicKeyToken>9396306c2be7fcc4</PublicKeyToken>
          </Reference>
        </References>
      </Manifest>
      <TypeDefinitions>
        <EntityTypes>
          <ClassTypes>
            <ClassType ID="Simple.Class" Accessibility="Public" Abstract="false"  
                           Base="System!System.Entity" Hosted="false"  
                           Singleton="false" Extension="false">
              <Property ID="Id"     Type="guid"   Key="true"  Required="true"  />
              <Property ID="Name"   Type="string" Key="false" Required="false" 
                           CaseSensitive="false" MaxLength="256" MinLength="0" />
              <Property ID="Value1" Type="string" Key="false" Required="false"  
                           CaseSensitive="false" MaxLength="256" MinLength="0" />
              <Property ID="Value2" Type="string" Key="false" Required="false"  
                           CaseSensitive="false" MaxLength="256" MinLength="0" />
            </ClassType>
          </ClassTypes>
        </EntityTypes>
      </TypeDefinitions>
      <LanguagePacks>
        <LanguagePack ID="ENU" IsDefault="true">
          <DisplayStrings>
            <DisplayString ElementID="Simple.Class">
              <Name>Simple Class</Name>
              <Description>A simple class declaration</Description>
            </DisplayString>
          </DisplayStrings>
        </LanguagePack>
      </LanguagePacks>
    </ManagementPack>

    The Service Manager console has a way to import this, of course, here's what it looks like:

    ImportMP001

    But you can also do this from PowerShell with a few of simple lines of script

    # the two .NET types, we'll need to create
    $EMGTYPE = "Microsoft.EnterpriseManagement.EnterpriseManagementGroup"
    $MPTYPE  = "Microsoft.EnterpriseManagement.Configuration.ManagementPack"
    # Create a connection to the Data Access Service
    $EMG = new-object ${EMGTYPE} localhost
    # Create a management pack object based on the management pack file and 
    # connection to the Data Access Service
    $MP = new-object ${MPTYPE} c:\temp\Simple.ManagementPack.xml,$EMG
    # Call the import method on the ManagementPacks interface - all done!
    $EMG.ManagementPacks.ImportManagementPack($MP)

    As you can see, it's pretty straightforward.  However, this isn't the whole story.  If you notice in the XML above, there's a section for "References", this section of the MP allows you to declare that your MP relies on other MPs for class definitions, etc.  This means that if those references aren't installed, you're MP isn't going to work.  We go along way to keep bad data out of the system, so if you try to import an MP which references an MP which isn't present on the system, then that import will fail.  When you're considering a single MP, working out these references may not be to bad, but if you have a number of MPs that you want to import and some of them depend on other new imported MPs, you've got to get the order correct, or you won't be able to import.  Most of the time, you'll wind up inspecting those sections of the MPs, create your order, and finally import the MPs.  (I've seen a number of batch files that do this very thing, so I know that this happens).  That's no good as it's an awful waste of time and error prone as well.  I've created a script to go through any number of MPs and figure out what order they should be installed, and then install them!  The script is built so you can pipe the output of get-childitem to it. 

    From a pseudo-code perspective, here's what we need to do

    Find out what MPs are already installed
    and then
    FOREACH of MPs to install
       retrieve the references
       FOREACH reference
             check to see if the referenced MPs is not already installed (or will be installed)
             IF the needed MP is not installed, skip for now
             IF all the references are
                 installed and 
                 the version is compatible and 
                 the referenced MP is sealed and 
                 the keytoken matches
             THEN
                 add this MP to the list of MPs to which we'll be able to install
                 remove this MP from the list of MPs that we want to install
                 add it to the list of MPs that are already installed (since it will be)
                 since the list of MPs to install has now changed, restart the main loop
    
    

    If there are any MPs left in the list to install, it means that we weren't able to resolve the references, so we won't be able to install that MP.  Note that the failure can be for any reason, it could rely on an unsealed MP (I don’t want to discuss sealing here, suffice it to say that sealing an MP makes it immutable, this way we know the things that we rely on won’t change), the reference could be missing, it could require the wrong version, or it could have an incorrect KeyToken.   There really isn't much automatic remediation that we can do, but we can report what went wrong.

    So, now that we have a mechanism to put MPs in order, let's see how it looks.

    
    

    For our simple test, we'll create 6 MPs, each one of them dependent on the next MP (from an alphabetical point of view). 

    1. SimpleMP.A depends on SimpleMP.B
    2. SimpleMP.B depends on SimpleMP.C
    3. SimpleMP.C depends on SimpleMP.D
    4. SimpleMP.D depends on SimpleMP.L
    5. SimpleMP.L depends on SimpleMP.M
    6. SimpleMP.M depends on System.Library

    this means that the need to be installed in the opposite order, M installed before L, etc. 

    First we'll start by importing the XML file, since XML files can't be sealed, we should get an error for every MP except the only one that can be installed (SimpleMP.M, since it relies on System.Library).

    ImportMP002

    Now we can see where we have problems, and which MPs we can install.  Because I'm importing unsealed MPs, the references that I had can't be satisfied.  Only SimpleMP.M can be installed, since it has only one reference to a sealed MP (System.Library).  Note also, that SimpleMP.L can't be installed because it references SimpleMP.M which is unsealed.  The other MPs can't be installed because their references aren't there.  Now, let's try this again with sealed MPs.

    PS> Get-ChildItem *.mp
    
    
        Directory: Microsoft.PowerShell.Core\FileSystem::C:\test
    
    
    Mode                LastWriteTime     Length Name
    ----                -------------     ------ ----
    -a---          4/2/2009  12:49 PM       4608 SimpleMP.A.mp
    -a---          4/2/2009  12:49 PM       4608 SimpleMP.B.mp
    -a---          4/2/2009  12:49 PM       4608 SimpleMP.C.mp
    -a---          4/2/2009  12:49 PM       4608 SimpleMP.D.mp
    -a---          4/2/2009  12:49 PM       4608 SimpleMP.L.mp
    -a---          4/2/2009  12:49 PM       4608 SimpleMP.M.mp
    

    If we tried to import these MPs in alphabetical order, only the last one would succeed, so we could import them by creating a script which does the import explicitly, one at a time, but we can use our script to do it in one fell swoop.  This means that we don't really have to work out the dependencies manually - automation - that's the key!

    PS> get-childitem *.mp|./Import-ManagementPack -whatif 
    WhatIf: Importing Management Pack SimpleMP.M
    WhatIf: Importing Management Pack SimpleMP.L
    WhatIf: Importing Management Pack SimpleMP.D
    WhatIf: Importing Management Pack SimpleMP.C
    WhatIf: Importing Management Pack SimpleMP.B
    WhatIf: Importing Management Pack SimpleMP.A

    Wahoo!  Even though the MPs have dependencies in reverse to the order returned by get-childitem, they'll be installed in the correct order.  Granted, this example is pretty simple and the dependencies are straightforward, however, this script should handle pretty complicated sets of dependencies.

    When actually coding this script, I decided that I wanted to make sure that I could be a bit more flexible than what was strictly possible from within Service Manager.  So I added options to avoid checking for the correct version, whether the MP in the reference is sealed and if the KeyToken is correct.    You still won’t be able to actually import if some of these things are wrong, but you’ll see the order if everything was correct and proper.  I also added -Verbose and -Debug parameters as well as a -WhatIf parameter which allows you to not actually Import the MPs, but will tell you what order they would be installed.  The script it a little on the larger size (about 200 lines), so I'm not going to walk through the script in this blog, but the code is fairly well commented. 

    Here's a link to the script:

    3/10/2009

    More with management packs

    Last post, I wrote about retrieving management packs from Service Manager and I don't really have a lot more to say about retrieving the management pack information, except for provide a way where we don't have to specify formatting.   Generally, I want to see whether the management pack is sealed, the version number and then name, which translates into using format-table like this:

    format-table Sealed,Version,Name -autosize

    I'll create a ServiceManager.Format.ps1xml file which will format the default view of management packs.

    <configuration>
     <viewdefinitions>
      <view>
       <name>ManagementPackView</name>
       <viewselectedby>
        <typename>Microsoft.EnterpriseManagement.Configuration.ManagementPack</typename>
       </viewselectedby>
       <tablecontrol>
        <autosize />
        <tableheaders>
         <tablecolumnheader>
          <label>Sealed</label>
         </tablecolumnheader>
         <tablecolumnheader>
          <label>Version</label>
         </tablecolumnheader>
         <tablecolumnheader>
          <label>Name</label>
         </tablecolumnheader>
        </tableheaders>
        <tablerowentries>
         <tablerowentry>
          <tablecolumnitems>
           <tablecolumnitem>
            <propertyname>Sealed</propertyname>
           </tablecolumnitem>
           <tablecolumnitem>
            <propertyname>Version</propertyname>
           </tablecolumnitem>
           <tablecolumnitem>
            <propertyname>Name</propertyname>
           </tablecolumnitem>
          </tablecolumnitems>
         </tablerowentry>
        </tablerowentries>
       </tablecontrol>
      </view>
     </viewdefinitions>
    </configuration>
    

    In order to add this to my environment, all I need to do is use the Update-FormatData cmdlet using the filename as an argument.  After that, retrieving management packs will be be in the format that I want.

    PS> $MGroup.ManagementPacks.GetManagementPacks()
    
    Sealed Version    Name
    ------ -------    ----
    True   7.0.3683.0 ServiceManager.ServiceMaps.Library
    True   7.0.3683.0 Microsoft.SystemCenter.InstanceGroup.Library
    ...
    False  7.0.3683.0 ServiceManager.OpsMgrConnector.Configuration
    
    

    So less typing for me, yay! 

    For even less typing, I'll create a script called Get-ManagementPack which creates a connection to Service Manager and retrieves the management packs.

    here's the script:

    PS> get-content Get-ManagementPack.ps1
    param ( $computerName = "localhost" )
    $SMDIR = "C:\Program Files\Microsoft System Center\Service Manager 2010"
    $COREDLL = "${SMDIR}/SDK Binaries/Microsoft.EnterpriseManagement.Core.dll"
    [reflection.assembly]::LoadFile($COREDLL) | out-null
    $MGroup = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup $computerName
    $MGroup.ManagementPacks.GetManagementPacks()
    

    and to run:

    PS> Get-ManagementPack
    
    Sealed Version    Name
    ------ -------    ----
    True   7.0.3683.0 ServiceManager.ServiceMaps.Library
    True   7.0.3683.0 Microsoft.SystemCenter.InstanceGroup.Library
    ...
    False  7.0.3683.0 ServiceManager.OpsMgrConnector.Configuration
    
    

    Now we have a simple script and default formatting.  But we're not done with management packs - if I want to see the contents of a management pack, I can do that via a process called "exporting".  Exporting a management pack lets me create an XML file of the management pack which I can then inspect the various elements of the management pack so I can see what it does (and how it does it).  An object exists for just the purpose of exporting management packs - Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter and using the WriteManagementPack method, I can easily create the XML files.

    This is perfect for a foreach pipeline, so for each management pack that I retrieve, I'll create an XML file of the management pack contents.

    PS> Get-ManagementPack | %{
    >> $xmlWriter = new-object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter C:\temp 
    >> } { 
    >> $xmlWriter.WriteManagementPack($_) 
    >> }
    

    The first script block in the foreach command creates me an xmlWriter which will used for all the management pack objects that are passed from Get-ManagementPack.   The ManagementPackXmlWriter object has two constructors.  The constructor that I'm using takes a string which points to a directory which will contain the exported xml files.  When invoked, the method returns a string which is the fullname of the exported XML file, so when I execute it, I see the following (ellipses used to save space):

    C:\temp\ServiceManager.ServiceMaps.Library.xml
    C:\temp\Microsoft.SystemCenter.InstanceGroup.Library.xml
    ...
    C:\temp\ServiceManager.OpsMgrConnector.Configuration.xml
    

    I can easily incorporate this into a script as well:

    PS> Get-Content Export-ManagementPack.ps1
    param ( $targetDirectory = $( throw "Need a target directory"), [switch]$verbose )
    begin {
      if ( $verbose )
      {
        $verbosePreference = "Continue"
      }
      $xmlWriter = new-object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter $targetDirectory
    }
    process {
      if ( $_ -is "Microsoft.EnterpriseManagement.Configuration.ManagementPack" )
      {
        $path = $xmlWriter.WriteManagementPack($_)
        if ( $verbose )
        {
          Write-Verbose "Exporting: $path"
        }
      }
      else
      {
        Write-Error "$_ is not a management pack"
      }
    }
    

    I've also added support for -verbose so I can see what's being exported if I want, along with just a little checking to be sure that I've actually got a management pack.

    Now I can run the following:

    PS> Get-ManagementPack | Export-ManagementPack C:\temp
    or
    PS> Get-ManagementPack | Export-ManagementPack C:\temp -verbose

     MPBlog2

    and export all my management packs in one simple step.   Next time I'll discuss importing management packs.

    2/5/2009

    Service Manager and PowerShell

    Hi There!

    This is the first in a series of blogs that I plan to do about Service Manager and PowerShell.  These will be posted in two places - the Service Manager Blog and my spaces page, you can go to the Service Manager blog to see more posts on Service Manager, and if you found this on the Service Manager blog, you can take a look on my Spaces (http://jtruher.spaces.live.com/) page which has a number of different posts on PowerShell and my other hobbies.  My semi-regular readers know that I was one of the co-creators of the PowerShell scripting language and I was one of the first members of the PowerShell team and had a whole lot to do with the first release of PowerShell, but you may not know that I am now part of the System Center Service Manager team where my area of responsibility is the SDK.  This is a great new challenge for me, with an entire new area of system management to learn, so it’s very exciting.

    One of great things about the Service Manager product is that it is based on managed code, and because of that, PowerShell can take direct advantage of all that great code.  As these articles are really about using PowerShell with Service Manager, I’m not going to spend too much time in a tutorial on PowerShell in general as there are a number of great sources for information on PowerShell (to which I’ll provide links at the end of this article). 

    At the moment there are no cmdlets for Service Manager. We will likely be providing some later in our release cycle, but for these first articles, I’ll be interacting directly against the managed interfaces that are part of the SDK.  This does make the scripting a bit more advanced from a PowerShell perspective, but it shouldn’t be too much to start – We’ll essentially be approaching this similarly to how you would approach programming against Service Manager in C#.  Jakub Oleksy, a dev lead on the project has a great blog here: http://blogs.mdn.com/jakuboleksy/ he has a ton of great articles about programming against Service Manager (and Operations Manager).

    Here we go!

    To get access to the Service Manager object space, we’ll need to load the appropriate assemblies.  When you install Service Manager, the libraries we need are put on the file-system and by default can be found in the directory C:\Program Files\Microsoft System Center\Service Manager 2010\SDK Binaries.  The following lines should allow you to load the Service Manage core assembly.

    PS> $SMDIR = "C:\Program Files\Microsoft System Center\Service Manager 2010"
    PS> $COREDLL = "${SMDIR}/SDK Binaries/Microsoft.EnterpriseManagement.Core.dll"
    PS> [reflection.assembly]::LoadFile($COREDLL)

    GAC    Version        Location
    ---    -------        --------
    True   v2.0.50727     C:\Windows\assembly\GAC_MSIL\Microsoft.EnterpriseManagement.Core\1.0.2700.0__9396306c2be7fcc4\...

    As we progress with these articles, we’ll do the above every time, so to save typing, you should put those three lines in your $profile so it just happens automatically. 

    Now that we have the assembly loaded we can start interacting with Service Manager.  The way that you communicate with Service Manager is via the Microsoft.EnterpriseManagement.EnterpriseManagementGroup object and there are a number of ways to create this object. We’ll create it via the simplest method, just by providing the server name where the Service Manager management server has been installed.   Since I’m running these examples on the same system where the Service Manager management server is installed, I’ll use the server name “localhost” (by the way, if you run the Service Manager Console on a different the system than where the Service Manager management server is installed, you’ll need to provide the name of the management server here).

    How did I know that Microsoft.EnterpriseManagement.EnterpriseManagementGroup was the place to start?  I looked that up in the SDK documentation. 

    PS> $MGroup = New-Object Microsoft.EnterpriseManagement.EnterpriseManagementGroup localhost

    Excellent!  If there was a problem in creating the object, I would have gotten an error message, now I can inspect the $MGroup object that I just created.

                                                                                  
    
    PS> $MGroup 
    
    ConnectorFramework : Microsoft.EnterpriseManagement.ConnectorFramework.ConnectorFrameworkConfigurationManagement
    EntityTypes        : Microsoft.EnterpriseManagement.EntityTypeManagement
    Instances          : Microsoft.EnterpriseManagement.InstancesManagement
    Knowledge          : Microsoft.EnterpriseManagement.KnowledgeManagement
    LanguagePacks      : Microsoft.EnterpriseManagement.LanguagePacksManagement
    ManagementPacks    : Microsoft.EnterpriseManagement.ManagementPackManagement
    Monitoring         : Microsoft.EnterpriseManagement.MonitoringConfigurationManagement
    Overrides          : Microsoft.EnterpriseManagement.OverridesManagement
    Presentation       : Microsoft.EnterpriseManagement.PresentationManagement
    Reporting          : Microsoft.EnterpriseManagement.ReportingConfigurationManagement
    Resources          : Microsoft.EnterpriseManagement.ResourceManagement
    Security           : Microsoft.EnterpriseManagement.SecurityConfigurationManagement
    Subscription       : Microsoft.EnterpriseManagement.Subscriptions.SubscriptionManagement
    Tasks              : Microsoft.EnterpriseManagement.TaskConfigurationManagement
    Templates          : Microsoft.EnterpriseManagement.TemplatesManagement
    TypeCache          : Microsoft.EnterpriseManagement.TypeCacheManagement
    TypeDefinitions    : Microsoft.EnterpriseManagement.TypeDefinitionsManagement
    DataWarehouse      : Microsoft.EnterpriseManagement.DataWarehouseManagement
    Notifications      : Microsoft.EnterpriseManagement.NotificationManagement
    Name               : Woodgrove
    Id                 : 86a8c51c-4680-3ba0-4843-1c38f462b0b6
    IsConnected        : True
    CacheMode          : Configuration
    AutoRefreshCache   : True
    SkuForLicense      : Select
    SkuForProduct      : SCOM
    TimeOfExpiration   : 12/31/9999 11:59:59 PM
    ProductId          :
    ConnectionSettings : Microsoft.EnterpriseManagement.EnterpriseManagementConnectionSettings
    Version            : 1.0.3105.0 
    
                                                                       
    

    You can see a number of different interesting bits of information here:  First you can see that the IsConnected property is true, which means that I have a connection to the Service Manager SDK service.  If this is ever “false”, it means that I won’t be able to get any data from Service Manager because I’m no longer connected to the service that talks to Service Manager.

    it’s ok if that happens, the object has a Reconnect() method which I can use to open my connection again

    This object also has version information, and a whole lot of other stuff.  You'll notice a number of more complicated properties (the top 2/3rds of the output).  These properties provide us the access points to the various parts of the product such as the sort of classes that are installed (EntityTypes), the actual data stored in Service Manager (Instances), and a whole bunch of other things that we’ll explore in later articles.  Take a look at the SDK documentation if you want more information.

    The management pack is the way that you determine what sort of data you will track in Service Manager.  Management packs define a whole bunch of information about the various entities such as Computers, Users, Network Cards, as well as actions that can be used with these various entities – so knowing what management packs are installed is really important.  If you start the Analyst Console you’ll see something that looks like this if you select the Administration tab:

    AdminConsole002

    We can approximate this view of the ManagementPack table with PowerShell with the following command line:

    PS> $MGroup.ManagementPacks.GetManagementPacks()|format-Table Sealed,Name,Description -au 
    
    Sealed Name                                                            Description
    ------ ----                                                            -----------
      True Microsoft.SystemCenter.InstanceGroup.Library                    Microsoft System Center Instance Group Librar...
      True Microsoft.Windows.Peripheral.Library                            Microsoft Windows Peripheral Library: This ma...
     False Woodgrove.AutomatedActivity.AddUserToUserGroup
      True ServiceManager.ActivityManagement.Library
      True System.Software.Library                                         System Software Library: This Management Pack...
      True Microsoft.SystemCenter.Deployment.Library
      True ServiceManager.KnowledgeManagement.Library
      True Microsoft.EnterpriseManagement.ServiceManager.UI.Administration ServiceManager Administration ManagementPack
     False ServiceManager.LinkingFramework.Configuration
      True ServiceManager.LinkingFramework.Library
      True System.Snmp.Library                                             SNMP Library: Contains SNMP definitions.
      True ServiceManager.Core.Library
      True Microsoft.EnterpriseManagement.ServiceManager.UI.Console        Service Manager Console ManagementPack
      True System.ApplicationLog.Library                                   System Application Log Library: This Manageme...
      True Microsoft.EnterpriseManagement.ServiceManager.UI.Authoring      Service Manager Authoring ManagementPack
      True Microsoft.SystemCenter.Library                                  Microsoft System Center Library: This Managem...
     False Microsoft.EnterpriseManagement.ServiceManager.Default
      True Microsoft.SystemCenter.WorkItemGroup.Library                    Microsoft System Center Instance Group Librar...
      True System.Library                                                  System Library: Root for all Management Packs...
      True Microsoft.Windows.Library                                       Microsoft Windows Core Library: This Manageme...
      True Microsoft.SystemCenter.ConfigurationManager                     Microsoft System Center Configuration Manager...
      True Microsoft.EnterpriseManagement.ServiceManager.Connector.Sms
     False ServiceManager.ChangeManagement.Configuration
      True System.Health.Library                                           System Health Library: This Management Pack c...
      True Microsoft.SystemCenter.WorkflowFoundation.Library               Microsoft System Center Workflow Foundation L...
      True Microsoft.SystemCenter.Report.Library
      True ServiceManager.Datawarehouse.Library
      True Microsoft.EnterpriseManagement.ServiceManager.Connector.AD
      True ServiceManager.ConfigurationManagement.Library
     False ServiceManager.ActivityManagement.Configuration
      True System.Notifications.Library                                    System Notification Library: This Management ...
      True ServiceManager.IncidentManagement.Library
      True Microsoft.SystemCenter.Subscriptions
      True ServiceManager.ChangeManagement.Library
      True System.Performance.Library                                      System Performance Library: This Management P...
     False ServiceManager.IncidentManagement.Configuration 
    
    

    Viola!  Now we can get access to the management packs that are installed without having to use the GUI!  At this point, all of the usual PowerShell utility can be used.  For example, if I wanted to see only those management packs which are unsealed I can use where-object (aliased to “?”) to look for where the Sealed property is false (not true).

    PS> $MGroup.ManagementPacks.GetManagementPacks()|?{ ! $_.Sealed}|format-Table Sealed,Name,Description -au 
    
    Sealed Name                                                            Description
    ------ ----                                                            -----------
    False Woodgrove.AutomatedActivity.AddUserToUserGroup
    False ServiceManager.LinkingFramework.Configuration
    False Microsoft.EnterpriseManagement.ServiceManager.Default
    False ServiceManager.ChangeManagement.Configuration
    False ServiceManager.ActivityManagement.Configuration
    False ServiceManager.IncidentManagement.Configuration 

    We’ve barely scratched the surface with the way that PowerShell can interact with Service Manager.  In the next article we’ll take a closer look at the contents of a management pack.

    Here are those PowerShell links I promised:

    http://blogs.msdn.com/powershell/
    http://thepowershellguy.com/
    http://www.leeholmes.com/blog/default.aspx
    http://mow001.blogspot.com/

    1/21/2009

    Sed fugit interea fugit irreparabile tempus

    I can’t believe it’s been more than a year since I’ve written here – man - am I a slacker!

    Lot’s of changes for me since my last post – I turned 50, my son got married got married to a wonderful young woman, I returned from Microsoft Research back to a product team in Microsoft (System Center Service Manager team).  Some things haven’t changed - I’m still using PowerShell all the time, and still conducting the Microsoft Orchestra (http://www.msorchestra.org/).

    I’m planning a number of postings about PowerShell and Service Manager which you’ll see here.  I’ll also be saying more about some of the musical things I’ve been doing.  I’ve been doing some research on the Vaughan Williams Pastoral Symphony (#3) which the Microsoft Orchestra will be performing in April.   I’ve also got other PowerShell posts I want to make – I’ve got a PowerShell media player that I’ve been using which I will be sharing as well as some other scripts and cmdlets that might entertain.

    Mind you, this is not a “new years’ resolution” – I detest the things.  I don’t even resolve to not have resolutions! 

    One thing I did was to eliminate the capability to comment on my posts – Sorry about that - I got very tired of culling the spam.  Spaces doesn’t seem to have a way to approve comments – if somebody knows how to do that, please send me an email.

    10/15/2007

    And now for a little Rossini

    More music performed by the 1978 PCC Chamber Singers.  This time it's a little Rossini:
    Accompanied again by Twyla Meyer and the soprano (heard around 4:20) is the spectacular Rebecca Sherburn.  This group was really quite impressive, I know that a number of the singers in this group have careers as singers.   Twyla is in great demand throughout LA and Bill Hatcher has had stellar career in choral music.  I feel extremely fortunate that I had the opportunity to learn from these amazing musicians.
    10/11/2007

    more from PCC - 1978

    Audite Nova by Lassus this time - it's a very silly piece. 
     
    I remember really liking this piece and having a great time performing it.  Listening to it now, it seems so heavy, i suppose it should, with 6 on a part.  I've performed and listened to so much one-on-a-part that it's clear how much I've changed in the past 30 years.  Thank goodness for that - it would be horrible to think that I hadn't changed.
     
    I've got a few more of these and also some recordings of the Pasadena Chorale from around 79 to 83.  I'll get them posted after I finish the Chamber Singers stuff
     
     
     
     
     
    8/31/2007

    I love Twitter

    I love the idea of twitter - I think it's the logical intersection between blogging and reduced attention spans.  I started to play about with it and I was frustrated with the way I needed to create entries.  Having a separate app to create twitter updates seemed wrong to me.  I don't want to change focus from my current shell to create an update and best case, I want to update my status automatically so I don't think about it, it just happens. 
     
    I know that there are some command line tools to do this, but I I want a native solution for PowerShell, so I created a Send-TwitterStatus cmdlet to allow me to send updates directly from my shell.  Not only that, but I can use this cmdlet in other scripts to automatically push my activity to Twitter as well.  I created media player script and it seems like a natural thing to do is to push my playlist to Twitter so my friends can see what I'm listening to (if they want).  I have a line now in the script when I append an album to my playlist is a line that calls my cmdlet and pushes my update:
     
       Send-TwitterStatus "Adding to office playlist: $album" $credential
     
    • $album is the name of the album
    • $credential is a global variable that contains a PSCredential which is used by the cmdlet to authenticate with the Twitter service.  
    I grabbed the Yedda.Twitter code to do the actual Twitter interaction and the rest is just the code to stitch the cmdlet together.  I also convert the XML results into a custom object so I can eventually create the appropriate formatting. 
     
    Anyway - here you go. 
     
    and the cmdlet code
    My post http://jtruher.spaces.live.com/blog/cns!7143DA6E51A2628D!119.entry will show how to compile and install a snapin and use.
     
    Next steps are to create the various twitter getters and create the format file so I can get activity directly from the shell.
     
     
     
     

    More media - PCC Chamber Singers, 1978

    It seems like the time just flies - it's been a couple months since I updated here, and I would like to post more often. 
     
    In any event, I have another audio file to post!  This is the third of the Trois Chanson Britonnes Soir d'été.  My memory of the recording circumstances is a little fuzzy, since it was nearly 30 years ago, but I seem to remember that we did this recording around the end of the calendar year, and we hadn't yet learned the second chanson, so I only have this one (the last) and the first.
     
    Conducted by Bill Hatcher and accompanied by the magnificent Twyla Meyer .
     
    Here it is: Soir d'été
     
    Some of the other pieces from this recording session are:
    • Audite Nova
    • I Heard a Voice
    • Deck the Halls
    There are more, and I'll post them as I can
     
    7/15/2007

    Tracing the script stack

    It's not uncommon that after I've created a fairly complicated script after a while of using it, something bad happens that I wasn't expecting. And I would really like to know how I got to this state, so a stacktrace of my script would be really, really nice. Sadly, this isn't something that is a default behavior or PowerShell, but fortunately, this sort of thing is actually possible to do with just a little bit of script!

    Most of the real world examples are more complicated than we really need to use to illuminate the problem.  So, I've created a simple example that is useful for discussion.

    Take the following script:
    # test-stacktrace1.ps1
    param ( $startVal )
    function func1
    {
        param ( $startVal )
        1/$startVal--
        func2 $startVal
    }
    function func2
    {
        param ( $startVal )
        1/$startVal--
        func3 $startVal
    }
    function func3
    {
        param ( $startVal )
        1/$startVal--
    }
    func1 $startVal

    When I run this script, depending on the value of my argument, the script will run or fail:

    PS# c:\temp\test-stacktrace 5
    0.2
    0.25
    0.333333333333333
    PS# c:\temp\test-stacktrace1 2
    0.5
    1

    Attempted to divide by zero.
    At c:\temp\test-stacktrace1.ps1:18 char:7
    +     1/$ <<<< startVal--

    The message is ok - it tells me that I had a problem on the appropriate line in the script, but I don't know how I got there by looking at the message.  What I would really like to see is both the error and the way I got there.  Here are some examples of what I want to see:

    This example is the normal behavior
    PS# c:\temp\test-stacktrace2 3
    0.333333333333333
    0.5
    1

    This example will show what happens when an error occurs deep in the stack:
    PS# c:\temp\test-stacktrace2 2
    0.5
    1
    func3 : Attempted to divide by zero.
    At c:\temp\test-stacktrace2.ps1:25 char:10
    +     func3  <<<< $startVal
    At c:\temp\test-stacktrace2.ps1:31 char:42+     trap { write-error $_; get-stacktrace  <<<< }
    At c:\temp\test-stacktrace2.ps1:25 char:10+     func3  <<<< $startVal
    At c:\temp\test-stacktrace2.ps1:17 char:10+     func2  <<<< $startVal
    At c:\temp\test-stacktrace2.ps1:36 char:6+ func1  <<<< $startVal
    At line:1 char:25+ c:\temp\test-stacktrace2  <<<< 2

    Notice that I see the functions that I called on the way to this error - This way I can see the path of woe that generated the error - which means I have a much better chance of actually fixing the problem. 

    Here's another example of what happens when an error occurs sooner in the stack, notice that we only see func1 and func2 calls:
    PS# c:\temp\test-stacktrace2 1
    1
    func2 : Attempted to divide by zero.
    At c:\temp\test-stacktrace2.ps1:17 char:10
    +     func2  <<<< $startVal
    At c:\temp\test-stacktrace2.ps1:23 char:42+     trap { write-error $_; get-stacktrace  <<<< }
    At c:\temp\test-stacktrace2.ps1:17 char:10+     func2  <<<< $startVal
    At c:\temp\test-stacktrace2.ps1:36 char:6+ func1  <<<< $startVal
    At line:1 char:25+ c:\temp\test-stacktrace2  <<<< 1

    And finally what happens when an error occurs right away, notice that we only see func1 in the stack:
    PS# c:\temp\test-stacktrace2 0
    func1 : Attempted to divide by zero.
    At c:\temp\test-stacktrace2.ps1:36 char:6
    + func1  <<<< $startVal
    At c:\temp\test-stacktrace2.ps1:15 char:42+     trap { write-error $_; get-stacktrace  <<<< }
    At c:\temp\test-stacktrace2.ps1:36 char:6+ func1  <<<< $startVal
    At line:1 char:25+ c:\temp\test-stacktrace2  <<<< 0

    So, here's the code - and a brief discussion follows:

    param ( $startVal )
    function get-stacktrace
    {
        trap { continue }
        1..100 | %{
            $inv = &{ gv -sc $_ myinvocation } 2>$null
            if ($inv) { write-host -for blue $inv.value.positionmessage.replace("`n","") }
            }
        exit
    }
    function func1
    {
        param ( $startVal )
        trap { write-error $_; get-stacktrace }
        1/$startVal--
        func2 $startVal
    }
    function func2
    {
        param ( $startVal )
        trap { write-error $_; get-stacktrace }
        1/$startVal--
        func3 $startVal
    }
    function func3
    {
        param ( $startVal )
        trap { write-error $_; get-stacktrace }
        1/$startVal
    }
    # Main
    func1 $startVal

    Notice the addition of the "get-stacktrace" function:

    function get-stacktrace
    {
        trap { continue }
        1..100 | %{
            $inv = &{ gv -sc $_ myinvocation } 2>$null
            if ($inv) { write-host -for blue $inv.value.positionmessage.replace("`n","") }
            }
        exit
    }
     

    This function takes advantage of the fact that the PowerShell scoping rules allow you to inspect variables in different scopes from your current scope.  This isn't available via syntax, but it is available via the get-variable cmdlet (aliased to gv).  So our little get-stacktrace function just drills down our scopes looking for the myInvocation property which has the information about what line on the script we're on.  There are some other things that are going on.  The trap statement assures me that if I get any terminating errors that I ignore them and I've placed the call of gv in a script block - this allows me to really throw away any messages that get-variable may throw that aren't terminating errors.   Lastly, I want to be sure that my message is on a single line, so I replace the carriage returns with empty strings.

    Notice also that each function now has a trap statement.  I think that this is generally good practice regardless, but these do two things.  First they write the error and then call the get-stacktrace function.  We need to write the error because the get-stacktrace function is going to exit, so if we didn't have this write-error we wouldn't actually see what the error was, just the stack trace which isn't enough info.

    So, if you've got a complicated script and you would really like to discover how you got where you are, I hope this little bit of script will help!

    Jim

     

    6/22/2007

    Blasts from the past

    Wow - I haven't written for a long time - however, I think I'll be doing more because while I was cleaning up my home office, I found a number of cassette tapes to which I've been hanging for an even longer time. 
    I've been incredibly fortunate to perform with a number of very excellent ensembles over the years.  I found recordings dating back to when I I first started singing at Pasadena City College in 1976-78 - I'm going to be sharing them here in my blog so the folks can get access to them as I can convert them from tape to digital.
     
    So, I'll try to reduce the noise, and do some of the usual mastering activities.  We'll see how successful I am at that, it will be a learning experience for me.  With the first one, I haven't done any noise reduction of mastering, it's just right off the tape, so you'll hear some tape hiss.  Also, as I recall, the recording was done by some pretty crummy omnidirectional mics (and I seem to remember that it was recorded on reel-to-reel, so this cassette might be a copy of that).  This is a recording from 1978 of the first movement of Trois Chanson by Henk Badings performed by the Pasadena City College Chamber Singers, conducted by Bill Hatcher who inspired me greatly and accompanied by the amazing Twyla Meyer.
     
    Here you go: La Nuit En Mer
     
     
    jim 
    2/19/2007

    PowerShell Extended Types (includes a TYPES.XSD)

    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.
     
     
    12/2/2006

    Getting more out of help

    As we were developing PowerShell, we knew that we wanted to provide a capability for searching through the help. Unfortunately, we don't always get to everything, and this is one of those things that we couldn't get to.  However, I still sometimes need to search through the help, so I created this little function to do the search.  It searches through the conceptual topics for the string for which I'm looking and with the switch parameter "-all", I search through the descriptions of the cmdlet help as well.

    Like most things in PowerShell, it turns out to be pretty simple.  Here's how I would look for help that has the word "process" in it, I use -all to retrieve cmdlet help in addition to the conceptural (about*) topics. 

    PS> search-help process -all
     
    HelpTopic                  Reference
    ---------                  ---------
    about_arithmetic_operators The command then processes the parameters as it w...
    about_array                .NET Framework. For example, the objects that Get...
    about_assignment_operators current process. For example, the following comma...
    about_automatic_variables  Contains objects for which an error occurred whil...
    about_commonparameters     the command during processing. This variable is
    about_environment_variable system path, the number of processors used by the...
    about_filter               processes that begin with the letters a through m...
    about_foreach              displays any processes whose working-set (memory ...
    about_function             filters. The primary difference between the two i...
    about_location             As a result, all commands are processed from this...
    about_logical_operator     When PowerShell processes this statement, it eval...
    about_object               receives the objects from the first command, proc...
    about_operator             fact that PowerShell processes operators in a ver...
    about_parsing              When processing a command, the PowerShell parser ...
    about_pipeline             down the pipeline to the second command. The seco...
    about_provider             Alias                ShouldProcess               ...
    about_quoting_rules        is passed to the command for processing. Consider...
    about_shell_variable       example, the $PID variable stores the process ID ...
    about_signing              export process.
    about_switch               The keyword "break" indicates that no more proces...
    about_wildcard             in order to return specific results. The process ...
    default                    get-help get-process   : Displays help about the ...
    ForEach-Object             Performs an operation against each of a set of in...
    Where-Object               Creates a filter that controls which objects will...
    Get-Process                Gets the processes that are running on the local ...
    Stop-Process               Stops one or more running processes.
    Set-Content                Writes or replaces the content in an item with ne...
    Export-Csv                 Creates a comma-separated values (CSV) file that ...
    Sort-Object                Sorts objects by property values.
    Get-TraceSource            Gets the Windows PowerShell components that are i...

    Here’s the search-help script - as you can see, it's just a few lines.   The interesting bit is the use of Select-Object.  With Select-Object, I create custom objects from both about* help and cmdlet help.  Select-Object allows me to specify which properties I want, but it also allows me to "rename" the property.  This way I can take two disparate bits of information (the bits I get back from Select-String and the bits I get out of the help object) and create objects that will act consistently regardless of their origin.

    function Search-Help
    {
    param ( $pattern, [switch]$all ) $path = “${pshome}\about*.txt” Select-String -list –pattern ${pattern} –path ${path}| select-object @{ n="HelpTopic"; e = {$_.filename -replace ".help.txt"}}, @{n="Reference";e={$_.line.trim()}} if ( $all ) { Get-Help * | where-object { $_.description -match ${pattern}}| select-object @{n="HelpTopic";e={$_.Name}}, @{n="Reference"; e={$_.synopsis}}
    } }
    I hope this is useful for you!
    jim
    10/16/2006

    Dijkstra

     

    I recently received the following question:

    Im  trying to solve the following problem in Powershell:

     I know the name of Active Directory Site A and the name of Active Directory Site B, Site A doesn’t necessarily have a site link to Site B (could go A -> C -> B). What is the total cost of the least cost path between these 2 sites? What is the total cost between any 2 sites? (effectively I’m trying to replace this : http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ad/ad/dsquerysitesbycost.asp api with a Powershell version.)

    To do so, I think I need to implement some kind of Dijkstra engine  to solve this...

     Yay for Wikipedia (http://en.wikipedia.org/wiki/Dijkstra)!

     Bruce and I thought it might be fun to put this together – we took it straight from the C code.  Given the following graph

    Where the weight of the path is written on the line.  Here’s the output from running the script:

    PS> get-dijkstra
    The distance between nodes 0 and 0 is 0
    The distance between nodes 0 and 1 is 2
    The distance between nodes 0 and 2 is 1
    The distance between nodes 0 and 3 is 5
    The distance between nodes 0 and 4 is 4
    The distance between nodes 0 and 5 is 6
    The distance between nodes 0 and 6 is 6
    The distance between nodes 0 and 7 is 8

    Showing that the shortest path is 8!  Unfortunately, this script doesn’t show the nodes in the path.  I’ll leave that as an exercise for the reader.  The real point of the exercise was to show the amount of effort that was needed to convert the C into PowerShell!  We basically had to create the structures in the C code, which we did via HashTables (and used functions to create the hashtables).

    Here’s the script (again, a straight forward port of the C code from the Wiki site, it's basic but it works!):

    ### get-dijkstra.ps1
    $INFINITY = [int]::MaxValue-1
    function edge ([int] $weight, [int] $dest)
    {
        @{weight = $weight; dest = $dest}
    }

    function Vertex( [object[]] $connections)
    {
        @{
           connections = $connections; # An array of weighted arcs
           numconnect = $connections.length
           distance = $INFINITY
           isDead = $false
        }
    }

    function Dijkstra([object[]] $graph, [int] $source)
    {
        [int] $nodecount = $graph.length
        $graph[$source].distance = 0
        for($i = 0; $i -lt $nodecount; $i++) {
            $min = $INFINITY+1
            # find the unchecked node closest to the source
            for($j = 0; $j -lt $nodecount; $j++) {
                if(! $graph[$j].isDead -and $graph[$j].distance -lt $min) {
                    $next = $j
                    $min = $graph[$j].distance
                }
            }
            # check all paths from node 
            for($j = 0; $j -lt $graph[$next].numconnect; $j++)
            {
                if($graph[$graph[$next].connections[$j].dest].distance -gt
                   $graph[$next].distance + $graph[$next].connections[$j].weight)
                {
                    $graph[$graph[$next].connections[$j].dest].distance =
                        $graph[$next].distance + $graph[$next].connections[$j].weight
                }
            }
            $graph[$next].isDead = $true
        }
        for([int] $i = 0; $i -lt $nodecount; $i++) {
            "The distance between nodes {0} and {1} is {2}" -f
                $source, $i, $graph[$i].distance
        }
    }
    $graph = @()
    ###
    ### Here’s where we define the different vertexi
    ### each vertex is a collection of edges
    ### an edge has a weight and a destination
    $graph += vertex (edge -w 1 -d 2),(edge -w 2 -d 1) # 0
    $graph += vertex (edge -w 3 -d 3),(edge -w 4 -d 4) # 1
    $graph += vertex (edge -w 3 -d 4),(edge -w 5 -d 6),(edge -w 10 -d 7) # 2
    $graph += vertex (edge -w 3 -d 5) # 3
    $graph += vertex (edge -w 2 -d 5),(edge -w 3 -d 6) # 4
    $graph += vertex (edge -w 2 -d 6),(edge -w 2 -d 7) # 5
    $graph += vertex (edge -w 2 -d 7) # 6
    $graph += vertex (edge -w 0 -d 0) # 7
    Dijkstra $graph 0

    ### END SCRIPT

     Woo Hoo!

     

     

     

    9/8/2006

    Getting Disk Usage Information

    Some of you might know that I've spent a lot of time on UNIX systems.  One of the scripts that I used a bunch was /etc/dfspace.  If you don't know what dfspace is, it's a simple wrapper for df that provides disk usage info in a more human readable format than the output of df.  Since I really miss having that on Windows, I built it in powershell using the Get-WMIObject cmdlet.  Here's how it looks when you run it:

    PS> dfspace
    name                  Size (MB) free (MB) percent
    ----                  --------- --------- -------
    C:                   152,499.84 76,827.33   50.38

    By default, it only shows me the local hard drives.  By using the "-all" switch parameter I can get all the drives.

    PS> dfspace -all
    name                  Size (MB) free (MB) percent
    ----                  --------- --------- -------
    A:                         0.00      0.00     NaN
    C:                   152,499.84 76,826.80   50.38
    D:                         0.00      0.00     NaN
    Z:                    78,528.64  7,342.27    9.35

    It can also get me the disk usage on another system via the -computer parameter (but you have to enable WMI remote access)

    PS> dfspace -computer jimtrup2
    name                 Size (MB) free (MB) percent
    ----                 --------- --------- -------
    C:                   57,231.53 11,540.28   20.16

    It gives me what I like, and it's actually a pretty simple script, where most of the script is creating the appropriate formatting

    # Get-DiskUsage.ps1 (aliased to dfspace)
    # Use Get-WMIObject to collect disk free info
    # Can be used with remote systems
    #
    param ( [string]$computer = "." , [switch]$all)
    # Formatting
    $size = @{ l = "Size (MB)"; e = { $_.size/1mb};      f = "{0:N}"}
    $free = @{ l = "free (MB)"; e = { $_.freespace/1mb}; f = "{0:N}"}
    $perc = @{ l = "percent"; e = { 100.0 * ([double]$_.freespace/[double]$_.size)}; f="{0:f}" }
    $name = @{ e = "name"; f = "{0,-20}" }
    $fields = $name,$size,$free,$perc

    # in case the user wants to see more than just local drives
    $filter = "DriveType = '3'"
    if ( $all ) { $filter = "" }

    # go do the work by getting the information from the appropriate
    # computer,
    and send it to format-table with the appropriate
    # fields and formatting info
    get-wmiobject -class win32_logicaldisk -filter $filter -comp $computer |
        format-table $fields -auto

    I suppose that I could handle division by zero better, but seeing NaN doesn't bother me.  If you don't like it, I'll leave that as an exercise for the reader :^)

    8/30/2006

    Background "jobs" and PowerShell

    One of the things that I'm used to on my unix systems is the ability to run applications in the background and that functionality is not available in PowerShell.  However, our RunSpace architecture can be used to create a pseudo-job environment.  A "simple" script, a few functions and a custom formatting file leaves me with a pretty good experience.  I'm not able to move jobs between foreground and background, but I never did that very much anyway.
     
    So, the experience looks like this:
     
    PS> new-job { get-date; start-sleep 5; get-date }
    Job 0 Started
    #<insert 5 second wait here>
    PS>
    Job 0 Completed
    PS> jobs 0
    JobId    Status       Command                      Results
    -----    ------       -------                      -------
    5        Completed    get-date; start-sleep 5; ... 8/30/2006 4:47:32 PM
    PS> (jobs 0).results
    Wednesday, August 30, 2006 4:47:32 PM
    Wednesday, August 30, 2006 4:47:37 PM
     
    This gives me what I need ok - I can run the command and get the results.  Let's do something a little more interesting (at least it is to me).  I want to find all the files on my system that are larger than 100mb and haven't been written for more than 3months.  This would probably take some time, since I'm going to be going over the entire filesystem.  Here's what I got:
     
    PS> new-job { ls -rec c:\ | ?{ $_.length -gt 100mb -and $_.lastwritetime -lt [datetime]::now.adddays(-90) } }
    Job 1 Started
    I would like to see what's going on - so let's check:
     
    PS> jobs
    JobId    Status       Command                      Results
    -----    ------       -------                      -------
    0        Completed    get-date; start-sleep 5; ... 8/30/2006 4:56:25 PM
    1        Running      ls -rec c:\ | ?{ $_.lengt...
     
    after using the shell for a while, I get the following message (when I get a prompt):
     
    Job 1 Completed
     
    yea!  now I can check my results:
     
    PS> jobs
    JobId    Status       Command                      Results
    -----    ------       -------                      -------
    0        Completed    get-date; start-sleep 5; ... 8/30/2006 4:56:25 PM
    1        Completed    ls -rec c:\ | ?{ $_.lengt... MSO060408_0001.wmv
    PS> (jobs 1).results

        Directory: Microsoft.PowerShell.Core\FileSystem::C:\Documents and Settings\jimtru\Desktop\mso

    Mode                LastWriteTime     Length Name
    ----                -------------     ------ ----
    -a---          4/9/2006   2:15 PM  368465546 MSO060408_0001.wmv

        Directory: Microsoft.PowerShell.Core\FileSystem::C:\Documents and Settings\jimtru\Desktop\mso\audio\051204 msoWav

    Mode                LastWriteTime     Length Name
    ----                -------------     ------ ----
    -a---         12/5/2005  10:53 AM  121192364 2005-12-05 10_26.wav
    -a---         12/5/2005  10:54 AM  213057644 2005-12-05 10_36.wav
     
    So, I've got a bunch of huge wav files that I haven't touched for a while (they're recordings of the Microsoft Orchestra for those of you that are curious).   I can even stop the jobs if I want:
     
    PS> new-job { ls -rec c:\ | ?{ $_.length -gt 100mb -and $_.lastwritetime -lt [da
    tetime]::now.adddays(-90) } }
    Job 2 Started
    PS> (jobs 2)
    JobId    Status       Command                      Results
    -----    ------       -------                      -------
    2        Running      ls -rec c:\ | ?{ $_.lengt...

    PS> (jobs 2).stop()
    Job 2 Stopped
    PS> jobs
    JobId    Status       Command                      Results
    -----    ------       -------                      -------
    0        Completed    get-date; start-sleep 5; ... 8/30/2006 4:56:25 PM
    1        Completed    ls -rec c:\ | ?{ $_.lengt... MSO060408_0001.wmv
    2        Stopped      ls -rec c:\ | ?{ $_.lengt... MSO060408_0001.wmv

     
    The new-job script has all the magic in it, so here it is:
     
    # New-Job.ps1
    # This script creates an object that can be used to invoke a
    # scriptblock asynchronously.
    #
    param ( [scriptblock]$scriptToRun )
    ##
    ## Object Created - Custom Object
    ##
    ## METHODS
    ##
    ## void InvokeAsync([string] $script, [array] $input = @()) 
    ## Invokes a script asynchronously.
    ## void Stop([bool] $async = $false) # Stop the pipeline.
    ##
    ## PROPERTIES
    ##
    ## [System.Management.Automation.Runspaces.LocalPipeline] LastPipeline     
    ##      The last pipeline that executed.
    ## [bool] IsRunning                                                        
    ##      Whether the last pipeline is still running.
    ## [System.Management.Automation.Runspaces.PipelineState] LastPipelineState
    ##      The state of the last pipeline to be created.
    ## [array] Results                                                         
    ##      The output of the last pipeline that was run.
    ## [array] LastError                                                       
    ##      The errors produced by the last pipeline run.
    ## [object] LastException                                                  
    ##      If the pipeline failed, the exception that caused it to fail.
    ##
    ## Private Fields
    ##
    ## [array] _lastOutput    # The objects output from the last pipeline run.
    ## [array] _lastError     # The errors output from the last pipeline run.
    #region Message
    $MultiplePipeline = "A pipeline was already running.   " +
        "Cannot invoke two pipelines concurrently."
    ##
    ## MAIN
    ##
    # First check to be sure that there is a Job array
    if ( test-path variable:jobs )
    {
        if ( $global:jobs -isnot [array] )
        {
            throw '$jobs exists and is not an array'
        }
    }
    else
    {
        $global:jobs = @()
    }

    # Create a runspace and open it
    $config = [Management.Automation.Runspaces.RunspaceConfiguration]::Create()
    $runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($config)
    $runspace.Open()
    # Create the object - we'll use this as the collector for the entire job.
    $object = new-object System.Management.Automation.PsObject
    # Add the object as a note on the runspace
    $object | add-member Noteproperty Runspace $runspace
    # Add a field for storing the last pipeline that was run.
    $object | add-member Noteproperty LastPipeline $null
    # Add an invoke method to the object that takes a script to invoke asynchronously.
    $invokeAsyncBody = {
      if ($args.Count -lt 1)
      {
        throw 'Usage: $obj.InvokeAsync([string] $script, [Optional][params][array]$inputObjects)'
      }
      & {
        [string]$script, [array] $inputArray =  @($args[0])
        $PipelineRunning = [System.Management.Automation.Runspaces.PipelineState]::Running
        # Check that there isn't a currently executing pipeline.
        # Only one pipeline may run at a time.
        if ($this.LastPipeline -eq $null -or
            $this.LastPipeline.PipelineStateInfo.State -ne $PipelineRunning )
        {
          $this.LastPipeline = $this.Runspace.CreatePipeline($script)
          # if there's input, write it into the input pipeline.
          if ($inputArray.Count -gt 0)
          {
            $this.LastPipeline.Input.Write($inputArray, $true)
          }
          $this.LastPipeline.Input.Close()
          # Set the Results and LastError to null.
          $this.Results   = $null
          $this.LastError = $null
          # GO!
          $this.LastPipeline.InvokeAsync()
        }
        else
        {
          # A pipeline was running.  Report an error.
          throw
        }
      } $args
    }
    $object | add-member ScriptMethod InvokeAsync $invokeAsyncBody
    # Adds a getter script property that lets you determine whether the runspace is still running.
    $get_isRunning = {
      $PipelineRunning = [System.Management.Automation.Runspaces.PipelineState]::Running 
      return -not ($this.LastPipeline -eq $null -or
                   $this.LastPipeline.PipelineStateInfo.State -ne $PipelineRunning  )
    }
    $object | add-member ScriptProperty IsRunning $get_isRunning
    # Add a getter for finding out the state of the last pipeline.
    $get_PipelineState = { return $this.LastPipeline.PipelineStateInfo.State }
    $object | add-member ScriptProperty LastPipelineState $get_PipelineState
    # Add a getter script property that lets you get the last output.
    $get_lastOutput = {
      if ($this._lastOutput -eq $null -and -not $this.IsRunning)
      {
        $this._lastOutput = @($this.LastPipeline.Output.ReadToEnd())
      }
      return $this._lastOutput
    }
    $set_lastOutput = { $this._lastOutput = $_ }
    $object | add-member ScriptProperty Results $get_lastOutput $set_lastOutput
    $object | add-member Noteproperty _lastOutput $null
    # Add a getter for finding out the last exception thrown if any.
    $get_lastException = {
      if ($this.LastPipelineState -eq "Failed" -and -not $this.IsRunning)
      {
        return $this.LastPipeline.PipelineStateInfo.Reason
      }
    }
    $object | add-member ScriptProperty LastException $get_lastException
    # Add a getter script property that lets you get the last errors.
    $get_lastError = {
      if ($this._lastError -eq $null -and -not $this.IsRunning)
      {
        $this._lastError = @($this.LastPipeline.Error.ReadToEnd())
      }
      return $this._lastError
    }
    $set_lastError = { $this._lastError = $args[0] }
    $object | add-member ScriptProperty LastError $get_lastError $set_lastError
    $object | add-member Noteproperty _lastError $null
    # Add a script method for stopping the execution of the pipeline.
    $stopScript = {
      if ($args.Count -gt 1)
      {
        throw 'Too many arguments.  Usage: $object.Stop([optional] [bool] $async'
      }
      if ($args.Count -eq 1 -and [bool] $args[0])
      {
        $this.LastPipeline.StopAsync()
      }
      else
      {
        $this.LastPipeline.Stop()
      }
    }
    $object | add-member ScriptMethod Stop $stopScript
    # finally, attach the script to run to the object
    $object | add-member Noteproperty Command $scriptToRun 
    # Ensure that the object has a "type" for which we can build a
    # formatting file.
    $object.Psobject.typenames[0] = "PowerShellJobObject"
    $object.InvokeAsync($scriptToRun)
    #$object
    $object | add-member NoteProperty JobId $jobs.count
    "Job " + $jobs.count + " Started"
    # Since we add this job to the we need to be sure that
    # we can remove jobs.  The clear-job function will allow for that
    $global:jobs += $object

     
    I've created 2 functions to help me with getting the job information (and an alias to make it a bit more UNIX like) add these to your profile.
     
    # get my job
    function get-job
    ([int[]]$range = 0..($jobs.count-1))
    {
        $jobs[$range]
    }
    # make a UNIX like alias
    alias jobs get-job
    function clear-job
    {
        # remove all the variables that hold my job info
        rm variable:jobs
        rm variable:jobshash
        # call the garbage collector, just because I can
        [system.gc]::Collect()
    }
     
    Then to be sure that I know when a job is finished, I added this to my prompt function:
     
        ### Job info code - only useful for new-job script
        ### paranoia - make a jobhash hashtable so I can track what jobs are done
        if ( $jobshash -isnot [hashtable] ) { $global:jobshash = @{} }
        $global:jobs | where { $_.lastpipelinestate -ne "Running" } | foreach-object {
            if ( ! $global:jobshash[([string]$_.jobid)] )
            {         
                $global:jobshash[([string]$_.jobid)] = 1
                if ( $_ ) { write-host Job $_.jobid   $_.lastpipelinestate }
            }
        }
        ### End job info code
     
    Finally, I created a format table view so I can see the output the way I want (when I get a PowerShellJobObject - see the new-job script).  My profile runs
        update-formatdata c:\powershell\format\job.format.ps1xml
    to ensure that the format file get loaded.  Here's the content of the format file
     
    PS> get-content c:\powershell\format\job.format.ps1xml
    <?xml version="1.0" encoding="utf-8" ?>
    <Configuration>
        <ViewDefinitions>
            <View>
                <Name>PowerShellJobObject</Name>
                <ViewSelectedBy>
                    <TypeName>PowerShellJobObject</TypeName>
                </ViewSelectedBy>
                <TableControl>
                    <TableHeaders>
                        <TableColumnHeader>
                            <Label>JobId</Label>
                            <Width>8</Width>
                        </TableColumnHeader>
                        <TableColumnHeader>
                            <Label>Status</Label>
                            <Width>12</Width>
                        </TableColumnHeader>
                        <TableColumnHeader>
                            <Label>Command</Label>
                        </TableColumnHeader>
                        <TableColumnHeader>
                            <Label>Results</Label>
                        </TableColumnHeader>
                    </TableHeaders>
                    <TableRowEntries>
                        <TableRowEntry>
                            <TableColumnItems>
                                <TableColumnItem>
                                    <PropertyName>JobId</PropertyName>
                                </TableColumnItem>
                                <TableColumnItem>
                                    <PropertyName>LastPipelineState</PropertyName>
                                </TableColumnItem>
                                <TableColumnItem>
                                    <PropertyName>Command</PropertyName>
                                </TableColumnItem>
                                <TableColumnItem>
                                    <ScriptBlock>
                                        if ( $_.results -is [array])
                                        {
                                            $_.results[0]
                                        }
                                        else
                                        {
                                            $_.results
                                        }
                                    </ScriptBlock>
                                </TableColumnItem>
                            </TableColumnItems>
                        </TableRowEntry>
                    </TableRowEntries>
                </TableControl>
            </View>
        </ViewDefinitions>
    </Configuration>
     
    The extra functions and addition to my prompt and the formatting file are all extra's and not really necessary to the operation of the runspace.  It just makes it easier for me to deal with.
     
    There you have it - you can run background "jobs" too!  Right now, I use a simple array to hold all my jobs - in the future I'll use an array list, so that way I can remove the old jobs if I want (rather than just blowing away the entire array as clear-job does.
     
    jim
     
     
    5/14/2006

    PowerShell and file version information

    I often want to get the version information about the files on my system.  Version information is provided as part of the System.Diagnostics.Process object, but I often want the version information about applications that aren't running.  This script allows me to get that information.  I've written the script so it handles both piped input and command line arguments.  It uses the begin/process/end features of the scripting language, so I can get it to behave almost like a compiled cmdlet. 
     
    Here's what it looks like when I use it:
    PS> ls c:\windows\*.exe | get-fileversion
    
    ProductVersion   FileVersion      FileName
    --------------   -----------      --------
    1.6.0.2          1.6.0.2          C:\windows\Alcmtr.exe
    1.1.0.27         1.1.0.27         C:\windows\alcwzrd.exe
    6.00.2900.2180   6.00.2900.218... C:\windows\explorer.exe
    5.2.3790.2453    5.2.3790.2453... C:\windows\hh.exe
    5, 51            5, 51, 138, 0    C:\windows\IsUninst.exe
    1.1.0.8          1.1.0.8          C:\windows\MicCal.exe
    5.1.2600.2180    5.1.2600.2180... C:\windows\NOTEPAD.EXE
    5.1.2600.2180    5.1.2600.2180... C:\windows\regedit.exe
    2.0.1.7          2.0.1.7          C:\windows\RTHDCPL.exe
    1.0.1.51         1.0.1.51         C:\windows\RTLCPL.exe
    2, 5, 0, 5       2, 5, 0, 5       C:\windows\RtlUpd.exe
    1, 0, 0, 21      1, 0, 0, 21      C:\windows\SoundMan.exe
    5.1.2600.0       5.1.2600.0 (x... C:\windows\TASKMAN.EXE
    1,7,0,0          1,7,0,0          C:\windows\twunk_16.exe
    1,7,1,0          1,7,1,0          C:\windows\twunk_32.exe
    3.10.425         3.10.425         C:\windows\winhelp.exe
    5.1.2600.2180    5.1.2600.2180... C:\windows\winhlp32.exe
    
    or
    PS> get-fileversion C:\monad\rc1\System.Management.Automation.dll
    
    ProductVersion   FileVersion      FileName
    --------------   -----------      --------
    1.0.9567.1       1.0.9567.1       C:\monad\rc1\System.Management.Automation.dll
    
    Here's the script - it's pretty straight forward. Since I don't know whether I'm going to have piped input or not, I use the begin script block to declare a couple of functions that will be used by either of the process or end blocks.
    The real work is done in the function GetVersionInfo where I simply call the GetVersionInfo static method on the System.Diagnostics.FileVersionInfo type. Note that most of the code is error correction and ensuring that I get a proper path when I call the GetVersionInfo method.
    param ( [string[]]$paths )
    begin {
        # I want to do some stuff with relative paths.   
        # create a variable that I can use later
        $P = [string](get-location)
    
        # the workhorse of the script
        function GetVersionInfo
        {
            param ( [string]$path )
            # resolve the path, we're going to need a fully qualified path to hand
            # to the method, so go get it.  I may not have that depending on how
            # was called
            $rpath = resolve-path $path 2>$null
            # the thing we hand to the method is the path string, so we'll tuck that away
            $path = $rpath.path
            # check to be sure that we're in the filesystem
            if ( $rpath.provider.name -ne "FileSystem" ) 
            { 
                "$path is not in the filesystem"
                return $null
            }
            # now that I've determined that I'm in the filesystem, go get the fileversion
            $o = [system.diagnostics.fileversioninfo]::GetVersionInfo($path)
            # this little dance adds a new property to the versioninfo object
            # I add the relative path to the versioninfo so I can inspect that in the output object
            # the way that add-member works is to not emit the object, so I need to 
            # use the -passthrough parameter
            $o|add-member noteproperty RelativePath ($path.replace($P,".")) -pass
        }
        # whoops! something bad happened
        function ShowFileError
        {
            param ( [string]$path )
            if ( test-path $path -pathtype container )
            {
                "$path is a container"
            }
            else
            {
                "$path not found"
            }
        }
    }
    
    # data could have been piped - check $_ to see if this cmdlet had data
    # piped to it
    process {
        if ( $_ )
        {
            # make sure that I'm not trying to get a versioninfo of a directory
            if ( test-path $_ -pathtype leaf )
            {
                GetVersionInfo $_
            }
            else
            {
                ShowFileError $_
            }
        }
    }
    
    # we could have also gotten arguments on the command line
    end {
        if ( $paths )
        {
            # by calling resolve path first, I can deal with wildcards on the command line
            foreach ( $path in resolve-path $paths )
            {
                # make sure it's a file, not a directory
                if ( test-path $path -pathtype leaf )
                {
                    GetVersionInfo $path
                }
                else
                {
                    ShowFileError $path
                }
            }
        }
    }
    
     
     
     
     
    5/5/2006

    PowerShell: Hashtables and Objects

    I use hash tables all the time - they're dead useful and when we were building PowerShell I used them as surrogate objects.  However, they don't quite behave like an object - display is problematic and it's tougher to add methods, etc, so, how do I get what I want?  There are a couple of ways to add functionality to the PowerShell environment. Scripts and functions are the usual way to add to a shell environment, but one of the things I really like about PowerShell is the dynamic nature of the snapin model. This allows me to add cmdlets and providers to my environment - and since one of the things I use all the time is a hashtable as an object, I figured that I would create a hashtable converterto do the trick.   The converter itself could be done as a script (and is left as an excercise for the reader)
    using System;
    using System.ComponentModel;
    using System.Collections.ObjectModel;
    using System.Collections.Generic;
    using System.Management.Automation;
    using System.Management.Automation.Runspaces;
    
    namespace System.Management.Automation.Demos
    {
        // This class defines the properties of a snapin
        [RunInstaller(true)]
        public class ConversionCmdlets : PSSnapIn
        {
            /// <summary>Creates an instance of DemoSnapin class.</summary>
            public ConversionCmdlets() : base()
            {
            }
            ///<summary>The snapin name which is used for registration</summary>
            public override string Name
            { get
                { return "ConversionCmdlets";
                }
            }
            /// <summary>Gets vendor of the snapin.</summary>
            public override string Vendor
            { get
                { return "Vendor String - JWT";
                }
            }
            /// <summary>Gets description of the snapin. </summary>
            public override string Description
            { get
                { return "This snapin contains demo conversion cmdlets";
                }
            }
            private string[] formats = null;
    
            public override string[] Formats
            { get 
                { return formats;
                }
            }
        }
    }
    
    
    That's the code that allows me to create a snapin, now I need the actual cmdlet code
    using System;
    using System.Management.Automation;
    using System.Collections;
    using System.Collections.ObjectModel;
    
    namespace JWT.Conversion.Cmdlets
    {
        [Cmdlet("Convert","HashToObject")]
        public class ConvertHashToObjectCommand : PSCmdlet
        {
            private Hashtable hash;
            [Parameter(Mandatory=true,Position=0,ValueFromPipeline=true)]
            public Hashtable Hash
            {
                get { return hash; }
                set { hash = value; }
            }
            private String[] name = { "ConvertedHashTable" };
            [Parameter(Position=1)]
            public String[] Names
            {
                get { return name; }
                set { name = value; }
            }
            protected override void ProcessRecord()
            {
                PSObject obj = new PSObject();
                // Go through each key and create a property and
                // assign the value to the new property.
                // In the case that the value is a script block, create
                // a ScriptMethod instead.
                foreach ( string key in Hash.Keys )
                {
                    if ( Hash[key].GetType().Name == "ScriptBlock" )
                        obj.Methods.Add(
                            new PSScriptMethod(key,(ScriptBlock)Hash[key])
                            );
                    else
                        obj.Properties.Add(
                            new PSNoteProperty(key, Hash[key])
                            );
                }
                // update the type names for the new object
                Collection<string> tnames = new Collection<string>();
                obj.TypeNames.Clear();
                foreach ( string tname in Names )
                {
                    obj.TypeNames.Add(tname);
                }
                WriteObject(obj);
            }
        }
    }
    
    
    Then I built a script to build and install the snapin
    # make.ps1
    # build my ConversionCmdlets PowerShell
    $out          = "Conversion-Cmdlets.dll"
    $src          = "convert-HashToObject.cs","convert-snapin.cs"
    $ref          = "${pshome}/system.management.automation.dll"
    $frameworkDir = "${env:windir}/Microsoft.Net/Framework/v2.0.50727"
    $csc          = "${frameworkDir}/csc.exe"
    $installutil  = "${frameworkDir}/installutil.exe"
    &$csc /target:library /out:$out /r:$ref $src
    if ( ! (test-path $out) )
    {
        Write-Host -red "$out could not be built"
        exit
    }
    &$installutil $out > install.out 2> install.err
    $snapin = get-pssnapin -reg | where {$_.modulename -match $out}
    if ( ! $snapin )
    {
        Write-Host -red "$out could not be installed"
        exit
    }
    else
    {
        "Snapin created and installed"
    }
    
    
    Here's what it looks like when you use it!
    PS> add-pssnapin ConversionCmdlets
    PS> get-command convert-hashtoobject
    
    CommandType     Name                            Definition
    -----------     ----                            ----------
    Cmdlet          Convert-HashToObject            Convert-HashToObject [-Hash]...
    
    PS> $a = @{A=1;B=2;C={$this.A};D={$this.A=$args[0]}}|convert-hashtoobject
    PS> $a|format-table -auto
    A B
    - -
    1 2
    PS> $a.C()
    1
    PS> $a.D(10)
    PS> $a|format-table -auto
     A B
     - -
    10 2
    PS> $A.C()
    10
    PS>
    

    All this rushing about

     
    This was originally started life as a blog I made for the Microsoft Orchestra
     
    I've been thinking about tempo and what it is that makes people rush - here are my thoughts on the topic.
    Many times, we rush when it is hard. I think that the reason is that you get nervous about the hard bits and then you think ok - this goes really, really fast, if I play it as fast as I can, I should be able to keep up. There are a few problems with this:
    1. It may be that you can play it faster than you think
    2. Rushing can be contagious; you're playing with your standmate, and they might go a little fast, and you want to catch up
    3. Not enough time is given to the spaces between the notes
    4. (and most importantly) you lose the connection with the rest of the whole group in concentrating on your little corner of the world. So, in the microcosm of your standmates, you're together, but rushing ahead of the entire orchestra.

    Then, of course, there's the rushing where it's too easy, I think that this is associated with music that has a lot of silence in the individual parts. A good example of this is probably the Donizetti "Daughter of the Regiment". Lots of the parts have little "oom-pah" or "dit-dit" parts - I think these have a tendency to rush because the overall tempo is not internalized (not by tapping the foot but getting the right subdivision of the overall tempo).  It's incredibly important that when you play these accompaniment parts that you tune into the player that's got the melody - be sure that you are supporting the melody rather than trying to fill in the silences - the melody takes care of filling in the silence, the accompaning parts just need to accompany.

     

    What can we do about this?  I think that the most important thing to do is to take the time to listen to the sounds around you.  Playing together does not mean that you play your part, the guy next to you plays his part and the whole thing comes together like magic.  Playing together means listening to everything, especially the players that don't happen to play the part you play.  Also, remember that music making is a social process, not a technical exercise - concentrate on making music, not the technique(1) - anyway, that's what I try to do. 

     

    jim

     

    1
    I'm not suggesting that technique is unimportant, it's just not the most important.  I've attended too many performances that were technically excellent but left me unmoved because of the lack of musicality.  I have more to say on that topic, but later.

    5/3/2006

    Music, Hark!

    While I make my living working with and planning software, I consider myself a musician rather than a "software guy". My background is all music, my education was in music, then as a teacher and performer (both singer and instrumentalist). I've been very lucky to have the opportunity to perform with some really great musicians.
     
    My latest musical endeavor finds me the conductor of the Microsoft Orchestra - when the post opened up a few years ago, I volunteered.  I never thought that I would be an orchestra conductor; I studied choral music and was a choir director and singer for 20 years and have conducted orchestras, but mostly in combination with choirs (like the Brahms Requiem, Dona Nobis Pacem, etc), but never thought I would regularly conduct an orchestra.
     
    I really love it - it's a chance for me to learn, to teach, and to make music that I never thought I would get the chance to do.  It is so much fun to build rapport with the players and to bring music out that they didn't think they had in them.  It's also been a great learning experience for me, and has made me a better musician by trying to keep ahead of these folks; making sure that I have something to say that inspires, cajoles or amuses these wonderful people into playing better.
     
    The MS orchestra is made up of MS employees, family and friends and has been around since the early 90s.  We're getting better and better with each concert and it is so much fun.  (if you're interested, you can see more at http://www.msorchestra.org/)
     
    j