This is a temporary post that was not deleted. Please delete this manually. (443c23da-13d3-4098-bd55-5b54c1df3e73 – 3bfe001a-32de-4114-a6b4-4005b770f6d7)
Author Archive
Temporary Post Used For Theme Detection (ae17e6a2-25fb-4a0c-a331-08054d3157f1 – 3bfe001a-32de-4114-a6b4-4005b770f6d7) Leave a comment
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.
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
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.
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 ## Quit button $form.controls.add($text) } |
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!
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!
More and more with management packs Leave a comment
I’ve been a little slow in updating here, but that doesn’t reflect any inactivity, just my poor rhythm for posting.
Earlier in the year I did a post on what was possible to do in a single line of PowerShell. In this post, I’m going to take that further and explore this in more detail. Specifically, we’ll take a closer look at all of the possibilities with PowerShell and Service Manager management packs. I’ve blogged on management packs in the past, but generally on what to do with them rather than what they are. In this blog, we’ll take a look at the various properties of a management pack and how easy they are to access from PowerShell.
First, if we take a look at the available properties of a management pack, we see a pretty rich object model.
PS> $emg = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup Localhost
PS> $emg.ManagementPacks.GetManagementPacks()|get-member
TypeName: Microsoft.EnterpriseManagement.Configuration.ManagementPack
Name MemberType Definition
---- ---------- ----------
AcceptChanges Method System.Void AcceptChanges(), System.Void AcceptChanges(Micros...
AddService Method System.Void AddService[T, V](string name, Microsoft.Enterpris...
CheckVersionCompatibility Method System.Void CheckVersionCompatibility(Microsoft.EnterpriseMan...
Configure Method System.Void Configure(System.IO.Stream configuration)
DeleteEnterpriseManagementObjectGroup Method System.Void DeleteEnterpriseManagementObjectGroup(Microsoft.E...
DeleteMonitoringObjectGroup Method System.Void DeleteMonitoringObjectGroup(Microsoft.EnterpriseM...
Dispose Method System.Void Dispose()
Equals Method bool Equals(System.Object obj)
FindManagementPackElementByName Method Microsoft.EnterpriseManagement.Configuration.ManagementPackEl...
GetCategories Method Microsoft.EnterpriseManagement.Configuration.ManagementPackEl...
. . .
RemoveService Method System.Void RemoveService(string name)
RemoveServices Method System.Void RemoveServices()
ToString Method string ToString()
Verify Method System.Void Verify()
ContentReadable Property System.Boolean ContentReadable {get;}
DefaultLanguageCode Property System.String DefaultLanguageCode {get;set;}
Description Property System.String Description {get;set;}
DisplayName Property System.String DisplayName {get;set;}
. . .
SchemaVersion Property System.Version SchemaVersion {get;}
Sealed Property System.Boolean Sealed {get;}
TimeCreated Property System.DateTime TimeCreated {get;}
Version Property System.Version Version {get;set;}
VersionId Property System.Guid VersionId {get;}
In fact, you’ll see about 150 different methods and properties. I’ve discussed various aspects of some of the properties and methods in earlier posts, but I want to concentrate on the methods in this posting, and the methods I want to look at specifically are the Get* methods. These methods return information about the contents and configuration of the management packs. Since there are about 100 Get* methods, i’d like to reduce the list to something more manageable, so we’ll only look at the get* methods that don’t take any arguments.
PS> $emg.ManagementPacks.GetManagementPacks()|get-member get*|?{$_.definition -match "\(\)"}
TypeName: Microsoft.EnterpriseManagement.Configuration.ManagementPack
Name MemberType Definition
---- ---------- ----------
GetCategories Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetClasses Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetConfigurationGroups Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetConsoleTasks Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetDataTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetDataWarehouseDataSets Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetDataWarehouseScripts Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetDiagnostics Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetDimensionTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetDiscoveries Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetEnumerations Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetFactTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetFolderItems Method Microsoft.EnterpriseManagement.Configuration.ManagementPackItemCollection[Mic...
GetFolders Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetForms Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetHashCode Method int GetHashCode()
GetImageReferences Method Microsoft.EnterpriseManagement.Configuration.ManagementPackItemCollection[Mic...
GetImages Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetLanguagePacks Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetLinkedReports Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetManagementPackCategories Method System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Configuration...
GetMeasureTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetModuleTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetMonitors Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetObjectTemplates Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetOutriggerTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetOverrides Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetRecoveries Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetRelationshipFactTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetRelationships Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetReportParameterControls Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetReportResources Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetReports Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetResources Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetRules Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetSchemaTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetSecureReferences Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetServiceLevelObjectives Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetServices Method System.Collections.Generic.IList[T] GetServices[T]()
GetStringResources Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetTasks Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetTemplates Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetType Method type GetType()
GetTypeProjections Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetUIPages Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetUIPageSets Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetUnitMonitorTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetViews Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetViewTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
GetWarehouseModuleTypes Method Microsoft.EnterpriseManagement.Configuration.ManagementPackElementCollection[...
There are still 50 of these, but there are some methods that are easier to use. First nearly all of these methods return a ManagementPackElementCollection, only 2 return a ManagementPackItemCollection (GetFolderItems and GetImageReferences), and 4 others require a typed invocation (GetServices, GetManagementPackCategories, GetResources and GetServiceLevelObjectives). We'll focus on the methods that return a ManagementPackElementCollection and that don't take a generic type, because they're the simplest to deal with. Since these methods don't take any arguments, we can just call them and see what we get back. The method name should give us a clue about what we'll get back, and you can check the Service Manager SDK documentation as well for more information.
As a first example, we'll retrieve the System.Library management pack and inspect all the classes declared in the mp and get their base class.
PS> $emg.ManagementPacks.GetManagementPacks()|?{$_.Name -eq "System.Library"}|
>> %{$_.GetClasses()}|ft Abstract,Name,@{Label="Base";e={$emg.entitytypes.GetClass($_.base.id).Name}} -au
>>
Abstract Name Base
-------- ---- ----
True System.Entity
True System.Collections System.Entity
True System.ConfigItem System.Entity
True System.LogicalEntity System.ConfigItem
True System.ApplicationComponent System.LogicalEntity
True System.ComputerRole System.LogicalEntity
True System.Database System.ApplicationComponent
True System.Device System.LogicalEntity
True System.Computer System.Device
True System.FTPSite System.ApplicationComponent
True System.Group System.LogicalEntity
True System.LocalApplication System.LogicalEntity
True System.LogicalHardware System.LogicalEntity
True System.NetworkDevice System.Device
True System.OperatingSystem System.LogicalEntity
True System.Perspective System.LogicalEntity
True System.PhysicalEntity System.ConfigItem
True System.Printer System.Device
True System.Service System.LogicalEntity
True System.SoftwareInstallation System.LogicalEntity
True System.User System.LogicalEntity
False System.Domain.User System.User
True System.WebSite System.ApplicationComponent
Note that I'm using the EMG in the formatting directives to get the base class.
If we wanted to get the XML for one of these classes, we can do that with the CreateNavigator method. We'll collect the classes and then use the CreateNavigator method to get at the XML. This examples retrieves the System.Library management pack and then displays the XML for the System.ConfigItem class.
PS> $emg.ManagementPacks.GetManagementPacks()|?{$_.Name -eq "System.Library"}|
>> %{$_.GetClasses()}|?{$_.Name -eq "System.ConfigItem"}|%{$_.CreateNavigator().OuterXML}
>>
<ClassType ID="System.ConfigItem" Accessibility="Public" Abstract="true"
Base="System.Entity" Hosted="false" Singleton="false" Extension="false">
<Property ID="ObjectStatus" Type="enum" AutoIncrement="false" Key="false" CaseSensitive="false"
MaxLength="256" MinLength="0" Required="false" EnumType="System.ConfigItem.ObjectStatusEnum"
DefaultValue="System.ConfigItem.ObjectStatusEnum.Active" />
<Property ID="AssetStatus" Type="enum" AutoIncrement="false" Key="false" CaseSensitive="false"
MaxLength="256" MinLength="0" Required="false" EnumType="System.ConfigItem.AssetStatusEnum" />
<Property ID="Notes" Type="richtext" AutoIncrement="false" Key="false" CaseSensitive="false"
MaxLength="4000" MinLength="0" Required="false" />
</ClassType>
Pretty cool! The following example retrieves all the views in the system:
PS> $emg.ManagementPacks.GetManagementPacks()|%{$_.GetViews()}|ft name,description
Name Description
---- -----------
Microsoft.EnterpriseManagement.ServiceManager.UI.Adminis... Lists all subscriptions
Microsoft.EnterpriseManagement.ServiceManager.UI.Adminis... Lists all Run As accounts
Microsoft.EnterpriseManagement.ServiceManager.UI.Adminis... Contains general, portal, and solution settings
Microsoft.EnterpriseManagement.ServiceManager.UI.Adminis... Administration Overview
Microsoft.EnterpriseManagement.ServiceManager.UI.Adminis... Lists all templates
. . .
AllComputersView Lists all computers
AllPrintersView Lists all printers
QueuesView Lists all the queues available
TemplatesView Lists all the templates available
TasksView Lists all the console tasks defined in the system
EnumerationView Displays all the lists available
GroupsView Lists all groups
WorkItemExclusionRule Work item exclusion workflow view
ChangeManagement.Views.ChangeRequestsCancelled Lists all canceled change requests
ChangeManagement.Views.ChangeRequestsCompleted Lists all completed change requests
ChangeManagement.Views.ChangeRequestsClosed Lists all closed change requests
ChangeManagement.Views.AllChangeRequests Lists all change requests
ChangeManagement.Views.ChangeRequestsRejected Lists all rejected change requests
ChangeManagement.Views.ChangeRequestsInReview Change Requests: In Review
. . .
System.WorkItem.Incident.Pending.View Lists all pending incidents
System.WorkItem.Incident.OverDue.View Lists all overdue incidents
System.WorkItem.Incident.Active.Unassigned.View Lists all open unassigned incidents
Microsoft.SystemCenter.AllActiveAnnouncementsView Active Announcements
Microsoft.SystemCenter.AllAnnouncementsView All Announcements
And we can inspect the XML for the one of the views (say the AllPrintersView), with this one-liner:
PS> $emg.ManagementPacks.GetManagementPacks()|%{$_.GetViews()}|
>> ?{$_.name -eq "allprintersview"}| %{$_.createnavigator().outerxml}
<View ID="AllPrintersView" Accessibility="Public" Enabled="true" Target="Windows!Microsoft.AD.Printer" TypeID="SMConsole!GridViewType" Visible="true">
<Category>NotUsed</Category>
<Data>
<Adapters>
<Adapter AdapterName="dataportal:EnterpriseManagementObjectProjectionAdapter">
<AdapterAssembly>Microsoft.EnterpriseManagement.UI.SdkDataAccess</AdapterAssembly>
<AdapterType>Microsoft.EnterpriseManagement.UI.SdkDataAccess.DataAdapters.EnterpriseManagementObjectProjectionAdapter</AdapterType>
</Adapter>
<Adapter AdapterName="viewframework://Adapters/AdvancedList">
<AdapterAssembly>Microsoft.EnterpriseManagement.UI.ViewFramework</AdapterAssembly>
<AdapterType>Microsoft.EnterpriseManagement.UI.ViewFramework.AdvancedListSupportAdapter</AdapterType>
</Adapter>
<Adapter AdapterName="omsdk://Adapters/Criteria">
<AdapterAssembly>Microsoft.EnterpriseManagement.UI.SdkDataAccess</AdapterAssembly>
<AdapterType>Microsoft.EnterpriseManagement.UI.SdkDataAccess.DataAdapters.SdkCriteriaAdapter</AdapterType>
</Adapter>
</Adapters>
<ItemsSource>
<AdvancedListSupportClass DataTypeName="" AdapterName="viewframework://Adapters/AdvancedList" FullUpdateAdapter="dataportal:EnterpriseManagementObjectProjectionAdapter" DataSource="mom:ManagementGroup" IsRecurring="True" RecurrenceFrequency="{x:Static s:Int32.MaxValue}" FullUpdateFrequency="1" Streaming="true" xmlns="clr-namespace:Microsoft.EnterpriseManagement.UI.ViewFramework;assembly=Microsoft.EnterpriseManagement.UI.ViewFramework" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib">
<AdvancedListSupportClass.Parameters>
<QueryParameter Parameter="TypeProjectionName" Value="Microsoft.Windows.PrinterView.ProjectionType" />
</AdvancedListSupportClass.Parameters>
</AdvancedListSupportClass>
</ItemsSource>
<Criteria>
<QueryCriteria Adapter="omsdk://Adapters/Criteria" xmlns="http://tempuri.org/Criteria.xsd">
<Criteria>
<FreeformCriteria>
<Freeform>
<Criteria xmlns="http://Microsoft.EnterpriseManagement.Core.Criteria/">
<Expression>
<SimpleExpression>
<ValueExpressionLeft>
<Property>$Context/Property[Type='System!System.ConfigItem']/ObjectStatus$</Property>
</ValueExpressionLeft>
<Operator>NotEqual</Operator>
<ValueExpressionRight>
<Value>$MPElement[Name="System!System.ConfigItem.ObjectStatusEnum.PendingDelete"]$</Value>
</ValueExpressionRight>
</SimpleExpression>
</Expression>
</Criteria>
</Freeform>
</FreeformCriteria>
</Criteria>
</QueryCriteria>
</Criteria>
</Data>
<Presentation>
<Columns>
<mux:ColumnCollection xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:mux="http://schemas.microsoft.com/SystemCenter/Common/UI/Views/GridView" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:datebinding="clr-namespace:Microsoft.EnterpriseManagement.UI.SdkDataAccess.Common;assembly=Microsoft.EnterpriseManagement.UI.SdkDataAccess">
<mux:Column Name="uncName" DisplayMemberBinding="{Binding Path=UNCName}" Width="120" DisplayName="Header_UNCName" Property="UNCName" DataType="s:String" />
<mux:Column Name="printerName" DisplayMemberBinding="{Binding Path=PrinterName}" Width="120" DisplayName="Header_PrinterName" Property="PrinterName" DataType="s:String" />
<mux:Column Name="description" DisplayMemberBinding="{Binding Path=Description}" Width="120" DisplayName="Header_Description" Property="Description" DataType="s:String" />
<mux:Column Name="location" DisplayMemberBinding="{Binding Path=Location}" Width="120" DisplayName="Header_Location" Property="Location" DataType="s:String" />
</mux:ColumnCollection>
</Columns>
<ViewStrings>
<ViewString ID="Header_UNCName">$MPElement[Name="AllPrintersView.Header_UNCName"]$</ViewString>
<ViewString ID="Header_PrinterName">$MPElement[Name="AllPrintersView.Header_PrinterName"]$</ViewString>
<ViewString ID="Header_Description">$MPElement[Name="AllPrintersView.Header_Description"]$</ViewString>
<ViewString ID="Header_Location">$MPElement[Name="AllPrintersView.Header_Location"]$</ViewString>
</ViewStrings>
</Presentation>
</View>
Since the CreateNavigator method is available on ManagementPackElement, we can use that method with every one of the objects returned by the methods mentioned above – it’s a great way to explore and see what’s going on in your management pack.
As an interesting aside, we can invoke all of these simple methods with a couple of lines of script. This will tell us the total number of each of the management pack elements returned by the method. The first line collects the names of the methods that we want to invoke. The second line creates a hash table to hold our results and the last line retrieves all the management packs, and invokes each method on the management pack object and adds the count of those elements to the hash table. This is a pretty cool trick to invoke a method without know what the method name is before hand.
PS> $names = $emg.ManagementPacks.GetManagementPacks()|
>> get-member get*s|
>> ?{$_.definition -match "ManagementPackElementCollection" -and
>> $_.definition -notmatch "\[T\]"}|%{$_.name}
PS> $counthash = $names | %{ $h = @{}}{$h.$_ = 0 } {$h }
PS> $emg.ManagementPacks.GetManagementPacks()|%{
>> $mp = $_
>> $names | %{ $counthash.$_ += $mp.$_.invoke().count }
>> }
>>
PS> $counthash
Name Value
---- -----
GetReportResources 28
GetModuleTypes 293
GetObjectTemplates 24
GetUIPages 48
GetOutriggerTypes 30
GetRules 53
GetTasks 6
GetReports 23
GetSchemaTypes 28
GetClasses 272
GetMonitors 47
GetTypeProjections 56
GetReportParameterControls 0
GetConfigurationGroups 0
GetDimensionTypes 26
GetRelationships 124
GetViewTypes 24
GetLinkedReports 0
GetStringResources 1211
GetFactTypes 4
GetDataTypes 45
GetTemplates 3
GetForms 16
GetFolders 85
GetWarehouseModuleTypes 9
GetMeasureTypes 2
GetSecureReferences 11
GetRecoveries 1
GetViews 138
GetDiscoveries 31
GetUIPageSets 225
GetEnumerations 460
GetDataWarehouseScripts 38
GetConsoleTasks 160
GetCategories 583
GetDataWarehouseDataSets 0
GetOverrides 49
GetImages 254
GetDiagnostics 0
GetLanguagePacks 89
GetRelationshipFactTypes 28
GetUnitMonitorTypes 239
woo hoo!
Testing the Integrity of a Management Pack Leave a comment
When I’m creating a new management pack, I want to be sure that the management pack is valid before I import it. I know that if it’s wrong, the system won’t import it, but I generally like to know these things before I try. In order to do that, I wrote a pretty simple script to test the management pack. It takes advantage of the Verify method on management pack object. This verification does a number of things. First, it checks to be sure that there are no XSD validation errors. If there are, the method throws an exception. The Verify method also checks to be sure that references are correct and present. Note that there may still be some errors that are found upon import, but most of the issues will be caught by this script.
When everything works right, you’ll see something like the following:
PS> test-managementpack .\PowerShell.WATest.xml|ft Verified,Name,FullName -au Verified Name FullName -------- ---- -------- True PowerShell.WATest C:\Program Files\System Center Management Packs\PowerShell.WATest.xml
However, if something is busted, you'll see this:
PS> test-managementpack .\BigHonkingMP.xml|Ft Verified,Name,FullName -au Verified Name FullName -------- ---- -------- False BigHonkingMP C:\Program Files\System Center Management Packs\BigHonkingMP.xml
The results of test-managementpack include the error, you can see what happened, by getting the Error property:
PS> test-managementpack .\BigHonkingMP.xml|Fl name,error
Name : BigHonkingMP
Error : {Exception calling "Verify" with "0" argument(s): "Verification failed with 1 errors:
-------------------------------------------------------
Error 1:
: Failed to verify class: BigHonkingMP.ConcreteMicroBleh
Host class BigHonkingMP.AbstractBleh and hosted class BigHonkingMP.ConcreteMicroBleh define the same set of key properties.
-------------------------------------------------------
"}
You can also pipe files at test-managementpack, so you can use it like this:
PS> ls *.xml|test-managementpack Verified Name FullName -------- ---- -------- False BigHonkingMP C:\Program Files\System Center Management Packs\BigHonkingMP.xml . . .
Which I thought was pretty handy - going through a bunch of MPs at once.
Here's the script, it relies on version 2.0 of PowerShell. I've added online help, so you can run get-help test-managementpack to get info on it. The business end of the script is in line 29 and 30. That's where I create a management pack object and then call verify. If the management pack object can't be created, or if it fails verify, the exception gets caught and then the script builds up an error message. Rather than using Write-Error to indicate the problem, I decided to include the error message in resultant object. I did this because I didn't really want the error output at this point, but I wanted to hang on to the verify failure.
|
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 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 |
#requires -version 2.0
param ( BEGIN PROCESS <# .INPUTS #> |
Getting Service Manager Incidents with PowerShell 2 comments
So far in these blogs, I’ve not addressed one of the main focuses of Service Manager which is how to manage incidents. In Service Manager, incidents are more than simple instances, they’re what’s called a “projection”. Jakub has some great postings on projections (here and here), so I’m not going to go into detail about what a projection is, but rather how it affects PowerShell. The object model of the projection makes it a little tricky to work with from PowerShell, but can be done.
Here’s what the incident view looks like in the console.
That’s my target – create a script that has output similar to the console.
The script is constructed into two sections:
- The BEGIN section makes sure that the assemblies we need are loaded and creates functions that the script will use.
- The code in lines 20 to 50 make sure Microsoft.EnterpriseManagement.Core.dll is loaded and that we have a successful connection to the Data Access Service.
- The Get-SMIncident function does the actual work of retrieving the incident from the Data Access Service. It’s a little tricky because the Service Manager 2010 SDK uses generics, so we need a bit of reflection to invoke the method. This function also creates the criteria that we use to retrieve the incident. This way we filter on the server side rather than the client side which will be much faster if we have any reasonable number of incidents.
- The Get-AdaptedEMO function converts the EnterpriseManagementObject to something more idiomatic for PowerShell. I’ve mentioned this before, but since the actual interesting information is actually in the Values property of the EnterpriseManagementObject, this function creates a PSCustom object to which we add NoteProperties. This will help us later when we start formatting.
- This is done by adapting the main object of the projection (the object property on the projection) and the EnterpriseManagementObjects that represent the various relationships which is in line 166 of the script. It takes advantage of the fact that a projection object has an enumerator. Line 166 retrieves the keys which are then used in the foreach loop in 167-174 to retrieve each related object of the projection. That object is then adapted for PowerShell use and added as a property to the PSCustom object which represents the incident.
- Lines 176-188 promote some of the properties of the main object (the incident’s Object property) as well as some of the related objects properties so we can create the formatting we want more easily.
- Finally, the adapted incident is output (line 190)
|
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 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
#requires -version 2.0
# Get-Incident # Retrieve Service Manager Incidnents # The IncidentString may need to include a trailing ‘%’ # examples: # Get-Incident IR17% # Retrieves incidents that have a displayname which starts with ‘IR17′ [CmdletBinding(SupportsShouldProcess=$true)] param ( [Parameter(Position=0)][string]$IncidentString = "%", [Parameter()][String]$ComputerName = "localhost", [Parameter()]$Credential ) # Set up the enviroment # Create the connection to the ManagementGroup and use a # Create some variables that we need for our script # this function retrieves incidents from the Server. It uses reflection to try # this function takes an instance of an EnterpriseManagementObject and } # END BEGIN END # Construct an adapted object for the main object of the incident # for each one of the component parts of the incident, add an # Add some members to the adapted incident |
When we run the script, we provide an incident ID to reduce the amount of data returned from the Data Access Service.
PS> ./get-incident IR17%
Object : @{LastModified=1/28/2010 1:30:24 AM; LastModifiedBy=7431e155-3d9e-4724-895e-c03ba951a352; Leas
tDerivedNonAbstractManagementPackClassId=a604b942-4c7b-2fb2-28dc-61dc6f465c68; TargetResolutio
nTime=; Escalated=False; Source=IncidentSourceEnum.Console; Status=IncidentStatusEnum.Active;
ResolutionDescription=; NeedsKnowledgeArticle=False; TierQueue=; HasCreatedKnowledgeArticle=Fa
lse; LastModifiedSource=IncidentSourceEnum.Console; Classification=IncidentClassificationEnum.
Hardware; ResolutionCategory=; Priority=9; Impact=System.WorkItem.TroubleTicket.ImpactEnum.Hig
h; Urgency=System.WorkItem.TroubleTicket.UrgencyEnum.High; ClosedDate=; ResolvedDate=; Id=IR17
; Title=my computer is brokeked; Description=it don't work; ContactMethod=; CreatedDate=1/18/2
010 10:58:23 PM; ScheduledStartDate=; ScheduledEndDate=; ActualStartDate=; ActualEndDate=; Dis
playName=IR17 - my computer is brokeked}
AppliesToTroubleTicket : {@{LastModified=1/28/2010 1:02:44 AM; LastModifiedBy=7431e155-3d9e-4724-895e-c03ba951a352; Lea
stDerivedNonAbstractManagementPackClassId=dbb6a632-0a7e-cef8-1fc9-405d5cd4d911; ActionType=Sys
tem.WorkItem.ActionLogEnum.RecordReopened; Title=Action log from 01/27/2010 17:02:44; Descript
setStatus=; Notes=; DisplayName=Domain Admninistrator}}
. . .
RequestedWorkItem : {@{Id=642feed0-7e9a-b516-81cc-7f94be6bce91; LastModified=11/18/2009 5:30:17 PM; LastModifiedBy
=8bb08d83-64f1-4230-a8c9-e022beae2819; LeastDerivedNonAbstractManagementPackClassId=eca3c52a-f
273-5cdc-f165-3eb95a2b26cf; Domain=WOODGROVE; UserName=blesh; DistinguishedName=CN=Bruce Lesh,
CN=Users,DC=woodgrove,DC=com; SID=S-1-5-21-2548544548-3952215810-4123597018-1129; FQDN=woodgro
ve.com; UPN=blesh@woodgrove.com; FirstName=Bruce; Initials=; LastName=Lesh; Company=; Departme
nt=; Office=; Title=; EmployeeId=; StreetAddress=; City=; State=; Zip=; Country=; BusinessPhon
e=; BusinessPhone2=; HomePhone=; HomePhone2=; Fax=; Mobile=; Pager=; ObjectStatus=System.Confi
gItem.ObjectStatusEnum.Active; AssetStatus=; Notes=; DisplayName=Bruce Lesh}}
Id : IR17
Title : my computer is brokeked
Description : it don't work
DisplayName : IR17 - my computer is brokeked
Priority : 9
CreatedDate : 1/18/2010 10:58:23 PM
LastModified : 1/28/2010 1:30:24 AM
Status : Active
AssignedTo : Domain Admninistrator
AffectedUser : Bruce Lesh
These results look pretty bad, it’s difficult to see what’s really important and what isn’t. However we can create a table view which can mimic the view that we see in the console. Here’s the formatting does it.
. . .
<View>
<Name>IncidentView</Name>
<ViewSelectedBy>
<TypeName>EnterpriseManagementObjectProjection#System.WorkItem.Incident.ProjectionType</TypeName>
</ViewSelectedBy>
<TableControl>
<AutoSize />
<TableHeaders>
<TableColumnHeader><Label>Id</Label></TableColumnHeader>
<TableColumnHeader><Label>Title</Label></TableColumnHeader>
<TableColumnHeader><Label>AssignedTo</Label></TableColumnHeader>
<TableColumnHeader><Label>Status</Label></TableColumnHeader>
<TableColumnHeader><Label>Priority</Label><Alignment>Right</Alignment></TableColumnHeader>
<TableColumnHeader><Label>AffectedUser</Label></TableColumnHeader>
<TableColumnHeader><Label>LastModified</Label></TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem><PropertyName>Id</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>Title</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>AssignedTo</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>Status</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>Priority</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>AffectedUser</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>LastModified</PropertyName></TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
. . .
update the format directives with update-formatdata, and voila!
PS> update-formatdata GetIncident.format.ps1xml PS> ./get-incident IR17% Id Title AssignedTo Status Priority AffectedUser LastModified -- ----- ---------- ------ -------- ------------ ------------ IR17 my computer is brokeked Domain Admninistrator Active 9 Bruce Lesh 1/28/2010 1:30:24 AM PS> ./get-incident IR% Id Title AssignedTo Status Priority AffectedUser LastModified -- ----- ---------- ------ -------- ------------ ------------ IR17 my computer is brokeked Domain Admninistrator Active 9 Bruce Lesh 1/28/2010 1:30:24 AM IR2 email is brokoken Domain Admninistrator Closed 9 Al Young 12/15/2009 11:41:57 PM IR13 more busted 2 Domain Admninistrator Closed 9 Carlos Garcia 1/28/2010 12:55:28 AM IR15 busted 3 Domain Admninistrator Closed 9 Greg Adams 1/5/2010 6:50:11 PM
that looks pretty good!
Batch Operations in Service Manager 2010 with PowerShell – Removing Instances 2 comments
Sometimes, when I am developing a demo for Service Manager, I wind up creating a lot of Service Requests or Incidents when I’m trying to get the demo just right. However, after I’ve gotten everything working just like I want and then I give the demo, I don’t really want to have all those earlier things visible because they get in the way of the what I’m trying to show. The Service Manager 2010 provides a way to removing instances from the console, and I could use that, but I like to script everything so I want to create a script instead of using the UI. With this script, I can use this to remove any instance in the CMDB, including Incidents and Service Requests.
Our programming interfaces provide a way to remove instances and I’ve written my script to work a couple of ways:
- If you provide the script with ClassName parameter, the script will remove every instance of that class!
- If you pipe an EnterpriseManagementObject at the script, the script will remove that instance
These are pretty big hammers, so I’ve made sure that you can use –WhatIf and I’ve also set ConfirmImpact as High which will ask for confirmation even if you don’t specify –confirm. My last warning is that you should not put this script anywhere near your production machines. It will remove the data forever, so be sure you are careful!!
I think the most interesting bit of the script is on line 21. This is where an IncrementalDiscoveryData object is created. The IncrementalDiscoveryData object allows you to deal with instances in bulk. I can use this object to remove instances then remove them all by the single call to Commit in line 73. The code between lines 29 and 35 represent the code that’s needed to call our generic methods, the script uses reflection to build the generic method and then call it.
The PROCESS block starting on line 50 handles the case when you pipe objects to the script. It first checks to be sure that it’s an EnterpriseManagementObject, and if so, adds the object to the IncrementalDiscoveryData collection which will be used in the END block. Rather than wrapping the call to Commit in another ShouldProcess block, I just check to be sure I have objects to remove. If there are, I make the Commit call. I don’t like it when my scripts ask me “Do you really want to do this” after I’ve already answered it once.
This script is a PowerShell version 2.0 script (as seen in line 1). This way I can take advantage of the ConfirmImpact and the other PowerShell 2.0 goodies.
|
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 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 |
#requires -version 2.0
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")] param ( [Parameter(Position=0)] $classname, [Parameter(ValueFromPipeline=$true)]$EMO ) BEGIN { # oh for a way to specify namespaces $NS = "Microsoft.EnterpriseManagement" if ( ! ("${NS}.Common.EnterpriseManagementObject" -as "type")) { [reflection.assembly]::LoadWithPartialName("${NS}.Core") } $LFX = "ConnectorFramework" $DEFAULT = ("${NS}.Common.ObjectQueryOptions" -as "type")::Default $EMOT = "${NS}.Common.EnterpriseManagementObject" -as "type" $EMG = new-object "${NS}.EnterpriseManagementGroup" localhost $IDD = new-object "${NS}.${LFX}.incrementaldiscoverydata" $guid = $EMG.ConnectorFramework.GetDefaultConnectorId().guid $SDKConnector = $EMG.ConnectorFramework.GetConnector($guid) $REMOVECOUNT = 0 # only go through this process if you got a classname and are # going to remove all instances of that class if ( $classname ) { $IMgmt = $EMG.EntityObjects.GetType() $class = $EMG.EntityTypes.GetClasses()|?{$_.name -eq $classname} [array]$arguments = ($class -as "${NS}.Configuration.ManagementPackClass"),$DEFAULT [type[]]$TYPES = ("${NS}.Configuration.ManagementPackClass" -as "type"), ("${NS}.Common.ObjectQueryOptions" -as "type") $ObjectReader = $IMgmt.getmethod("GetObjectReader",$TYPES) $GenericMethod = $ObjectReader.MakeGenericMethod($EMOT) if ( ! $class ) { throw "no class $classname" } # GET THE OBJECTS $SMObjects = $GenericMethod.invoke($EMG.EntityObjects,$arguments) if ( ! $SMObjects ) { "No objects to remove"; exit } $SMObjects|%{ if ( $PSCmdlet.ShouldProcess( $_.displayname ) ) { $REMOVECOUNT++ $IDD.Remove($_) } } } } PROCESS END <# |
Here’s an example of removing every Microsoft.Windows.Computer from Service Manager (I’m not actually going to do this, so I’m using –Whatif). If you need a reminder, Get-SmObject.ps1 was a blog posting here.
PS> ./get-smobject microsoft.windows.computer|./remove-smobject -whatif What if: Performing operation "remove-smobject.ps1" on Target "Computer028". What if: Performing operation "remove-smobject.ps1" on Target "Computer008". What if: Performing operation "remove-smobject.ps1" on Target "computer1.contoso.com". What if: Performing operation "remove-smobject.ps1" on Target "Computer027". What if: Performing operation "remove-smobject.ps1" on Target "Computer001". What if: Performing operation "remove-smobject.ps1" on Target "WIN-752HJBSX24M.woodgrove.com". What if: Performing operation "remove-smobject.ps1" on Target "Computer002". What if: Performing operation "remove-smobject.ps1" on Target "Computer024". What if: Performing operation "remove-smobject.ps1" on Target "Computer030". What if: Performing operation "remove-smobject.ps1" on Target "Computer007". What if: Performing operation "remove-smobject.ps1" on Target "Computer023". What if: Performing operation "remove-smobject.ps1" on Target "Computer025". What if: Performing operation "remove-smobject.ps1" on Target "Computer026". What if: Performing operation "remove-smobject.ps1" on Target "Computer003". What if: Performing operation "remove-smobject.ps1" on Target "Computer029".
If I want to remove one computer, I can just filter for what I want.
PS> ./get-smobject microsoft.windows.computer|?{$_.displayname -match "Computer028"}|
>> ./remove-smobject -whatif
What if: Performing operation "remove-smobject.ps1" on Target "Computer028".
This is what it will look like when you really remove it!
Computer028 is gone! Notice that this is really where PowerShell provides lots of value, the interaction to confirm the removal is done with the Cmdlet attribute in line 2 – ConfirmImpact=”High”, that plus the $PSCmdlet.ShouldProcess in lines 41 and 54 make it really easy to write scripts that won’t shoot me in the foot!


