Archive for the ‘PowerShell’ Category

Retrieving projection data with PowerShell   Leave a comment

There are a number of cmdlets in the SMLets project (http://smlets.codeplex.com) which retrieve data from Service Manager. To reduce the amount of data in getting simple instances from Service Manager, Get-SCSMObject provides a filter parameter which lets you provide a simple property/operator/value triad to reduce the amount of data that is retrieved from the CMDB. This is really helps performance because the filtering happens on the server. We can see the difference pretty easily:

PS# measure-command { get-scsmobject -Class $incidentclass | ?{ $_.Title -like "Ipsum*" } }
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 1
Milliseconds      : 460
Ticks             : 14609004
TotalDays         : 1.69085694444444E-05
TotalHours        : 0.000405805666666667
TotalMinutes      : 0.02434834
TotalSeconds      : 1.4609004
TotalMilliseconds : 1460.9004

PS# measure-command { get-scsmobject -Class $incidentclass -filter { Title -like "Ipsum*" } }
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 134
Ticks             : 1341265
TotalDays         : 1.5523900462963E-06
TotalHours        : 3.72573611111111E-05
TotalMinutes      : 0.00223544166666667
TotalSeconds      : 0.1341265
TotalMilliseconds : 134.1265

In total time, this might not be impressive, but from a percentage perspective, it is.  In this case, filtering on the server cuts the operation time by 90%, which is pretty substantial. However, simple instances are small potatoes in comparison to what we can save if we implement a filter on projection retrieval.

There is also a filter parameter on Get-SCSMObjectProjection, but it only allows you to filter against properties of the seed object, there’s no way to query the relationships in this filter. However, since much of the interesting information about a projection is the relationship data, so a simple filter isn’t as much help as it is for simple instances. Because I wanted to be sure that there was at least some way that you could query against the relationships, I included a criteria parameter which takes an ObjectProjectionCriteria, but left the creation of this criteria as “an exercise for the reader”. I’ve had a few requests for this, so I thought it would be good to build a way to easily create this criteria based on the projection. Behaviorally, I wanted to provide a similar experience to that of the filter’s property/operator/value trio, so the filter that I created for projections has the same basic shape, but the property part of the trio has a different look.

The property part of the filter is broken into 2 pieces, the relationship (as expressed in the alias) and the property on that relationship. If we look at the System.WorkItem.Incident.View.ProjectionType we see the following structure:

PS# get-scsmtypeprojection incident.view.projection

ProjectionType: System.WorkItem.Incident.View.ProjectionType
ProjectionSeed: System.WorkItem.Incident
Components:
   Alias           TargetType        TargetEndPoint
   -----           ----------        ---------------
   AffectedUser    System.User       RequestedWorkItem
   AssignedUser    System.User       AssignedWorkItem

This projection has two components “AffectedUser” and “AssignedUser”. With this script, I can construct a filter like this:

AssignedUser.DisplayName = 'Joe User'

which will check the DisplayName property of the System.User object which is the end point of the relationship. I also wanted to support multiple queries, so I added support for -AND which allows you to create multiple property/operator/value statements.

The savings in retrieving projection data is substantial. Here’s a query which retrieves incidents which have a priority of 2 and have a related work-item which has a DisplayName which is equal to MA37. Filtering in the query is 200 times faster.

PS# measure-command { 
>> .\new-scsmProjectionCriteria.ps1 $ipfull.__Base -filter {
>> priority = 2 -and RelatedWorkItems.DisplayName -eq "MA37" } -result
>> }
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 625
Ticks             : 6258242
TotalDays         : 7.24333564814815E-06
TotalHours        : 0.000173840055555556
TotalMinutes      : 0.0104304033333333
TotalSeconds      : 0.6258242
TotalMilliseconds : 625.8242
PS# measure-command { Get-SCSMObjectProjection -ProjectionObject $ipfull |
>>  ?{ $_.priority -eq "2" -and ($_.RelatesToWorkItem_ |?{$_.DisplayNAme -eq "MA37" })} }
Days              : 0
Hours             : 0
Minutes           : 2
Seconds           : 5
Milliseconds      : 888
Ticks             : 1258883302
TotalDays         : 0.0014570408587963
TotalHours        : 0.0349689806111111
TotalMinutes      : 2.09813883666667
TotalSeconds      : 125.8883302

