A little of this, a little of that   Leave a comment

Last week I had a delightful time at the EU PowerShell summit in Amsterdam (http://twitter.com/PSHSummit) and the Dutch PowerShell User Group (http://www.dupsug.com/?page_id=914). One of the talks I gave was a quick walk through of a dozen or so hidden PowerShell gems. I thought I would share some of those here. Here we go

Nearly every PowerShell user is aware of the $HOST automatic variable, but you might not be taking advantage of all of it’s coolness. Here’s a few things that you might not use, but might want to

$HOST.UI.RawUI.WindowTitle

I like a nice clean interface when I’m using the shell, with very little extraneous info, so I don’t have my path as part of the prompt (it’s just “PS>” if I’m a regular user and “PS#” in an elevated shell). I put my location in the window title by having my cd function include:

$HOST.UI.RawUI.WindowTitle = $PWD

However, $HOST can do a whole bunch more!

Blog001

The screen is made up of buffercells which have the specific character, foreground and background colors which can be changed and replaced with other APIs on the host. The script below randomly changes the colors of characters on the screen, and then paints the entire window with an ‘X’ in every cell. The code is relatively simple, and while you should be able to just cut-and-paste running this as a script will be cleaner. Comments and code follow:

# A couple of limits
$xmax = 30
$ymax = 80
# clear up before the fun
clear
# capture your current cursor position
$origin = $host.ui.rawui.CursorPosition
# put something on the screen
get-childitem $env:windir | select -first ($host.ui.rawui.windowsize.height-8)
# create a rectangle of part of the screen

$ul = new-object System.Management.Automation.Host.Coordinates 0,0
$lr = new-object System.Management.Automation.Host.Coordinates $ymax,$xmax
$r = new-object System.Management.Automation.Host.Rectangle $ul,$lr
# and capture the screen

$bc = $host.ui.rawui.GetBufferContents($r)
$cp = $host.ui.rawui.CursorPosition
$host.ui.rawui.CursorPosition  = $origin
# now for all of our buffer cells, paint the character with a new foreground color
# we’ll use write-host as it’s pretty because you can see the change as each character
# is painted

[consolecolor]$c = 1
foreach($x in 0..$xmax)
{
        foreach($y in 0..$ymax)
        {
                $f = get-random 16
                write-host -for $f -no $bc[$x,$y].Character
        }
        write-host ""
}
$host.ui.rawui.CursorPosition  = $cp
# before proceeding, get <ENTER>
read-host "Press Enter"

# Paint the screen with “X”
# determine the window size
$ws = $host.ui.rawui.windowsize
$ul = new-object System.Management.Automation.Host.Coordinates 0,0
$lr = new-object System.Management.Automation.Host.Coordinates $ws.width,$ws.height
$bc = $bc = new-object System.Management.Automation.Host.BufferCell
$bc.Character = "X"
$bc.ForegroundColor = "White"
$rect = new System.Management.Automation.Host.Rectangle
$rect.Bottom = $ws.height
$rect.Right = $ws.width
$coor = new-object System.Management.Automation.Host.Coordinates @(0,0)
$host.ui.rawui.CursorPosition = $coor
clear
# set the entire buffer contents with our single buffer cell
$host.ui.rawui.setbuffercontents($rect,$bc)

This could be done differently, of course. The random colors could have been assigned in the buffercells and then written all at once with the SetBufferContents method.

I’ve used a similar technique to implement a curses library for PowerShell, I’ll show more of that in a later missive. Here’s a teaser:

http://1drv.ms/1CZe8Mr

 

 

Posted October 9, 2014 by jtruher3 in PowerShell

A Little Dusty   1 comment

I need to blow the dust off this sight.

About 2 months ago I rejoined the PowerShell team after a 7 year hiatus. I’ve continued to use PowerShell everyday, and I’m excited to be part of the team again. I had been in the Office org working on Office online services and most of the PowerShell work I did was pretty specific to that, so I didn’t really have much to share. That condition has changed, so I expect I’ll have more to share.

One of the issues that we had when working with online services was certificate replacement. From a security perspective, you want to be able to replace certificates on a fairly regular basis; however, it’s sometimes hard to be sure that you’re not changing more than you should when you get a new cert (we had that problem quite a bit). To that end, I created a certificate comparison script which helped make sure that the new cert would not have the wrong changes. It allows you to compare arbitrary properties or extensions of two certificates (public (cer) or private (pfx)) so you could easily see if the new certificate was consistent with the prior cert in the places it should, and have only the changes (usually NotBefore and NotAfter).

Here’s the script:

[CmdletBinding()]
param (
    [Parameter(Position=0,Mandatory=$true,ParameterSetName="pfx")]$pfx1,
    [Parameter(Position=1,Mandatory=$true,ParameterSetName="pfx")]$pass1,
    [Parameter(Position=2,Mandatory=$true,ParameterSetName="pfx")]$pfx2,
    [Parameter(Position=3,Mandatory=$true,ParameterSetName="pfx")]$pass2,
    [Parameter(Position=0,Mandatory=$true,ParameterSetName="cer")]$cer1,
    [Parameter(Position=1,Mandatory=$true,ParameterSetName="cer")]$cer2,
    [Parameter()]$properties = @("NotBefore","NotAfter","Subject","Issuer"),
    [Parameter()]$extensions = "Key Usage",
    [Parameter()][switch]$raw
    )

BEGIN
{
    $X509T = "System.Security.Cryptography.X509Certificates.X509Certificate2"
}
END

{
    if ( $PSCmdlet.ParameterSetName -eq "pfx")
    {
        $fullname = (get-item $pfx1).fullname
        $cert1 = new-object $X509T ([io.file]::ReadAllBytes($fullname)),$pass1
        $fullname = (get-item $pfx2).fullname
        $cert2 = new-object $X509T ([io.file]::ReadAllBytes($fullname)),$pass2
        $file1 = $pfx1
        $file2 = $pfx2
        if ( $file1 -eq $file2 ) { $file2 = "${file2} (2)" }
    }
    else
    {
        $fullname = (get-item $cer1).fullname
        $cert1 = new-object $X509T $fullname
        $fullname = (get-item $cer2).fullname
        $cert2 = new-object $X509T $fullname
        $file1 = $cer1
        $file2 = $cer2
        if ( $file1 -eq $file2 ) { $file2 = "${file2} (2)" }
    }
   
    $collection = @()
    foreach($property in $properties)
    {
        $collection += new-object psobject -prop @{
            Property = $property
            $file1 = $cert1.$property
            $File2 = $cert2.$property
            Different = $cert1.$property -ne $cert2.$property
            }
    }
    foreach ( $extension in $extensions)
    {
        $v1 = $cert1.Extensions|%{$_.oid}|?{$_.friendlyname -eq "$extension"}|%{$_.value}
        $v2 = $cert2.Extensions|%{$_.oid}|?{$_.friendlyname -eq "$extension"}|%{$_.value}
        $k = $Extension -replace " "
        $collection += new-object psobject -prop @{
            Property = $k
            $File1 = $v1
            $File2 = $v2
            Different = $v1 -ne $v2
            }
    }
    if ( $raw )
    {
        $cert1
        $cert2
        ,$collection
    }
    else
    {
        $collection|ft @{ L="Property";W = 14; E={$_.property}},@{L="Different";W=10;E={$_.different}},$file1,$file2
    }
}

Because this was a tool for our service engineers to use, I provide formatted data, rather than raw objects. However, if you use the –Raw switch, you’ll get the certs that you’re comparing as well as the collection of differences. Here’s an example of output (using a cert provided by the network tool ‘fiddler2’)

 

PS# .\Compare-Certificate.ps1 -pfx1 C:\temp\fiddlerCert.pfx -pfx2 C:\temp\fiddlerCert.pfx

cmdlet Compare-Certificate.ps1 at command pipeline position 1
Supply values for the following parameters:
pass1:
pass2:

Property        Different C:\temp\fiddlerCert.pfx                                  C:\temp\fiddlerCert.pfx (2)
——–        ——— ———————–                                  —————————
NotBefore           False 8/2/2013 12:00:00 AM                                     8/2/2013 12:00:00 AM
NotAfter            False 8/1/2024 11:59:59 PM                                     8/1/2024 11:59:59 PM
Subject             False CN=clients1.google.com, O=DO_NOT_TRUST, OU=Created by… CN=clients1.google.com, O=DO_NOT_TRUST, OU=Created by…
Issuer              False CN=DO_NOT_TRUST_FiddlerRoot, O=DO_NOT_TRUST, OU=Creat… CN=DO_NOT_TRUST_FiddlerRoot, O=DO_NOT_TRUST, OU=Creat…
KeyUsage            False

Since it’s the same cert in this case, there’s no differences. That in itself would be pretty handy if you gotten a new cert and it was identical to the old cert.

Posted September 24, 2014 by jtruher3 in PowerShell

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

Service Manager Announcements   Leave a comment

I’ve written in previous blogs on how to get data out of Service Manager, and generally, that data is usually simple text, numbers or sometimes an enumeration (which is pretty easy to convert to text). However, Service Manager also allows you to store text with formatting (rich text data) which can be pretty difficult to view. First, let’s create an announcement:

We can use one of the scripts created in an earlier posting to retrieve an instance of the announcement:

PS> get-smclass announcement.item|get-scsmobject|fl
Id             : 2
Title          : Announcement 001
Body           : {\rtf1\ansi\ansicpg1252\uc1\htmautsp\deff2{\fonttbl{\f0\fcharset0
                 Times New Roman;}{\f2\fcharset0 Sego
                 e UI;}{\f3\fcharset0 Calibri;}{\f4\fcharset0 Copperplate Gothic
                 Bold;}}{\colortbl\red0\green0\blue0;\r
                 ed255\green255\blue255;\red255\green0\blue0;}
                 {\*\listtable
                 {\list\listtemplateid1\listhybrid
. . .
                 b0\jclisttab\tx720\fi-360\ql\par}
                 }
                 }
ExpirationDate : 6/28/2010 7:00:00 AM
Priority       : System.Announcement.PriorityEnum.Medium
DisplayName    : Announcement 001
Type           : System.Announcement.Item
Name           : 2
Path           :
FullName       : System.Announcement.Item:2

Since the body property of the announcement is rich text, it really isn’t readable in this format, but we can fix that with a fun little script. This is one of those scripts that allow us to mix the command line and the graphical environment. We’ll call this script Display-RichText.ps1 since that’s what it does!

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
param (
$string
)
begin
{
[void][reflection.assembly]::LoadWithPartialName(“System.Windows.Forms”)
[void][reflection.assembly]::LoadWithPartialName(“System.Drawing”)
## the form
$form = new-object System.Windows.Forms.Form
$form.size = new-object System.Drawing.Size 400,400 

## the Rich text box
$text = new-object System.Windows.Forms.RichTextBox
$text.multiline = $true
$text.dock = “Fill”
$text.scrollbars = “Both”
$text.width = 80

## Quit button
$QuitButton = new Windows.Forms.Button
$QuitButton.Name = “QuitButton”
$QuitButton.TabIndex = 0
$QuitButton.Text = “Quit”
$QuitButton.UseVisualStyleBackColor = $true
$QuitButton.Add_Click({$form.dispose()})
$QuitButton.Dock = “Bottom”

$form.controls.add($text)
$form.controls.add($QuitButton)
function loadtext
{
param ( $string )
try
{
$bytes = [byte[]]($string.ToCharArray())
$stream = new-object io.memorystream $bytes,$true
$text.loadfile($stream, “richtext”)
$text.DeselectAll()
[void]$form.showdialog()
}
finally
{
$stream.close()
$stream.dispose()
}
}

}
end
{
loadtext $string
}

Now let’s see what we can do!

PS> $announcement = get-smclass announcement.item|get-scsmobject
PS> display-richtext $announcement.body

Now we can see the contents of the announcement!

Posted June 13, 2010 by jtruher3 in ServiceManager

Saving Service Manager Instances in a CSV file   Leave a comment

In a post I did a few months ago, I wrote how it you can retrieve data from Service Manager via a PowerShell script, but I didn’t show much more than just plain output. Earlier this week I was asked if it would be possible to export configuration items in a CSV file. By using the script I wrote months ago, we have all the tools we need. Let’s review the previous script:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
param ( $classname )
$emg      = new-object microsoft.enterprisemanagement.enterprisemanagementgroup localhost
$class    = $emg.EntityTypes.GetClasses()|?{$_.name -eq $classname}
if ( ! $class )
{
    Write-Error "`nERROR: Class ‘$classname’ not found, exiting."
    exit
}
$DEFAULT  = [Microsoft.EnterpriseManagement.Common.ObjectQueryOptions]::Default
$EMOT     = [Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject]
# Retrieve the interface for EntityObjects, which we’ll use when we create our generic method
$IMGMT    = $emg.EntityObjects.GetType()
# the types of the parameters, this is so we can find the right method
[type[]]$TYPES = [Microsoft.EnterpriseManagement.Configuration.ManagementPackClass],
                 [Microsoft.EnterpriseManagement.Common.ObjectQueryOptions]
# Retrieve the method
$ObjectReader = $IMGMT.GetMethod("GetObjectReader",$TYPES)
# Create a generic method
$GenericMethod = $ObjectReader.MakeGenericMethod($EMOT)
# Invoke the method with our arguments
[array]$arguments = [Microsoft.EnterpriseManagement.Configuration.ManagementPackClass]$class,$DEFAULT
$GenericMethod.invoke($emg.EntityObjects,$arguments) | %{
    # Create a custom object based on the original object
    $o = new-object psobject $_
    # elevate the properties in the Values collection to the top level
    $o.values|%{ $o | add-member -force NoteProperty $_.Type $_.Value }
    # assign a synthetic typename to the object, so we can use our formatting
    # more easily
    $name = $_.GetLeastDerivedNonAbstractClass().name
    $o.psobject.typenames.Insert(0, "EnterpriseManagementObject#$name")
    # now, emit the object!
    $o
    }

We’ll call this script get-object (and it assumes that you’ve already loaded the Microsoft.EnterpriseManagement.Core.dll), and just to remind you, here’s how it works:

PS> get-object microsoft.windows.computer|format-table DisplayName,Netbios*,PrincipalName -au

DisplayName                   NetbiosComputerName NetbiosDomainName PrincipalName
-----------                   ------------------- ----------------- -------------
JWT-SCDW$                     JWT-SCDW            WOODGROVE         JWT-SCDW.woodgrove.com
WIN-752HJBSX24M.woodgrove.com WIN-752HJBSX24M     WOODGROVE         WIN-752HJBSX24M.woodgrove.com

You provide a class name to it and it retrieves all the instances of that class. Now, to save these instances in a CSV file, I just need to use the Export-CSV cmdlet that is part of the PowerShell distribution and viola!

PS> get-object microsoft.windows.computer|Export-CSV Microsoft.Windows.Computer.csv
PS> get-content .\Microsoft.Windows.Computer.csv
#TYPE EnterpriseManagementObject#Microsoft.Windows.Computer
"PrincipalName","DNSName","NetbiosComputerName","NetbiosDomainName","IPAddres...
"JWT-SCDW.woodgrove.com","JWT-SCDW.woodgrove.com","JWT-SCDW","WOODGROVE",,,"S...
"WIN-752HJBSX24M.woodgrove.com","WIN-752HJBSX24M.woodgrove.com","WIN-752HJBSX...

You can use this to create a copy of your data, or use it as a way to exchange your data with other applications. Because of the way that other applications use CSV files, you may need to remove the first line of the CSV file which describes what the object was from the PowerShell perspective, but otherwise, you should be able to use the CSV file easily!

Posted June 4, 2010 by jtruher3 in ServiceManager

Follow

Get every new post delivered to your Inbox.

Join 116 other followers