In my last blog, I wrote about the Service Manager Type Environment, this time we’re going to work with what’s called the instance space. This “space” represents the actual data which is kept for your environment. When you get data from one of the connectors, create a change request or incident, an instance (or a set of instances) is created. In this post, we’ll investigate how we can take advantage of this from PowerShell.
FAREWELL TO SCRIPTING
This is also the time where we will bid adieu to a completely script based interaction model. This is due to a couple of reasons; First and foremost, when you are working with the instance space in Service Manager, you need to use the EntityObjects interface on the EnterpriseManagementGroup object. The methods that allow you to retrieve data from SM requires the use of generics, and PowerShell doesn’t have support in its syntax for direct method invocation which includes generics.
Let’s take a look:
PS> $emg.EntityObjects|gm getobjects|ft name,definition -au
Name Definition
---- ----------
GetObjects System.Collections.Generic.IList[T] GetObjects[T](Microsoft.Enterpr...
We can see that PowerShell tells us that the methods require a type – you can see the bit I highlighted in red above, PowerShell indicates the type by putting it in square brackets, but sadly, you can’t call the method in this way. There’s just no good way to call this method from within a PowerShell script, you have to write some C#. PowerShell V2 will let me embed C# in a script and create a type (see Get-PowerShell » Embedded C# for a good example) on the fly which I could use to paint over the generics bit, but I think for my purposes, creating a compiled C# cmdlet will be better overall.
I need to do the following:
- Create a connection to the Data Access Server (create my EnterpriseManagementGroup object)
- Call the appropriate method on the EntityObjects Interface
In looking at the available methods, I see a number that might be useful
PS> $emg = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup localhost
PS> $emg.EntityObjects|gm
TypeName: Microsoft.EnterpriseManagement.InstancesManagement
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetObject Method T GetObject[T](System.Guid id, Microsoft.EnterpriseManagement.Common....
GetObjectHistoryTransactions Method System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetObjectProjectionReader Method Microsoft.EnterpriseManagement.Common.IObjectProjectionReader[T] GetO...
GetObjectProjectionWithAccessRights Method Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectProje...
GetObjectReader Method Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReade...
GetObjects Method System.Collections.Generic.IList[T] GetObjects[T](Microsoft.Enterpris...
GetObjectWithAccessRights Method Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject GetO...
GetParentObjects Method System.Collections.Generic.IList[T] GetParentObjects[T](System.Guid i...
GetRelatedObjects Method System.Collections.Generic.Dictionary[System.Guid,System.Collections....
GetRelationshipObject Method Microsoft.EnterpriseManagement.Common.EnterpriseManagementRelationshi...
GetRelationshipObjects Method System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetRelationshipObjectsBySourceClass Method System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetRelationshipObjectsByTargetClass Method System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetRelationshipObjectsWhereSource Method System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetRelationshipObjectsWhereTarget Method System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetType Method type GetType()
RefreshGroupMembers Method System.Void RefreshGroupMembers(Microsoft.EnterpriseManagement.Config...
ToString Method string ToString()
I have a little filter that I use when I want to see the a method, it’s a little different than the definition property of get-member output, and for me a little clearer.
filter Get-Definition
{
param ( $MemberName )
$_.GetType().GetMembers()|
?{ $_.name -eq $MemberName -and $_.membertype -eq "method"}|
%{
($_.returntype.tostring() -replace "``1") + " " + $_.name + "[T] ("
$_.GetParameters() | %{
$s = @()
}{
$s += ,(" " + $_.parametertype + " " + $_.name)
}{
$s -join ",`n"
}
" )"
}
}
Here’s what the output looks like for GetObjectReader.
PS> $emg.EntityObjects | get-definition GetObjectReader
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectGenericCriteria criteria,
Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
)
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
Microsoft.EnterpriseManagement.Configuration.ManagementPackClass managementPackClass,
Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
)
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectCriteria criteria,
Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
)
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
System.Collections.Generic.ICollection[Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectCri
teria] criteriaCollection,
Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
)
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectGenericCriteria criteria,
Microsoft.EnterpriseManagement.Configuration.ManagementPackClass mpClass,
Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
)
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
System.Collections.Generic.ICollection[System.Guid] ids,
Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
)
This is the method that will do what I need, it will return a reader (which I can use to turn the results into a collection) and there is one overload that takes a management pack class and some options. This will return all the instances of a particular class, based on a set of passed options, which in my case will be our default options (return everything).
In my case, all I need now is a Management Pack Class (which means I can use my earlier Get-SMClass script). In my case I just need to wrap the call to GetObjectReader in a fairly simple cmdlet and viola!
01 using System;
02 using System.Management.Automation;
03 using Microsoft.EnterpriseManagement;
04 using Microsoft.EnterpriseManagement.Common;
05 using Microsoft.EnterpriseManagement.Configuration;
06 namespace ServiceManager.Powershell.Demo
07 {
08 [Cmdlet("Get","SCSMObject")]
09 public class GetSMObjectCommand : PSCmdlet
10 {
11 private ManagementPackClass _class = null;
12 [Parameter(ParameterSetName="Class",Position=0,Mandatory=true,ValueFromPipeline=true)]
13 public ManagementPackClass Class
14 {
15 get { return _class; }
16 set { _class = value; }
17 }
18 private EnterpriseManagementGroup _mg;
19 protected override void BeginProcessing()
20 {
21 _mg = new EnterpriseManagementGroup("localhost");
22 }
23 protected override void ProcessRecord()
24 {
25 foreach(EnterpriseManagementObject o in
26 _mg.EntityObjects.GetObjectReader<EnterpriseManagementObject>(Class,ObjectQueryOptions.Default)
27 )
28 {
29 WriteObject(o);
30 }
31 }
32 }
33 }
A couple of things to note here:
- I’m assuming that I’m running on the server, in line 21 where I create the EnterpriseManagementGroup object, I use “localhost” as the hostname.
- I set the ValueFromPipeline attributed on the ManagementPackClass parameter to true, which will allow me to pipe the result of Get-SMClass to Get-SCSMObject (line 12).
- I use GetObjectReader and emit each of the objects explicitly (line 29)
Woo! Let’s give it a go!
PS> get-smclass windows.computer$|get-scsmobject
PropertyAccessRights : Unknown
Parent : {WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M, WOODGROVE...}
Type : PrincipalName
Value : WIN-752HJBSX24M.woodgrove.com
Id : 00000000-0000-0000-0000-000000000000
ManagementGroup : ServiceManagerMgmtGroup1
ManagementGroupId : 8884fff9-00d4-abcc-6620-e6d25f39e0d6
...
PropertyAccessRights : Unknown
Parent : {WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M, WOODGROVE...}
Type : DisplayName
Value : WIN-752HJBSX24M.woodgrove.com
Id : 00000000-0000-0000-0000-000000000000
ManagementGroup : ServiceManagerMgmtGroup1
ManagementGroupId : 8884fff9-00d4-abcc-6620-e6d25f39e0d6
This doesn’t look quite like what I wanted – what I want is a table of the properties of the windows.computer class. Let’s investigate:
PS> $o = get-smclass windows.computer$|get-scsmobject
PS> $o|gm
TypeName: Microsoft.EnterpriseManagement.Common.EnterpriseManagementSimpleObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
...
ManagementGroup Property Microsoft.EnterpriseManagement.EnterpriseManagementGroup ManagementGroup {get;}
ManagementGroupId Property System.Guid ManagementGroupId {get;}
Parent Property Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectBaseWithProperties P...
PropertyAccessRights Property Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectAccessRights Propert...
Type Property Microsoft.EnterpriseManagement.Configuration.ManagementPackProperty Type {get;}
Value Property System.Object Value {get;set;}
This isn’t certainly isn’t what I wanted, I should be getting an EnterpriseManagementObject not an EnterpriseManagementSimpleObject, perhaps piping the input is a problem.
PS> gm -input $o
TypeName: Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject
Name MemberType Definition
---- ---------- ----------
ApplyTemplate Method System.Void ApplyTemplate(Microsoft.EnterpriseMan...
Commit Method System.Void Commit(Microsoft.EnterpriseManagement...
ContainsProperty Method bool ContainsProperty(Microsoft.EnterpriseManagem...
CreateNavigator Method System.Xml.XPath.XPathNavigator CreateNavigator()
Equals Method bool Equals(System.Object obj)
GetClasses Method System.Collections.Generic.IList[Microsoft.Enterp...
GetEnumerator Method System.Collections.Generic.IEnumerator[Microsoft....
...
FullName Property System.String FullName {get;}
Id Property System.Guid Id {get;}
LastModified Property System.DateTime LastModified {get;}
LastModifiedBy Property System.Nullable`1[[System.Guid, mscorlib, Version...
LeastDerivedNonAbstractManagementPackClassId Property System.Guid LeastDerivedNonAbstractManagementPack...
ManagementGroup Property Microsoft.EnterpriseManagement.EnterpriseManageme...
ManagementGroupId Property System.Guid ManagementGroupId {get;}
...
ah – there’s the culprit – EnterpriseManagementObject has a GetEnumerator method, which will get called when the formatter gets invoked (or when it hits the pipeline). We can keep change our formatting a bit to see the information differently:
PS> fl -input $o -expand coreonly
Name : WIN-752HJBSX24M.woodgrove.com
Path :
DisplayName : WIN-752HJBSX24M.woodgrove.com
FullName : Microsoft.Windows.Computer:WIN-752HJBSX24M.woodgrove.com
ManagementPackClassIds : {7b070bc5-0f54-6663-f840-17affa1d6304, a754daf6-0e0e-9fd3-beef-9da66b39e
41f, 139a4e2d-47fe-ac19-02c4-a31210c457cc, 9f178faf-54f4-b13b-6b32-a684d
c6d3ec1...}
LeastDerivedNonAbstractManagementPackClassId : 7b070bc5-0f54-6663-f840-17affa1d6304
TimeAdded : 7/1/2009 11:23:41 PM
LastModifiedBy : 7431e155-3d9e-4724-895e-c03ba951a352
LastModified : 7/2/2009 9:50:34 PM
Id : e4011629-ae31-a8a6-9bee-99980c68ba69
ManagementGroup : ServiceManagerMgmtGroup1
ManagementGroupId : 8884fff9-00d4-abcc-6620-e6d25f39e0d6
Well, this is better (at least I’m getting a single list rather than multiple objects), but it’s not still not quite right. What’s I really want is the values of the various properties of object from the Service Manager perspective. This information is kept in the collection of properties (available via the GetProperties method). This is what I was seeing at first, just in a format I wasn’t expecting. Let’s try a different approach, I noticed that when I saw the data flashing by the first time, it looked like a couple of properties were interesting. If I select the Type and Value properties of the EnterpriseSimpleObject, that may be what I want.
PS> get-smclass windows.computer$|get-scsmobject|format-table Type,Value -au
Type Value
---- -----
PrincipalName WIN-752HJBSX24M.woodgrove.com
DNSName WIN-752HJBSX24M.woodgrove.com
NetbiosComputerName WIN-752HJBSX24M
NetbiosDomainName WOODGROVE
IPAddress
NetworkName WIN-752HJBSX24M.woodgrove.com
ActiveDirectoryObjectSid
IsVirtualMachine True
DomainDnsName
OrganizationalUnit
ForestDnsName
ActiveDirectorySite
LogicalProcessors
OffsetInMinuteFromGreenwichTime
LastInventoryDate
InstallDirectory
IsVirtualNode
ObjectStatus
AssetStatus
Notes
DisplayName WIN-752HJBSX24M.woodgrove.com
OK! this is more along the lines that I was thinking. It’s the representation of the information about the computer that I was expecting. However, having to do this formatting is a pain, and it’s not possible for me to refer to a windows.computer objects properties the way I want, so what I will do is build a simple-minded data adapter, using PowerShell’s capability for building objects on the fly.
1 using System;
2 using System.Management.Automation;
3 using Microsoft.EnterpriseManagement;
4 using Microsoft.EnterpriseManagement.Common;
5 using Microsoft.EnterpriseManagement.Configuration;
6 namespace ServiceManager.Powershell.Demo
7 {
8 [Cmdlet("Get","SCSMObject")]
9 public class GetSMObjectCommand : PSCmdlet
10 {
11 private ManagementPackClass _class = null;
12 [Parameter(ParameterSetName="Class", Position=0,Mandatory=true,ValueFromPipeline=true)]
13 public ManagementPackClass Class
14 {
15 get { return _class; }
16 set { _class = value; }
17 }
18 private EnterpriseManagementGroup _mg;
19 protected override void BeginProcessing()
20 {
21 _mg = new EnterpriseManagementGroup("localhost");
22 }
23 protected override void ProcessRecord()
24 {
25 foreach(EnterpriseManagementObject o in
26 _mg.EntityObjects.GetObjectReader(Class,ObjectQueryOptions.Default)
27 )
28 {
29 PSObject pso = new PSObject();
30 pso.Properties.Add(new PSNoteProperty("__base",o));
31 pso.TypeNames.Insert(0, "EnterpriseManagementObject#"+Class.Name);
32 foreach ( ManagementPackProperty p in o.GetProperties())
33 {
34 pso.Properties.Add(new PSNoteProperty(p.Name, o[p].Value));
35 }
36 WriteObject(pso);
37 }
38 }
39 }
40 }
So, it’s a fairly simple change, an additional few lines of code (lines 29-36) and it should be what we want. Just a couple of things to note:
- I save the entire original object in a NoteProperty called “__base”
- I add a new string to the TypeNames collection, this way I can create formatting based on the Service Manager class. This is similar to what the PowerShell team did for WMI.
- For each one of the Service Manager properties in the class, I created a NoteProperty (lines 32-35)
Let’s see what we have now!
PS> get-smclass windows.computer$|get-scsmobject
__base : {WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M, WOODG
ROVE...}
PrincipalName : WIN-752HJBSX24M.woodgrove.com
DNSName : WIN-752HJBSX24M.woodgrove.com
NetbiosComputerName : WIN-752HJBSX24M
NetbiosDomainName : WOODGROVE
IPAddress :
NetworkName : WIN-752HJBSX24M.woodgrove.com
ActiveDirectoryObjectSid :
IsVirtualMachine : True
DomainDnsName :
OrganizationalUnit :
ForestDnsName :
ActiveDirectorySite :
LogicalProcessors :
OffsetInMinuteFromGreenwichTime :
LastInventoryDate :
InstallDirectory :
IsVirtualNode :
ObjectStatus :
AssetStatus :
Notes :
DisplayName : WIN-752HJBSX24M.woodgrove.com
Ah! This is much better! Now I’m getting the interesting properties of the instance. Let’s try get-member
PS> get-smclass windows.computer$|get-scsmobject|gm
TypeName: EnterpriseManagementObject#Microsoft.Windows.Computer
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
ActiveDirectoryObjectSid NoteProperty ActiveDirectoryObjectSid=null
ActiveDirectorySite NoteProperty ActiveDirectorySite=null
AssetStatus NoteProperty AssetStatus=null
DisplayName NoteProperty System.String DisplayName=WIN-752HJBSX24M.woodgrove.com
DNSName NoteProperty System.String DNSName=WIN-752HJBSX24M.woodgrove.com
DomainDnsName NoteProperty DomainDnsName=null
ForestDnsName NoteProperty ForestDnsName=null
InstallDirectory NoteProperty InstallDirectory=null
IPAddress NoteProperty IPAddress=null
IsVirtualMachine NoteProperty System.Boolean IsVirtualMachine=True
IsVirtualNode NoteProperty IsVirtualNode=null
LastInventoryDate NoteProperty LastInventoryDate=null
LogicalProcessors NoteProperty LogicalProcessors=null
NetbiosComputerName NoteProperty System.String NetbiosComputerName=WIN-752HJBSX24M
NetbiosDomainName NoteProperty System.String NetbiosDomainName=WOODGROVE
NetworkName NoteProperty System.String NetworkName=WIN-752HJBSX24M.woodgrove.com
Notes NoteProperty Notes=null
ObjectStatus NoteProperty ObjectStatus=null
OffsetInMinuteFromGreenwichTime NoteProperty OffsetInMinuteFromGreenwichTime=null
OrganizationalUnit NoteProperty OrganizationalUnit=null
PrincipalName NoteProperty System.String PrincipalName=WIN-752HJBSX24M.woodgrove.com
__base NoteProperty Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject __base...
And finally, let’s take a look at this as a table.
PS> get-smclass windows.computer$|get-smobject|ft Type,DisplayName
Type DisplayName
---- -----------
Microsoft.Windows.Computer Client4.woodgrove.com
Microsoft.Windows.Computer Client5.woodgrove.com
Microsoft.Windows.Computer Client3.woodgrove.com
Microsoft.Windows.Computer Server1.woodgrove.com
Microsoft.Windows.Computer Server3.woodgrove.com
Microsoft.Windows.Computer Server2.woodgrove.com
Microsoft.Windows.Computer WIN-752HJBSX24M.woodgrove.com
Microsoft.Windows.Computer Client1.woodgrove.com
Microsoft.Windows.Computer Client2.woodgrove.com
This is a great start, and we’ll use this as a starting point for the future! I’ve put all the module files in my Sky Drive, from here on I’ll be adding features to the module.