It should be no surprise that it’s much faster to return only the data that you want, because there’s so much less information that needs to be passed back from the CMDB. Also, during the first pipeline, the CPU utilization was quite high (ranging between 60-80%) where utilization was split between PowerShell (PowerShell does a lot of adaptation of the returned projection), the SQL server and the DataAccess Service.

Here are some of the filters that I tested against the System.WorkItem.Incident.ProjectionType projection:

'title -like "Ipsum*" -and CreatedByUser.DisplayName -like "D*"'
'title -like "Ipsum*" -and RelatedWorkItems.DisplayName -like "M*"'
'title -like "Ipsum*" -and RelatedWorkItems.DisplayName -eq "MA37"'
'RelatedWorkItems.DisplayName -eq "MA37"'
'priority = 2 -and RelatedWorkItems.DisplayName -eq "MA37"'
'priority -gt 1 -and RelatedWorkItems.DisplayName -eq "MA37"'
'priority -lt 3 -and RelatedWorkItems.DisplayName -eq "MA37"'
'priority -le 3 -and RelatedWorkItems.DisplayName -eq "MA37"'
'priority <= 3 -and RelatedWorkItems.DisplayName -eq "MA37"'
'priority -ne 3 -and RelatedWorkItems.DisplayName -eq "MA37"'
'priority -ne 3 -and RelatedWorkItems.DisplayName -notlike "MA3*"'
'priority -eq 3 -and Status -eq "Closed" -and RelatedWorkItems.DisplayName -notlike "MA3*"'
'priority -eq 3 -and AssignedUser.displayname -like "D*" -and Status -eq "Closed" 
-and RelatedWorkItems.DisplayName -notlike "MA3*"'

Each time, the difference in time between client side and server side filtering is huge!

Here’s the script:

 1: ###
 2: ### filters have the form of:
 3: ### [alias.]propertyname <operator> value
 4: ### if there's no ".", then the assumption is that the 
 5: ### criteria is looking for the property of a seed
 6: ### if there is a ".", then it's a property of a relationship
 7: ### the relationship is described by the alias
 8: ### 
 9: [CmdletBinding()]
 10: param (
 11:     [parameter(Mandatory=$true,Position=0)]
 12:     $projection,
 13:     [parameter(Mandatory=$true,Position=1)][string]$filter,
 14:     [parameter()][switch]$results
 15:     )
 16: 
 17: # determine whether the property is an enumeration type
 18: function Test-IsEnum
 19: {
 20:     param ( $property )
 21:     if ( $property.SystemType.Name -eq "Enum" ) { return $true }
 22:     return $false
 23: }
 24: # Get the string which provides a reference in our criteria to the
 25: # management pack which contains the element we're searching against
 26: function Get-ReferenceString
 27: {
 28:     param (
 29:         $ManagementPack,
 30:         [ref]$alias
 31:         )
 32:     $alias.Value = $ManagementPack.Name.Replace(".","")
 33:     $refstring = '<Reference Id="{0}" PublicKeyToken="{1}" Version="{2}" Alias="{3}" />'
 34:     $refstring -f $ManagementPack.Name,$ManagementPack.KeyToken,$ManagementPack.Version,$Alias.Value
 35: }
 36: 
 37: # retrieve the property from the class
 38: # we want to do this because we may get a property from the user which has the case
 39: # incorrect, this allows us to match property names case insensitively
 40: function Get-ClassProperty
 41: {
 42:     param ( $Class, $propertyName )
 43:     $property = ($Class.GetProperties("Recursive")|?{$_.name -eq $propertyName})
 44:     if ( ! $property ) { throw ("no such property '$propertyName' in " + $Class.Name) }
 45:     return $property
 46: }
 47: # in the case that the value that we got is applicable to an enum, look up the
 48: # guid that is needed for the comparison and substitute that guid value
 49: # replace the '*' with '%' which is needed by the criteria
 50: function Get-ProperValue
 51: {
 52:     param ( $Property, $value )
 53:     if ( Test-IsEnum $property )
 54:     {
 55:         $value = get-scsmenumeration $property.type|?{$_.displayname -eq $value}|%{$_.id}
 56:     }
 57:     return $value -replace "\*","%"
 58: }
 59: # create the XML expression which describes the criteria
 60: function Get-Expression
 61: {
 62:     param (
 63:         $TypeProjection,
 64:         [Hashtable]$POV,
 65:         [ref]$neededReferences
 66:         )
 67:     $Property = $POV.Property
 68:     $Operator = $POV.Operator
 69:     $Value    = $POV.Value
 70:     $ExpressionXML = @'
 71:         <Expression>
 72:          <SimpleExpression>
 73:           <ValueExpressionLeft><Property>{0}</Property></ValueExpressionLeft>
 74:           <Operator>{1}</Operator>
 75:           <ValueExpressionRight><Value>{2}</Value></ValueExpressionRight>
 76:          </SimpleExpression>
 77:         </Expression>
 78: '@
 79:     [ref]$MPAlias = $null
 80: 
 81:     # a proper property reference in a projection criteria looks like this:
 82:     # <Property>
 83:     # $Context/Path[Relationship='CustomSystem_WorkItem_Library!System.WorkItemAffectedUser' 
 84:     # TypeConstraint='CustomSystem_Library!System.User']/
 85:     # Property[Type='CustomSystem_Library!System.User']/FirstName$
 86:     # </Property>
 87:     # we need to collect all the bits and do the same
 88:     # if the property has a "." in it, we will assume that this is the property
 89:     # of a relationship. Therefore, get the relationship and construct the 
 90:     # appropriate string for the property access
 91:     #
 92:     # This routine only supports a single ".", anything more complicated and this will 
 93:     # fail
 94:     if ( $property -match "\." )
 95:     {
 96:         $alias,$prop = $property -split "\."
 97:         $component = $projection.TypeProjection[$alias]
 98:         $references = @()
 99:         $NS = "Microsoft.EnterpriseManagement"
 100:         $ConfigNS = "${NS}.Configuration"
 101:         $ComponentType = "${ConfigNS}.ManagementPackTypeProjectionComponent"
 102:         if ( $component -isnot $ComponentType)
 103:         {
 104:             throw "'$alias' not found on projection"
 105:         }
 106:         $target = $component.TargetType
 107:         $references += Get-ReferenceString $target.GetManagementPack() $MPAlias
 108:         $TargetFQN = "{0}!{1}" -f $MPAlias.Value,$Target.Name
 109:         $property = Get-ClassProperty $target $prop
 110:         $value = Get-ProperValue $property $value
 111:         
 112:         $relationship = $component.Relationship
 113:         $references += Get-ReferenceString $relationship.GetManagementPack() $MPAlias
 114:         $relationshipFQN = "{0}!{1}" -f $MPAlias.Value,$relationship.name
 115:  
 116:         $PropString = '$Context/Path[Relationship=''{0}'' TypeConstraint=''{1}'']/Property[Type=''{1}'']/{2}$'
 117:         $XPATHSTR = $PropString -f $RelationshipFQN,$TargetFQN,$property.Name
 118: 
 119:         $Expression = $ExpressionXML -f $XPATHSTR,$QueryOperator,$value
 120:         $neededReferences.Value = $references | sort-object -uniq
 121:         return $Expression
 122:     }
 123:     else
 124:     {
 125:         $SeedClass = get-scsmclass -id $projection.TargetType.Id
 126:         $property = Get-ClassProperty $SeedClass $property
 127:         $value = Get-ProperValue $Property $value
 128: 
 129:         $SeedMP = $SeedClass.GetManagementPack()
 130:         $reference = Get-ReferenceString $SeedMP $MPAlias
 131:         $typeFQN = "{0}!{1}" -f $MPAlias.Value,$SeedClass.Name
 132: 
 133:         $PropString = '$Context/Property[Type=''{0}'']/{1}$' -f $typeFQN,$Property.Name
 134:         $Expression = $ExpressionXML -f $PropString,$Operator,$Value
 135:         $neededReferences.Value = $reference
 136:         return $Expression
 137:     }
 138: }
 139:  
 140: trap { $error[0];exit }
 141: if ( $projection -is "psobject"  )
 142: {
 143:     $projection = $projection.__base
 144: }
 145: $ProjectionType = "Microsoft.EnterpriseManagement.Configuration.ManagementPackTypeProjection"
 146: if ( $projection -isnot $ProjectionType )
 147: {
 148:     throw "$projection is not a projection and cannot be converted"
 149: }
 150: # right now, only AND is supported,
 151: # eventually, OR will be supported
 152: $GroupOperators = " -and "
 153: # and the conversion to what is needed in the criteria
 154: $OperatorConverter = @{
 155:     "="     = "Equal"
 156:     "-eq"   = "Equal"
 157:     "!="    = "NotEqual"
 158:     "-ne"   = "NotEqual"
 159:     "-like" = "Like"
 160:     "-notlike" = "NotLike"
 161:     "<"     = "Less"
 162:     "-lt"   = "Less"
 163:     ">"     = "Greater"
 164:     "-gt"   = "Greater"
 165:     ">="    = "GreaterEqual"
 166:     "-ge"   = "GreaterEqual"
 167:     "<="    = "LessEqual"
 168:     "-le"   = "LessEqual"
 169:     }
 170: # a list of allowed operators, generated from the converter
 171: $Operators = ($OperatorConverter.Keys |%{" $_ "}) -join "|"
 172: # split the filter up based on the GroupOperator
 173: $filters = @($filter.ToString() -split $GroupOperators | %{$_.trim()})
 174: # some variables that we will need 
 175: [ref]$neededrefs = $null
 176: $Expressions = @()
 177: $ReferenceStrings = @()
 178: # loop through the filters and construct some XML which we will use
 179: foreach ( $filterString in $filters)
 180: {
 181:     # check to be sure we have a valid filter which includes
 182:     # a property, an operator and a value
 183:     $foundMatch = $filterString.toString() -match "(?<p>.*)(?<o>$operators)(?<v>.*)"
 184:     if ( ! $foundMatch )
 185:     {
 186:         throw "bad filter $filter"
 187:     }
 188:     # manipulate the found elements into a PropertyOperatorValue hashtable
 189:     # which we will use to encapsulate the filter
 190:     $Property = $matches['p'].Trim()
 191:     $Operator = $matches['o'].Trim()
 192:     $QueryOperator = $OperatorConverter[$Operator]
 193:     if ( ! $Operator ) { throw "Bad Operator '$Operator'" }
 194:     $Value    = $matches['v'].Trim() -replace '"' -replace "'"
 195:     $POV = @{
 196:         Property = $Property
 197:         Operator = $QueryOperator
 198:         Value    = $Value
 199:         }
 200:     # now go get the expression that we need for the criteria
 201:     # pass the projection, the PropertyOperatorValue hashtable
 202:     # and the needed references (as a reference variable }
 203:     $expressions += get-expression $projection $POV $neededrefs
 204:     $neededRefs.Value | %{ $ReferenceStrings += $_ }
 205: }
 206: # now that we have looped through the filters, construct the XML
 207: # which we need to call the ObjectProjectCriteria constructor
 208: # start off with the start of the criteria XML
 209: $CriteriaString = '<Criteria xmlns="http://Microsoft.EnterpriseManagement.Core.Criteria/">'
 210: # now add the references that are needed in the criteria
 211: $ReferenceStrings | sort -uniq | %{ $CriteriaString += "`n $_" }
 212: # if we actually had multiple filters, add the 
 213: # <And>
 214: if ( $Filters.count -gt 1 )
 215: {
 216:     $CriteriaString += "`n<Expression>"
 217:     $CriteriaString += "`n <And>"
 218: }
 219: # now, for each of the expressions, add it to the criteria string
 220: foreach($ex in $expressions ) { $CriteriaString += "`n $ex" }
 221: # and in the case where we have filters that have and "-and", add the
 222: # </And> to finish correctly
 223: if ( $Filters.Count -gt 1)
 224: {
 225:     $CriteriaString += "`n </And>"
 226:     $CriteriaString += "`n</Expression>"
 227: }
 228: $CriteriaString += "`n</Criteria>"
 229: write-verbose $CriteriaString
 230: # at this stage, the criteria XML should be complete, so we can create the
 231: # criteria object
 232: $CTYPE = "Microsoft.EnterpriseManagement.Common.ObjectProjectionCriteria"
 233:  
 234: $criteriaobject = new-object $CTYPE $CriteriaString,$projection,$projection.ManagementGroup
 235: if ( $criteriaObject -and $Results )
 236: {
 237:     get-scsmobjectprojection -criteria $criteriaobject
 238: }
 239: elseif ( $criteriaObject )
 240: {
 241:     $criteriaObject
 242: }
 243: 

I added a Result parameter to the script which calls Get-SCSMObjectProjection, just for convenience. Eventually, I’ll add this logic into the filter parameter for the cmdlet, so it will be part of the cmdlet rather than this addition.

Posted May 13, 2011 by jtruher3 in PowerShell, ServiceManager

A tool for table formatting   3 comments

As I was working on the SMLets CodePlex project, I created a bunch of new cmdlets and I wanted to make sure that the output looked good and was useful. In order to do that I needed to sling a bunch of XML and create my own formatting file. If you’ve ever looked at the format files in PowerShell (to see what’s shipped, type get-childitem “$pshome/*.format.ps1xml”) it can be pretty daunting, so most folks (me included) start with something that works an then modify it. After I did that a few times, I reckoned that I had better build a tool to make life easier. My requirements were pretty straight-forward; build a tool that emitted the XML that I needed to put in the formatting file. Second, commit no unnatural acts with regard to parameters to create the XML. What I really wanted, is to use Format-Table to get the output exactly how I wanted and then just substitute my tool for Format-Table. Thus, New-TableFormat is born.

The following is how I use it, for this example, I’m just using a process object in this example, but the concept applies to any object. First I get the output looking exactly like I want, so in this case, I show the processID, the handles, the name and then the handles in KB. In order to do that last bit, I need to use a script block. After that, it’s simply replace format-table with new-tableformat and hey! presto! I’ve got my XML!

PS> get-process lsass|format-table id,handles,name,@{L="HandlesKB";E={$_.Handles/1KB};A="Right";F="{0:N2}"} -au

 Id Handles Name  HandlesKB
 -- ------- ----  ---------
552    1274 lsass      1.24

PS> get-process lsass|new-tableformat id,handles,name,@{L="HandlesKB";E={$_.Handles/1KB};A="Right";F="{0:N2}"} -au
   <View>
    <Name>ProcessTable</Name>
    <ViewSelectedBy>
     <TypeName>System.Diagnostics.Process</TypeName>
    </ViewSelectedBy>
    <TableControl>
     <AutoSize />
     <TableHeaders>
      <TableColumnHeader><Label>id</Label></TableColumnHeader>
      <TableColumnHeader><Label>handles</Label></TableColumnHeader>
      <TableColumnHeader><Label>name</Label></TableColumnHeader>
      <TableColumnHeader>
        <Label>HandlesKB</Label>
        <Alignment>Right</Alignment>
      </TableColumnHeader>
     </TableHeaders>
     <TableRowEntries>
      <TableRowEntry>
       <TableColumnItems>
        <TableColumnItem><PropertyName>id</PropertyName></TableColumnItem>
        <TableColumnItem><PropertyName>handles</PropertyName></TableColumnItem>
        <TableColumnItem><PropertyName>name</PropertyName></TableColumnItem>
        <TableColumnItem><ScriptBlock>$_.Handles/1KB</ScriptBlock><FormatString>{0:N2}</FormatString></TableColumnItem>
       </TableColumnItems>
      </TableRowEntry>
     </TableRowEntries>
    </TableControl>
   </View>

In order for me to take advantage of this, I need to save it to a file and then call update-formatdata on the new file. You might notice that this is not quite complete as it doesn’t have the required elements for the beginning and end of the file, so I’ve got another parameter –complete which emits complete, standalone XML which I can dump into a file and then import. Once imported I can just use format-table as usual (in this case since there’s already a table format for process objects, I need to include the view name, which is “ProcessTable”, in this case. If you’re building formatting for an object of your own creation or doesn’t already exist, this last step is not necessary. Just output the object and the formatter takes care of everything automatically.

PS> get-process lsass|new-tableformat id,handles,name,@{L="HandlesKB";E={$_.Handles/1KB};A="Right";F="{0:N2}"} -au -comp
 > handleKB.format.ps1xml
PS> update-formatdata handleKB.format.ps1xml
PS> get-process lsass | format-table -view ProcessTable

id  handles name  HandlesKB
--  ------- ----  ---------
552 1263    lsass      1.23

Here’s the script, it’s only 125 lines or so!

param (
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]$object,
    [Parameter(Mandatory=$true,Position=0)][object[]]$property,
    [Parameter()][switch]$Complete,
    [Parameter()][switch]$auto,
    [Parameter()][string]$name,
    [Parameter()][switch]$force
    )
End
{
if ( $object )
{
    if ( $object -is "PSObject")
    {
        $TN = $object.psobject.typenames[0]
    }
    else
    {
        $TN = $object.gettype().fullname
    }
}
elseif ( $name )
{
    $TN = $name
}
$NAME = $TN.split(".")[-1]
$sb = new-object System.Text.StringBuilder
if ( $complete )
{
    [void]$sb.Append("<Configuration>`n")
    [void]$sb.Append(" <ViewDefinitions>`n")
}
[void]$sb.Append(" <View>`n")
[void]$sb.Append(" <Name>${Name}Table</Name>`n")
[void]$sb.Append(" <ViewSelectedBy>`n")
[void]$sb.Append(" <TypeName>${TN}</TypeName>`n")
[void]$sb.Append(" </ViewSelectedBy>`n")
[void]$sb.Append(" <TableControl>`n")
if ( $auto )
{
    [void]$sb.Append(" <AutoSize />`n")
}
[void]$sb.Append(" <TableHeaders>`n")
# 
# Now loop through the properties, creating a header for each 
# provided property
#
foreach($p in $property)
{
    if ( $p -is "string" )
    {
        [void]$sb.Append(" <TableColumnHeader><Label>${p}</Label></TableColumnHeader>`n")
    }
    elseif ( $p -is "hashtable" )
    {
        $Label = $p.keys | ?{$_ -match "^L|^N" }
        if ( ! $Label )
        {
            throw "need Name or Label Key"
        }
        [void]$sb.Append(" <TableColumnHeader>`n")
        [void]$sb.Append(" <Label>" + $p.$label + "</Label>`n")
        $Width = $p.Keys |?{$_ -match "^W"}|select -first 1
        if ( $Width )
        {
            [void]$sb.Append(" <Width>" + $p.$Width + "</Width>`n")
        }
        $Align = $p.Keys |?{$_ -match "^A"}|select -first 1
        if ( $Align )
        {
            [void]$sb.Append(" <Alignment>" + $p.$align + "</Alignment>`n")
        }
        [void]$sb.Append(" </TableColumnHeader>`n")
        # write-host -for red ("skipping " + $p.Name + " for now")
    }
}
[void]$sb.Append(" </TableHeaders>`n")
[void]$sb.Append(" <TableRowEntries>`n")
[void]$sb.Append(" <TableRowEntry>`n")
[void]$sb.Append(" <TableColumnItems>`n")
foreach($p in $property)
{
    if ( $p -is "string" )
    {
        [void]$sb.Append(" <TableColumnItem><PropertyName>${p}</PropertyName></TableColumnItem>`n")
    }
    elseif ( $p -is "hashtable" )
    {
        [void]$sb.Append(" <TableColumnItem>")
        $Name = $p.Keys | ?{ $_ -match "^N" }|select -first 1
        if ( $Name )
        {
            $v = $p.$Name
            [void]$sb.Append("<PropertyName>$v</PropertyName>")
        }
        $Expression = $p.Keys | ?{ $_ -match "^E" }|select -first 1
        if ( $Expression )
        {
            $v = $p.$Expression
            [void]$sb.Append("<ScriptBlock>$v</ScriptBlock>")
        }
        $Format = $p.Keys | ?{$_ -match "^F" }|select -first 1
        if ( $Format )
        {
            $v = $p.$Format
            [void]$sb.Append("<FormatString>$v</FormatString>")
        }
        [void]$Sb.Append("</TableColumnItem>`n")
    }
}
[void]$sb.Append(" </TableColumnItems>`n")
[void]$sb.Append(" </TableRowEntry>`n")
[void]$sb.Append(" </TableRowEntries>`n")
[void]$sb.Append(" </TableControl>`n")
[void]$sb.Append(" </View>`n")
if ( $complete )
{
    [void]$sb.Append(" </ViewDefinitions>`n")
    [void]$sb.Append("</Configuration>`n")
}
$sb.ToString()
}

the thing to note here is that I’m just building the XML string and then emitting it, I’m not using all the XML programming goo, which would probably be better practice, but I didn’t want to be bothered (clearly, that could be a future enhancement). You’ll notice that it handles pretty much everything that format-table does.

I hope this is as useful for you as it is for me

Posted April 19, 2011 by jtruher3 in PowerShell

missing in action   1 comment

Clearly i haven’t posted for a long while, and while there are many reasons which I don’t need to go into, it’s not because I’ve been slacking. Most recently, I’ve been working on a codeplex project (http://smlets.codeplex.com) which uses PowerShell to interact with Service Manager (i’ve been working on Service Manager for a while). The project has been great fun and has let me do a quite a bit of cmdlet development, which I really enjoy. If you use Service Manager, you should definitely check it out.

The real point of this post is to share my experience over the last couple of days at the PowerShell Deep Dive which happened at the Experts Conference in Las Vegas. I have always been extremely proud of my work while I was working on PowerShell; giving birth to that product was a long, and at times, difficult experience which taught me a lot about myself. However, seeing how it has affected (in a positive way) so many people really makes all that worth while. Seeing the excitement in others cannot help but uplift the spirit and I am so glad that I had the opportunity to contribute. The stories about how PowerShell have improved the lives of others in concrete ways is awesome.

The experience has reinvigorated me as well; talking to so many intelligent, articulate people has given me a whole bunch of new ideas to write on, so I hope you will see a stream of posts here (as long as I can follow through!). My next post will be on creating custom formatting, hopefully posted before the plane lands.

Posted April 19, 2011 by jtruher3 in General, PowerShell, ServiceManager

Getting system uptime   Leave a comment

Sometimes I miss my Unix system. Actually, that’s not true, sometimes I miss the tools that are available.  For example, I needed to figure out when the system booted.  Thankfully, there are performance counters that can tell me!

PS# cat get-uptime.ps1
$PCounter = "System.Diagnostics.PerformanceCounter"
$counter = new-object $PCounter System,"System Up Time"
$value = $counter.NextValue()
$uptime = [System.TimeSpan]::FromSeconds($counter.NextValue())
"Uptime: $uptime"
"System Boot: " + ((get-date) - $uptime)

Simple!

Posted July 28, 2009 by jtruher3 in PowerShell

I love Twitter   3 comments

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. 
 
Here’s the Yedda.Twitter library
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.
 
 
 
 

Posted August 31, 2007 by jtruher3 in PowerShell

Tracing the script stack   1 comment

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

 

Posted July 15, 2007 by jtruher3 in PowerShell

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

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

Posted February 19, 2007 by jtruher3 in PowerShell

Getting more out of help   Leave a comment

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

Posted December 2, 2006 by jtruher3 in PowerShell

Dijkstra   1 comment

 

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!

 

 

 

Posted October 16, 2006 by jtruher3 in PowerShell

Getting Disk Usage Information   4 comments

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 :^)

Posted September 8, 2006 by jtruher3 in PowerShell

Follow

Get every new post delivered to your Inbox.