James w's profileTime is an illusion. Lun...PhotosBlogListsMore ![]() | Help |
|
10/22/2009 Batch Operations in Service Manager 2010 with PowerShell – Removing InstancesSometimes, 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:
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.
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. 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! 10/2/2009 Service Manager and the PowerShell 1 linerI’ve written a number of fairly complicated scripts for Service Manager over the last few months, and while talking to a team-mate about something he wanted to do, it looked like it was just 1-line PowerShell script. That got me thinking about what other things in Service Manager could be handled by a really simple (say less than 5 lines) of PowerShell. The list is good sized, so I thought it would be good if I shared them. The problem we had at hand was how I could help one our development partners figure out in which management pack a particular class resides. It turns out it was 3 lines of script to find out.
the business end of the script is just the last line. The first two lines are just what I need to get access to the Service Manager Data Access Service. I do this so much that put those two lines in my $profile. After that, it’s just a matter of adding a filter to find out the actual class of interest. PS> $EMG.EntityTypes.GetClasses()|?{$_.name -match "System.Knowledge.Article"}|ft name,{$_.GetManagementPack().Name}
Name $_.GetManagementPack().Name
---- ---------------------------
System.Knowledge.Article System.Knowledge.LibraryNow, the label for the second column may not pretty, but I’m not fussed about that, I’ve got the data that I need. The next thing I needed to figure out was the properties of this class. That’s another one liner: PS> $emg.entitytypes.GetClasses()|?{$_.name -match "System.Knowledge.Article"}|
>> select -expand propertycollection|ft name,key,type -au
>>
Name Key Type
---- --- ----
ArticleType False enum
ArticleTemplate False string
ArticleOwner False string
Category False enum
Comments False string
CreatedDate False datetime
CreatedBy False string
PrimaryLocaleID False int
Status False enum
Tag False enum
VendorArticleID False string
Title False string
Abstract False string
Keywords False string
ArticleId True string
EndUserContent False binary
AnalystContent False binary
ExternalURLSource False string
ExternalURL False string(ok, so it’s a long line, but it’s still a single pipeline) This doesn’t quite tell the whole story, because if I wanted to create one of these classes, I may have more properties available to me (based on the base classes for the class I want). That’s just *2* lines: PS> $class = $emg.entitytypes.GetClasses()|?{$_.name -match "System.Knowledge.Article"}
PS> (new-object microsoft.enterprisemanagement.common.CreatableEnterpriseManagementObject $emg,$class).GetProperties()|
>> ft name,key,type -au
>>
Name Key Type
---- --- ----
ArticleType False enum
ArticleTemplate False string
ArticleOwner False string
Category False enum
Comments False string
CreatedDate False datetime
CreatedBy False string
PrimaryLocaleID False int
Status False enum
Tag False enum
VendorArticleID False string
Title False string
Abstract False string
Keywords False string
ArticleId True string
EndUserContent False binary
AnalystContent False binary
ExternalURLSource False string
ExternalURL False string
ObjectStatus False enum
AssetStatus False enum
Notes False richtext
DisplayName False stringI save the class and then use it to create the object I want with new-object. Sometimes, I need to know which management pack an enumeration is in. Another 1 liner: PS> $emg.EntityTypes.GetEnumerations()|?{$_.name -match "high"}|ft name,{$_.getmanagementpack().name} -au
Name $_.getmanagementpack().name
---- ---------------------------
System.WorkItem.TroubleTicket.ImpactEnum.High System.WorkItem.Library
System.WorkItem.TroubleTicket.UrgencyEnum.High System.WorkItem.Library
System.ServiceManagement.ServicePriority.High ServiceManager.ServiceMaps.Configuration
IncidentResolutionCategoryEnum.FixedByHigherTierSupport ServiceManager.IncidentManagement.Configuration
ChangePriorityEnum.High ServiceManager.ChangeManagement.Configuration
ChangeRiskEnum.High ServiceManager.ChangeManagement.Configuration
ActivityPriorityEnum.High ServiceManager.ActivityManagement.ConfigurationOne of my early examples for retrieving management packs. That’s a 1 liner: PS> $emg.ManagementPacks.GetManagementPacks()|ft Sealed,Version,Name Sealed Version Name ------ ------- ---- False 7.0.5228.0 Microsoft.SystemCenter.ServiceManager.Connector.Configuration True 7.0.5228.0 Microsoft.SystemCenter.Internal True 7.0.5228.0 ServiceManager.Reporting.Help True 7.0.5228.0 Microsoft.SystemCenter.Report.Library True 7.0.5228.0 ServiceManager.LinkingFramework.Library True 7.0.5228.0 System.ApplicationLog.Library True 7.0.5228.0 ServiceManager.IncidentManagement.Library.Datawarehouse True 7.0.5228.0 Microsoft.EnterpriseManagement.ServiceManager.UI.Console True 7.0.5228.0 ServiceManager.ActivityManagement.Library.Datawarehouse True 7.0.5228.0 ServiceManager.ChangeManagement.Library True 7.0.5228.0 ServiceManager.IncidentManagement.Report.Library True 7.0.5228.0 ServiceManager.ChangeManagement.Report.Library . . . What if I wanted to remove a management pack? Before I do, I had better find out whether it’s possible, as if other Management Packs depend on the one I want to remove. So I need to find the dependent management packs - 2 lines! PS> $crLib = $emg.ManagementPacks.GetManagementPacks()|?{$_.name -eq "System.WorkItem.ChangeRequest.Library"}
PS> $emg.ManagementPacks.GetDependentManagementPacks($crLib)|ft sealed,version,name -au
Sealed Version Name
------ ------- ----
True 7.0.5228.0 ServiceManager.ActivityManagement.Library.Datawarehouse
True 7.0.5228.0 ServiceManager.ChangeManagement.Library
True 7.0.5228.0 ServiceManager.ChangeManagement.Report.Library
True 7.0.5228.0 ServiceManager.ServiceMaps.Library
True 7.0.5228.0 ServiceManager.ChangeManagement.Library.Datawarehouse
False 7.0.5228.0 ServiceManager.ConfigurationManagement.Configuration
True 7.0.5228.0 ServiceManager.ChangeManagement.Help
True 7.0.5228.0 Microsoft.SystemCenter.ServiceManager.Portal
False 7.0.5228.0 ServiceManager.ChangeManagement.ConfigurationIn this case, I won’t be able to remove the ChangeRequest Library, because of all the dependencies, but if I have a management pack that is not needed by other management packs, removing the management pack is another one-liner: PS> $emg.ManagementPacks.GetManagementPacks()|?{$_.name -eq "MPToRemove"}|
>> %{ $emg.ManagementPacks.UninstallManagementPack($_) }
>>Perhaps I want to find out how much localization I need to do. To understand how much work I will need to do, I should find out how many lines of text I need to localize. How do I find out how many different English display strings are stored in my management packs? 1 line! PS> ($emg.LanguagePacks.getlanguagepacks()|?{$_.name -eq "ENU"}|select-object -Expand DisplayStringCollection).count
6456and if I wanted to know the count of my display strings for each language? 1 line! PS> $emg.LanguagePacks.getlanguagepacks()|select-object -ExpandProperty DisplayStringCollection | >> Group-Object LanguageCode|format-table Count,Name -au >> Count Name ----- ---- 6456 ENU 6455 DEU 6282 JPN and what if I wanted to see the actual English display strings from the System.Library management pack? Just another line! yow! 9/3/2009 Introducing Management Pack BundlesHistorically, management packs have been comprised of a single XML file (either in XML format in a .xml file or in a binary representation in a signed .mp file). With the new version of the common System Center management pack infrastructure that ships in Service Manager, the definition of a management pack is being extended to include associated “resources” such as images, form assemblies, workflow assemblies, reports, T-SQL scripts, and more. The aggregation of the XML (or even multiple XMLs) plus its associated resources is called a “management pack bundle”. A “management pack bundle” is really a MSI file with a file extension of .mpb (I should tell you that these .msi’s aren’t installable, we’re just using MSI as a file format). These bundles can be imported into Service Manager as a whole through a new MP import interface on the Data Access Service. You can import .mpb files via either the Management Packs view in the Administration workspace in the Service Manager console or using the Import-SCSMManagementPack PowerShell cmdlet (available in Beta 2). After import, the resources are automatically distributed to the appropriate places. In this post I’ll explain how to to aggregate your assemblies and images and multiple mps into a single file, using the BundleFactory in the Microsoft.EnterpriseManagement.Packaging assembly. This factory will let you create .MPB files, which can include resources needed by the management pack. I’ve written a PowerShell script to make this easier for you. You can either just use the script attached to this blog post to create management pack bundles or continue on to learn more about how to use the BundleFactory APIs to create management pack bundles. The script inspects the management pack defined in the ‘Resources” section of the management pack XML and retrieves the resources. Here’s what this section looks like in the MP I’m using as an example: <Resources> <Image ID="SmileyImage" Accessibility="Public" FileName="Smiley.png" HasNullStream="false" /> </Resources> The script is one of the more complicated scripts that I’ve done in this blog at about 140 lines so we’ll go through it in sections. Lines 10 through 19 declare some “constants” which I’ll use in the rest of the script. Lines 22 and 23 load the needed assemblies. Since we install the assemblies into the GAC on the management server or a computer that has the Service Manager console installed on it, I can use the static LoadWithPartialName method on Reflection.Assembly to load the assemblies we need if this script is run where the assemblies are installed. Since the LoadWithPartialName method returns the assembly, this is saved away so I can use it in lines 24 through 27 to retrieve the types I need later. I’ve done this to avoid the requirement of loading the needed assemblies before running the script. This means that the script has fewer preconditions. Lines 29 through 78 have function declarations. I declare two functions; the first function (Invoke-GenericMethod) allows me to invoke a generic method, which is how the resources from the management pack are retrieved. It’s a pretty tricky function which uses reflection to invoke the methods in Service Manager which use Generics. The second function, “Get-Resources” retrieves the resources and emits a stream of hash tables which contain the stream and the name of the resource. I need this information when I actually associate the resource with the management pack in the .mpb file. Lines 80 through 103 collect the management packs into an array. This script allows you to create a .mpb file with more than a single management pack. Line 99 has a check to be sure that I actually got some files in my array, if not, the script exits.
Line 107 is where we connect to the Service Manager Data Access Service. This used when the management pack objects are created in line 117. Line 110 is where we finally create our bundle object which we use to aggregate all the file. Since we’re going to be creating a number of resources, Line 112 declares an array which we’ll use to keep all the resources so we can clean up in the end. The foreach loop in lines 113 to 131 is where the work really takes place:
We’re not done yet. We’ve created our bundle, but we need to write it, so line 135 creates a BundleWriter object with the BundleFactory and then line 137 writes the .mpb file. Finally, we have a bit of clean up, so if there were any resources, we will close the stream and then dispose. Strictly speaking, this is probably not needed because when the script exits, the variables go out of scope and are then cleaned up eventually by the garbage collector, but it doesn’t hurt to be tidy. The following is an example of using the script. It creates a new .mpb file based on an MP (ResourceExample.xml) which has a single resource (an image file) and some instructions to create a new folder with the image. The MP (as an XML file) and the image file are in my sky drive if you want to use them to try it out. PS> new-mpbfile .\ResourceExample.xml resourceexample VERBOSE: Adding MP: ResourceExample VERBOSE: Adding stream: SmileyImage VERBOSE: wrote mpb: C:\Program Files\System Center Management Packs\resourceexample.mpb Here’s what it looks like in the Service Manager Console after I import the .mpb (using the Import-SCSMManagementPack cmdlet that is available in Beta2). awesome! Now that we can create a .mpb file, it sure would be nice if we could inspect one. The following script does that very thing. It takes as a .mpb file and returns the management packs and resources found in it. This requires PowerShell V2 because of the way I’m using new-object which takes advantage of new features. Instead of using a BundleWriter, I create a BundleReader to retrieve the management packs (line 18 through 20) and for each management pack (line 22), get the associated streams (line 26)
Here’s what it looks like when we use it. First on the MPB we just created: PS> get-mpbinfo resourceexample.mpb|ft -group managementpack length,resourcename -au ManagementPack: ResourceExample (Not Sealed) Length ResourceName ------ ------------ 861 SmileyImage Since we ship some .mpb files, I can use the script to inspect our product files: PS> get-mpbinfo ConfigManagementPack.mpb|ft -gro managementpack length,resourcename -au ManagementPack: ServiceManager.ConfigurationManagement.Library (Sealed) Length ResourceName ------ ------------ 100224 ConfigurationManagementFormsAssembly 55152 JA.ConfigurationManagementFormResourcesAssembly 55152 EN.ConfigurationManagementFormResourcesAssembly 55152 DE.ConfigurationManagementFormResourcesAssembly 96112 ServiceManager.ConfigurationManagement.Library.Assembly.Form 46960 EN.ServiceManager.ConfigurationManagement.Library.Assembly.FormResource 42864 JA.ServiceManager.ConfigurationManagement.Library.Assembly.FormResource 42864 DE.ServiceManager.ConfigurationManagement.Library.Assembly.FormResource 38784 ServiceManager.ConfigurationManagement.Library.Assembly.Task 11136 EN.ServiceManager.ConfigurationManagement.Library.Assembly.TaskResource 10608 JA.ServiceManager.ConfigurationManagement.Library.Assembly.TaskResource 10608 DE.ServiceManager.ConfigurationManagement.Library.Assembly.TaskResource 1399 ConfigItemImage32x32 712 ConfigItemImage16x16 492 ServiceManager.ConfigItem.Image.Edit 922 ServiceManager.ConfigurationManagement.Library.Image.User 3320 ServiceManager.ConfigurationManagement.Library.Image.DeletedItem ManagementPack: ServiceManager.ConfigurationManagement.Configuration (Not Sealed) Length ResourceName ------ ------------ 712 ComputerImage16x16 815 SoftwareImage16x16 800 PrinterImage16x16 1073 SoftwareUpdateImage16x16
Thanks to Lee Holmes and his “Set-ClipboardScript” script which provided the formatting of the code samples! 8/17/2009 Getting data from Service Manager – a scripted approachIn one of my earlier posts, I said that you needed some C# to get data from Service Manager because of the way some of our methods use generics. It was pointed out to me that I was wrong, wrong, wrong. So I thought I better post a completely scripted approach for retrieving data from Service Manager. The following script will return all instances of the class that’s passed as a parameter. 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
}
It uses reflection to retrieve the method that I want and then uses that to create a generic method, which can then be invoked with the parameters that I want. In this case, it’s fairly straightforward, since I want to retrieve all instances of a particular class, I use the overload of GetObjectReader which takes a ManagementPackClass and then provide a default ObjectQueryOptions. The last thing of interest is how I make the object more useful. First by using each one of the Values property on EnterpriseManagementObject and creating a note property, it lets me see the “real” properties of the object (the ones on the Service Manager class). By adding the name of the class to the TypeNames collection of the psobject, I can then use that with a formatting .ps1xml file so I can customize the output by the Service Manager class. PS> get-smobject.ps1 microsoft.windows.computer|ft DisplayName,LastModified -au DisplayName LastModified ----------- ------------ Computer2.woodgrove.com 8/14/2009 10:48:24 PM Computer5.woodgrove.com 8/14/2009 10:48:24 PM WIN-752HJBSX24M.woodgrove.com 8/13/2009 8:09:02 PM Computer1.woodgrove.com 8/14/2009 10:48:24 PM Computer4.woodgrove.com 8/14/2009 10:48:24 PM Computer3.woodgrove.com 8/14/2009 10:48:24 PM 8/14/2009 Creating Data in Service ManagerIn my last post, we saw how we were able to retrieve data from Service Manager, where we also said farewell to scripting. In this post, I’ll quickly go through how to create instance data in Service Manager. It was a short farewell to scripting, because unlike the last post were we needed some C# to do what we wanted, we can create most objects in the Service Manager CMDB directly from script. In order to create instances in Service Manager, we need to use CreatableEnterpriseManagementObject. The constructor for this object takes a reference to the EnterpriseManagementGroup and a ManagementPackClass. After this, it’s simply a matter of assigning values to various properties of the object. Here’s a script that creates 5 instances of Microsoft.Windows.Computer and sets a number of the property values.
this should add 5 computers to our system, (computer1 to computer5). We can check this with our previous cmdlets: PS> get-smclass microsoft.windows.computer$|get-scsmobject | ft DisplayName DisplayName ----------- Computer2.woodgrove.com Computer5.woodgrove.com WIN-752HJBSX24M.woodgrove.com Computer1.woodgrove.com Computer4.woodgrove.com Computer3.woodgrove.com Woo hoo! There are my new instances! However, it’s not quite as simple as this. Some properties are required and we have to be sure that we provide values for those properties. So, how can we find out what they are? These properties are designated as key properties. We can determine what these key properties are, by inspecting the properties of the newly created object. So, I can see that the only the PrincipalName is required to create an instance, and the type of value that I must supply is a string. In my next post, I’ll look more closely at how to provide values for things other than strings. 7/28/2009 Getting system uptimeSometimes I miss my Unix system. Actually, that’s not true, sometimes I miss the tools that are available. For example, I needed to figure out when the system booted. Thankfully, there are performance counters that can tell me! PS# cat get-uptime.ps1 $PCounter = "System.Diagnostics.PerformanceCounter" $counter = new-object $PCounter System,"System Up Time" $value = $counter.NextValue() $uptime = [System.TimeSpan]::FromSeconds($counter.NextValue()) "Uptime: $uptime" "System Boot: " + ((get-date) - $uptime) Simple! 7/27/2009 PowerShell and the Service Manager Instance SpaceIn 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 I need to do the following:
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! A couple of things to note here:
Woo! Let’s give it a go! 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:
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: 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:
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.comAh! 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. 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. 6/22/2009 PowerShell and the Service Manager Type EnvironmentService Manager, like Operations Manager, has a dynamic type system. ManagementPacks define types, called classes, which define the actual data that you want to keep track of. You declare a class which may or may not be based on another class and you can add properties to your class, and manipulate it in a number of ways. Once you've imported your management pack, you'll have access to that new class, and can create instances of that class. There are two discrete categorizations of types. The first is a simple class, and the more complex is called a type projection. In this blog, we'll discuss how to find out what Service Manager stores as simple classes and how to discover them.
To get access to the list of classes that have been imported to the Service Manager environment, we’ll use the EnterpriseManagementGroup object again (see my previous posts). The EnterpriseManagementGroup object has an interface named "EntityTypes", which has a number of methods that we'll investigate. First, we'll create an EnterpriseManagementGroup, and take a look at the EntityTypes interface. PS> $EMG = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup localhost PS> $EMG.EntityTypes|get-member TypeName: Microsoft.EnterpriseManagement.EntityTypeManagement Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetCategories Method System.Collections.Generic.IList[Micro... GetCategory Method Microsoft.EnterpriseManagement.Configu... GetCategoryList Method System.Collections.Generic.List[Micros... GetChildEnumerations Method System.Collections.Generic.IList[Micro... GetClass Method Microsoft.EnterpriseManagement.Configu... GetClasses Method System.Collections.Generic.IList[Micro... GetClassesList Method System.Collections.Generic.List[Micros... GetEnumeration Method Microsoft.EnterpriseManagement.Configu... GetEnumerationList Method System.Collections.Generic.List[Micros... GetEnumerations Method System.Collections.Generic.IList[Micro... GetHashCode Method int GetHashCode() GetRelationshipClass Method Microsoft.EnterpriseManagement.Configu... GetRelationshipClasses Method System.Collections.Generic.IList[Micro... GetRelationshipClassesList Method System.Collections.Generic.List[Micros... GetTopLevelEnumerations Method System.Collections.Generic.IList[Micro... GetType Method type GetType() GetTypeProjection Method Microsoft.EnterpriseManagement.Configu... GetTypeProjectionList Method System.Collections.Generic.List[Micros... GetTypeProjections Method System.Collections.Generic.IList[Micro... ToString Method string ToString() The methods break up in two big groups: The first group returns all various different objects that you can define in a management pack (those objects which have to do with types, anyway). That list is: GetCategories GetClasses GetEnumerations GetRelationshipClasses GetTopLevelEnumerations GetTypeProjections The other methods on this interface take arguments and return a reduced set of results based on the arguments. It is important to note that the Service Manager environment caches most of this configuration information, so when we get all of them, it doesn't result in lots of trips to the database. That's good news from a performance perspective. From a PowerShell perspective, it means that we can grab all the data and use the PowerShell filtering capabilities rather than bothering with the other available methods. If we want to see all the different classes that Service Manager knows about, we can do that easily: PS> $EMG.EntityTypes.GetClasses()
PropertyCollection : {DisplayName}
Base :
Hosted : False
Singleton : False
Extension : False
OptimizationCollection : {}
FullTextSearchable :
XmlTag : ClassType
Abstract : True
Accessibility : Public
ManagementGroup : WIN-752HJBSX24M
ManagementGroupId : 386f5f57-9f7a-3c6b-1f53-ccc02d6206d4
Name : System.Entity
Id : ac5cddfc-a96a-ee99-745d-ec74845f53f6
DisplayName : Object
Description : All objects
LanguageCode : ENU
Comment :
Status : Unchanged
LastModified : 5/14/2009 11:56:48 PM
TimeAdded : 5/14/2009 11:56:48 PM
PropertyCollection : {}
Base : ManagementPackElementUniqueIdentifier=ac5cddfc-a96a-ee
99-745d-ec74845f53f6
Hosted : False
Singleton : False
Extension : False
. . .Wow! There's a lot here - let's find out how many: PS> $Classes = $EMG.EntityTypes.GetClasses() We should determine the .NET type as well, as we can use this when we create our formatting instructions PS> $classes[0].gettype().fullname Microsoft.EnterpriseManagement.Configuration.ManagementPackClass With a result as large as this, it makes more sense to see as a table, rather than a list and I’ll select the first 5, just to cut down on space, so now we have: PS> $Classes|select-object -first 5| format-table Abstract,Name,DisplayName -au
Abstract Name DisplayName
-------- ---- -----------
True System.Entity Object
True System.AdminItem Admin Item
False System.Announcement.Config Config
False System.Announcement.Item Announcement
True System.Collections Collections
We can see that this supports the PowerShell filters as well, let’s find all the types which pertain to printers. The first approach would be to select only those objects whose name matches print! PS> $EMG.EntityTypes.GetClasses()|?{$_.name –match "print"}|
The objects returned work well with the PowerShell environment. We can invoke the other methods on the EntityTypes interface: PS> $emg.EntityTypes.GetCategories().Count
285
PS> $emg.EntityTypes.GetCategories()|select-object -first 5|format-table Name,DisplayName -auto
Name DisplayName
---- -----------
ServiceManager.ActivityManagement.EditActivity.Task.FlagCategory
ServiceManager.ActivityManagement.Library.ApprovalEnumVisibleCategory
ServiceManager.ActivityManagement.Library.DecisionEnumVisibleCategory
ServiceManager.ActivityManagement.Library.DecisionEnumCategory
ServiceManager.ActivityManagement.Library.ApprovalEnumCategory
PS> $emg.EntityTypes.GetClasses().Count
231
PS> $emg.EntityTypes.GetClasses()|select-object -first 5|format-table Name,DisplayName -auto
Name DisplayName
---- -----------
System.Entity Object
System.AdminItem Admin Item
System.Announcement.Config Config
System.Announcement.Item Announcement
System.Collections Collections
PS> $emg.EntityTypes.GetEnumerations().Count
414
PS> $emg.EntityTypes.GetEnumerations()|select-object -first 5|format-table Name,DisplayName -auto
Name DisplayName
---- -----------
ActivityAreaEnum.Messaging.Client Client
ServiceManager.ConfigurationManagement.WindowsPrintersTasks Windows Printer Tasks
IncidentSourceEnum.DCM SCCM (DCM)
System.Knowledge.CategoryEnum.Software
ActivityStageEnum.Develop Develop
PS> $emg.EntityTypes.GetRelationshipClasses().Count
118
PS> $emg.EntityTypes.GetRelationshipClasses()|select-object -first 5|format-table Name,DisplayName -auto
Name DisplayName
---- -----------
System.ComputerPrimaryUser Computer Primary User
System.ConfigItemContainsConfigItem Config Item Contains Config Item
System.ConfigItemHasFileAttachment Config Item Has File Attachment
System.ConfigItemImpactsCustomers Config Item Impacts Customers
System.ConfigItemOwnedByUser Config Item Owned By User
PS> $emg.EntityTypes.GetTopLevelEnumerations().Count
72
PS> $emg.EntityTypes.GetTopLevelEnumerations()|select-object -first 5|format-table Name,DisplayName -auto
Name DisplayName
---- -----------
System.Internal.ManagementPack Management Pack
System.WorkItem.ActionLogEnum Action Log Enum
ChangeManagement.CreateTask Create Task
Microsoft.EnterpriseManagement.ServiceManager.UI.Authoring.AllObjectTemplates All Object Templates
System.Knowledge.CategoryEnum
PS> $emg.EntityTypes.GetTypeProjections().Count
43
PS> $emg.EntityTypes.GetTypeProjections()|select-object -first 5|format-table Name,DisplayName -auto
Name DisplayName
---- -----------
It looks like we have reasonable output for everything except TypeProjections. By looking at the count, I can tell that we have some sort of results, so let’s take a closer look at the object: PS> $emg.EntityTypes.GetTypeProjections()[0]
Key Value
--- -----
SyncStatus {}
That’s not terribly useful, perhaps get-member will help me. Oho! it looks like this object has an enumerator. This means that when PowerShell attempts to format the object, it will call the enumerator and format the enumerated contents (rather than the object). We can suppress this in PowerShell by specifying the –EXPAND parameter with format-table: PS> $emg.EntityTypes.GetTypeProjections()|select-object -first 5| >> format-table -Expand coreonly Name,DisplayName -au >> Name DisplayName ---- ----------- Microsoft.SystemCenter.LinkingFramework.SyncStatus.Projection System.LinkingFramework.DataConnector.Projection OpsMgrConnector.Config.Projection System.NotificationChannel.SMTP.ProjectionType SMTP Projection Type System.User.Projection User Projection That’s better! Although I have a feeling that this really isn’t a good long term solution, perhaps we’ll deal with this in a future posting. Now that we have a set of these methods we can invoke, it’s a great opportunity to build a PowerShell V2 Module. We can easily convert these bits of script into functions and aggregate those functions into a module. All I need to do is put my module (as a .psm1 file) in the right place (see PowerShell V2 documentation on Modules for more help) and call import-module! Here’s the script file:
Notice also that I added a parameter to the functions so I can pass in a string and reduce the result without having to always add my own where-object pipeline. Notice further that I created a global instance of the EnterpriseManagementGroup, that way I can use it outside of the module. I’ll use this as the contents for my EntityTypes.PSM1 file and import the module. You should note that this assumes an installation on the Server machine only. If you want to run these on a machine where only the console is installed, you’ll need to load the SMDLL a little differently. You would need to do the following instead: [Reflection.Assembly]::LoadWithPartialName("Microsoft.EnterpriseManagement.Core")And you will also need to provide the name of the Service Manager server system when you create your EnterpriseManagementGroup object by calling new-EMG: $GLOBAL:EMG = new-EMG servername On to the module!
and we can see that my commands are all present! PS> get-module entitytypes|fl
Name : entitytypes
Path : C:\Users\Administrator\Documents\WindowsPowerShell\Modules\entitytypes\entitytypes.psm1
Description :
ModuleType : Script
Version : 0.0
NestedModules : {}
ExportedFunctions : {Get-Category, Get-Class, Get-RelationshipClass, Get-TopLevelEnumeration...}
ExportedCmdlets : {}
ExportedVariables : {}
ExportedAliases : {}
And I can use them just like a cmdlet: PS> get-class system.user$|ft abstract,name,displayname -au
Abstract Name DisplayName
-------- ---- -----------
True System.User Users
In my next post, I’ll discuss the Service Manager instance space and create cmdlets to use against the actual data that we store in Service Manager. 6/1/2009 Even more with Management PacksLast post, I went through the process of exporting management packs, so we can see what they do and what they define. In this post, I'll discuss importing management packs. In order to get any benefit from a Management Pack (MP), it needs to be added to the Service Manager platform. The process of adding an MP to the system is called "Importing". For this example, we'll use just a very simple management pack (the contents aren't really interesting, but to go through the exercise we need one of these). <ManagementPack ContentReadable="true" SchemaVersion="1.1"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<Manifest>
<Identity>
<ID>Simple.ManagementPack</ID>
<Version>7.0.5622.0</Version>
</Identity>
<Name>A Simple ManagementPack</Name>
<References>
<Reference Alias="System">
<ID>System.Library</ID>
<Version>7.0.5622.0</Version>
<PublicKeyToken>9396306c2be7fcc4</PublicKeyToken>
</Reference>
</References>
</Manifest>
<TypeDefinitions>
<EntityTypes>
<ClassTypes>
<ClassType ID="Simple.Class" Accessibility="Public" Abstract="false"
Base="System!System.Entity" Hosted="false"
Singleton="false" Extension="false">
<Property ID="Id" Type="guid" Key="true" Required="true" />
<Property ID="Name" Type="string" Key="false" Required="false"
CaseSensitive="false" MaxLength="256" MinLength="0" />
<Property ID="Value1" Type="string" Key="false" Required="false"
CaseSensitive="false" MaxLength="256" MinLength="0" />
<Property ID="Value2" Type="string" Key="false" Required="false"
CaseSensitive="false" MaxLength="256" MinLength="0" />
</ClassType>
</ClassTypes>
</EntityTypes>
</TypeDefinitions>
<LanguagePacks>
<LanguagePack ID="ENU" IsDefault="true">
<DisplayStrings>
<DisplayString ElementID="Simple.Class">
<Name>Simple Class</Name>
<Description>A simple class declaration</Description>
</DisplayString>
</DisplayStrings>
</LanguagePack>
</LanguagePacks>
</ManagementPack>
The Service Manager console has a way to import this, of course, here's what it looks like: But you can also do this from PowerShell with a few of simple lines of script # the two .NET types, we'll need to create
$EMGTYPE = "Microsoft.EnterpriseManagement.EnterpriseManagementGroup"
$MPTYPE = "Microsoft.EnterpriseManagement.Configuration.ManagementPack"
# Create a connection to the Data Access Service
$EMG = new-object ${EMGTYPE} localhost
# Create a management pack object based on the management pack file and
# connection to the Data Access Service
$MP = new-object ${MPTYPE} c:\temp\Simple.ManagementPack.xml,$EMG
# Call the import method on the ManagementPacks interface - all done!
$EMG.ManagementPacks.ImportManagementPack($MP)
As you can see, it's pretty straightforward. However, this isn't the whole story. If you notice in the XML above, there's a section for "References", this section of the MP allows you to declare that your MP relies on other MPs for class definitions, etc. This means that if those references aren't installed, you're MP isn't going to work. We go along way to keep bad data out of the system, so if you try to import an MP which references an MP which isn't present on the system, then that import will fail. When you're considering a single MP, working out these references may not be to bad, but if you have a number of MPs that you want to import and some of them depend on other new imported MPs, you've got to get the order correct, or you won't be able to import. Most of the time, you'll wind up inspecting those sections of the MPs, create your order, and finally import the MPs. (I've seen a number of batch files that do this very thing, so I know that this happens). That's no good as it's an awful waste of time and error prone as well. I've created a script to go through any number of MPs and figure out what order they should be installed, and then install them! The script is built so you can pipe the output of get-childitem to it. From a pseudo-code perspective, here's what we need to do Find out what MPs are already installed
and then
FOREACH of MPs to install
retrieve the references
FOREACH reference
check to see if the referenced MPs is not already installed (or will be installed)
IF the needed MP is not installed, skip for now
IF all the references are
installed and
the version is compatible and
the referenced MP is sealed and
the keytoken matches
THEN
add this MP to the list of MPs to which we'll be able to install
remove this MP from the list of MPs that we want to install
add it to the list of MPs that are already installed (since it will be)
since the list of MPs to install has now changed, restart the main loop
If there are any MPs left in the list to install, it means that we weren't able to resolve the references, so we won't be able to install that MP. Note that the failure can be for any reason, it could rely on an unsealed MP (I don’t want to discuss sealing here, suffice it to say that sealing an MP makes it immutable, this way we know the things that we rely on won’t change), the reference could be missing, it could require the wrong version, or it could have an incorrect KeyToken. There really isn't much automatic remediation that we can do, but we can report what went wrong. So, now that we have a mechanism to put MPs in order, let's see how it looks. For our simple test, we'll create 6 MPs, each one of them dependent on the next MP (from an alphabetical point of view).
this means that the need to be installed in the opposite order, M installed before L, etc. First we'll start by importing the XML file, since XML files can't be sealed, we should get an error for every MP except the only one that can be installed (SimpleMP.M, since it relies on System.Library). Now we can see where we have problems, and which MPs we can install. Because I'm importing unsealed MPs, the references that I had can't be satisfied. Only SimpleMP.M can be installed, since it has only one reference to a sealed MP (System.Library). Note also, that SimpleMP.L can't be installed because it references SimpleMP.M which is unsealed. The other MPs can't be installed because their references aren't there. Now, let's try this again with sealed MPs.PS> Get-ChildItem *.mp
Directory: Microsoft.PowerShell.Core\FileSystem::C:\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 4/2/2009 12:49 PM 4608 SimpleMP.A.mp
-a--- 4/2/2009 12:49 PM 4608 SimpleMP.B.mp
-a--- 4/2/2009 12:49 PM 4608 SimpleMP.C.mp
-a--- 4/2/2009 12:49 PM 4608 SimpleMP.D.mp
-a--- 4/2/2009 12:49 PM 4608 SimpleMP.L.mp
-a--- 4/2/2009 12:49 PM 4608 SimpleMP.M.mp
If we tried to import these MPs in alphabetical order, only the last one would succeed, so we could import them by creating a script which does the import explicitly, one at a time, but we can use our script to do it in one fell swoop. This means that we don't really have to work out the dependencies manually - automation - that's the key! PS> get-childitem *.mp|./Import-ManagementPack -whatif WhatIf: Importing Management Pack SimpleMP.M WhatIf: Importing Management Pack SimpleMP.L WhatIf: Importing Management Pack SimpleMP.D WhatIf: Importing Management Pack SimpleMP.C WhatIf: Importing Management Pack SimpleMP.B WhatIf: Importing Management Pack SimpleMP.A Wahoo! Even though the MPs have dependencies in reverse to the order returned by get-childitem, they'll be installed in the correct order. Granted, this example is pretty simple and the dependencies are straightforward, however, this script should handle pretty complicated sets of dependencies. When actually coding this script, I decided that I wanted to make sure that I could be a bit more flexible than what was strictly possible from within Service Manager. So I added options to avoid checking for the correct version, whether the MP in the reference is sealed and if the KeyToken is correct. You still won’t be able to actually import if some of these things are wrong, but you’ll see the order if everything was correct and proper. I also added -Verbose and -Debug parameters as well as a -WhatIf parameter which allows you to not actually Import the MPs, but will tell you what order they would be installed. The script it a little on the larger size (about 200 lines), so I'm not going to walk through the script in this blog, but the code is fairly well commented. Here's a link to the script: 3/10/2009 More with management packsLast post, I wrote about retrieving management packs from Service Manager and I don't really have a lot more to say about retrieving the management pack information, except for provide a way where we don't have to specify formatting. Generally, I want to see whether the management pack is sealed, the version number and then name, which translates into using format-table like this: format-table Sealed,Version,Name -autosize I'll create a ServiceManager.Format.ps1xml file which will format the default view of management packs. <configuration>
<viewdefinitions>
<view>
<name>ManagementPackView</name>
<viewselectedby>
<typename>Microsoft.EnterpriseManagement.Configuration.ManagementPack</typename>
</viewselectedby>
<tablecontrol>
<autosize />
<tableheaders>
<tablecolumnheader>
<label>Sealed</label>
</tablecolumnheader>
<tablecolumnheader>
<label>Version</label>
</tablecolumnheader>
<tablecolumnheader>
<label>Name</label>
</tablecolumnheader>
</tableheaders>
<tablerowentries>
<tablerowentry>
<tablecolumnitems>
<tablecolumnitem>
<propertyname>Sealed</propertyname>
</tablecolumnitem>
<tablecolumnitem>
<propertyname>Version</propertyname>
</tablecolumnitem>
<tablecolumnitem>
<propertyname>Name</propertyname>
</tablecolumnitem>
</tablecolumnitems>
</tablerowentry>
</tablerowentries>
</tablecontrol>
</view>
</viewdefinitions>
</configuration>
In order to add this to my environment, all I need to do is use the Update-FormatData cmdlet using the filename as an argument. After that, retrieving management packs will be be in the format that I want. PS> $MGroup.ManagementPacks.GetManagementPacks() Sealed Version Name ------ ------- ---- True 7.0.3683.0 ServiceManager.ServiceMaps.Library True 7.0.3683.0 Microsoft.SystemCenter.InstanceGroup.Library ... False 7.0.3683.0 ServiceManager.OpsMgrConnector.Configuration So less typing for me, yay! For even less typing, I'll create a script called Get-ManagementPack which creates a connection to Service Manager and retrieves the management packs. here's the script: PS> get-content Get-ManagementPack.ps1 param ( $computerName = "localhost" )
$SMDIR = "C:\Program Files\Microsoft System Center\Service Manager 2010"
$COREDLL = "${SMDIR}/SDK Binaries/Microsoft.EnterpriseManagement.Core.dll"
[reflection.assembly]::LoadFile($COREDLL) | out-null
$MGroup = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup $computerName
$MGroup.ManagementPacks.GetManagementPacks()
and to run: PS> Get-ManagementPack Sealed Version Name ------ ------- ---- True 7.0.3683.0 ServiceManager.ServiceMaps.Library True 7.0.3683.0 Microsoft.SystemCenter.InstanceGroup.Library ... False 7.0.3683.0 ServiceManager.OpsMgrConnector.Configuration Now we have a simple script and default formatting. But we're not done with management packs - if I want to see the contents of a management pack, I can do that via a process called "exporting". Exporting a management pack lets me create an XML file of the management pack which I can then inspect the various elements of the management pack so I can see what it does (and how it does it). An object exists for just the purpose of exporting management packs - Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter and using the WriteManagementPack method, I can easily create the XML files. This is perfect for a foreach pipeline, so for each management pack that I retrieve, I'll create an XML file of the management pack contents. PS> Get-ManagementPack | %{
>> $xmlWriter = new-object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter C:\temp
>> } {
>> $xmlWriter.WriteManagementPack($_)
>> }
The first script block in the foreach command creates me an xmlWriter which will used for all the management pack objects that are passed from Get-ManagementPack. The ManagementPackXmlWriter object has two constructors. The constructor that I'm using takes a string which points to a directory which will contain the exported xml files. When invoked, the method returns a string which is the fullname of the exported XML file, so when I execute it, I see the following (ellipses used to save space): C:\temp\ServiceManager.ServiceMaps.Library.xml C:\temp\Microsoft.SystemCenter.InstanceGroup.Library.xml ... C:\temp\ServiceManager.OpsMgrConnector.Configuration.xml I can easily incorporate this into a script as well: PS> Get-Content Export-ManagementPack.ps1 param ( $targetDirectory = $( throw "Need a target directory"), [switch]$verbose )
begin {
if ( $verbose )
{
$verbosePreference = "Continue"
}
$xmlWriter = new-object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter $targetDirectory
}
process {
if ( $_ -is "Microsoft.EnterpriseManagement.Configuration.ManagementPack" )
{
$path = $xmlWriter.WriteManagementPack($_)
if ( $verbose )
{
Write-Verbose "Exporting: $path"
}
}
else
{
Write-Error "$_ is not a management pack"
}
}
I've also added support for -verbose so I can see what's being exported if I want, along with just a little checking to be sure that I've actually got a management pack. Now I can run the following: PS> Get-ManagementPack | Export-ManagementPack C:\tempor PS> Get-ManagementPack | Export-ManagementPack C:\temp -verbose and export all my management packs in one simple step. Next time I'll discuss importing management packs. 2/5/2009 Service Manager and PowerShellHi There! This is the first in a series of blogs that I plan to do about Service Manager and PowerShell. These will be posted in two places - the Service Manager Blog and my spaces page, you can go to the Service Manager blog to see more posts on Service Manager, and if you found this on the Service Manager blog, you can take a look on my Spaces (http://jtruher.spaces.live.com/) page which has a number of different posts on PowerShell and my other hobbies. My semi-regular readers know that I was one of the co-creators of the PowerShell scripting language and I was one of the first members of the PowerShell team and had a whole lot to do with the first release of PowerShell, but you may not know that I am now part of the System Center Service Manager team where my area of responsibility is the SDK. This is a great new challenge for me, with an entire new area of system management to learn, so it’s very exciting. One of great things about the Service Manager product is that it is based on managed code, and because of that, PowerShell can take direct advantage of all that great code. As these articles are really about using PowerShell with Service Manager, I’m not going to spend too much time in a tutorial on PowerShell in general as there are a number of great sources for information on PowerShell (to which I’ll provide links at the end of this article). At the moment there are no cmdlets for Service Manager. We will likely be providing some later in our release cycle, but for these first articles, I’ll be interacting directly against the managed interfaces that are part of the SDK. This does make the scripting a bit more advanced from a PowerShell perspective, but it shouldn’t be too much to start – We’ll essentially be approaching this similarly to how you would approach programming against Service Manager in C#. Jakub Oleksy, a dev lead on the project has a great blog here: http://blogs.mdn.com/jakuboleksy/ he has a ton of great articles about programming against Service Manager (and Operations Manager). Here we go! To get access to the Service Manager object space, we’ll need to load the appropriate assemblies. When you install Service Manager, the libraries we need are put on the file-system and by default can be found in the directory
As we progress with these articles, we’ll do the above every time, so to save typing, you should put those three lines in your Now that we have the assembly loaded we can start interacting with Service Manager. The way that you communicate with Service Manager is via the Microsoft.EnterpriseManagement.EnterpriseManagementGroup object and there are a number of ways to create this object. We’ll create it via the simplest method, just by providing the server name where the Service Manager management server has been installed. Since I’m running these examples on the same system where the Service Manager management server is installed, I’ll use the server name “localhost” (by the way, if you run the Service Manager Console on a different the system than where the Service Manager management server is installed, you’ll need to provide the name of the management server here).
Excellent! If there was a problem in creating the object, I would have gotten an error message, now I can inspect the $MGroup object that I just created.
PS> $MGroup
ConnectorFramework : Microsoft.EnterpriseManagement.ConnectorFramework.ConnectorFrameworkConfigurationManagement
EntityTypes : Microsoft.EnterpriseManagement.EntityTypeManagement
Instances : Microsoft.EnterpriseManagement.InstancesManagement
Knowledge : Microsoft.EnterpriseManagement.KnowledgeManagement
LanguagePacks : Microsoft.EnterpriseManagement.LanguagePacksManagement
ManagementPacks : Microsoft.EnterpriseManagement.ManagementPackManagement
Monitoring : Microsoft.EnterpriseManagement.MonitoringConfigurationManagement
Overrides : Microsoft.EnterpriseManagement.OverridesManagement
Presentation : Microsoft.EnterpriseManagement.PresentationManagement
Reporting : Microsoft.EnterpriseManagement.ReportingConfigurationManagement
Resources : Microsoft.EnterpriseManagement.ResourceManagement
Security : Microsoft.EnterpriseManagement.SecurityConfigurationManagement
Subscription : Microsoft.EnterpriseManagement.Subscriptions.SubscriptionManagement
Tasks : Microsoft.EnterpriseManagement.TaskConfigurationManagement
Templates : Microsoft.EnterpriseManagement.TemplatesManagement
TypeCache : Microsoft.EnterpriseManagement.TypeCacheManagement
TypeDefinitions : Microsoft.EnterpriseManagement.TypeDefinitionsManagement
DataWarehouse : Microsoft.EnterpriseManagement.DataWarehouseManagement
Notifications : Microsoft.EnterpriseManagement.NotificationManagement
Name : Woodgrove
Id : 86a8c51c-4680-3ba0-4843-1c38f462b0b6
IsConnected : True
CacheMode : Configuration
AutoRefreshCache : True
SkuForLicense : Select
SkuForProduct : SCOM
TimeOfExpiration : 12/31/9999 11:59:59 PM
ProductId :
ConnectionSettings : Microsoft.EnterpriseManagement.EnterpriseManagementConnectionSettings
Version : 1.0.3105.0
You can see a number of different interesting bits of information here: First you can see that the IsConnected property is true, which means that I have a connection to the Service Manager SDK service. If this is ever “false”, it means that I won’t be able to get any data from Service Manager because I’m no longer connected to the service that talks to Service Manager.
This object also has version information, and a whole lot of other stuff. You'll notice a number of more complicated properties (the top 2/3rds of the output). These properties provide us the access points to the various parts of the product such as the sort of classes that are installed (EntityTypes), the actual data stored in Service Manager (Instances), and a whole bunch of other things that we’ll explore in later articles. Take a look at the SDK documentation if you want more information. The management pack is the way that you determine what sort of data you will track in Service Manager. Management packs define a whole bunch of information about the various entities such as Computers, Users, Network Cards, as well as actions that can be used with these various entities – so knowing what management packs are installed is really important. If you start the Analyst Console you’ll see something that looks like this if you select the Administration tab: We can approximate this view of the ManagementPack table with PowerShell with the following command line: PS> $MGroup.ManagementPacks.GetManagementPacks()|format-Table Sealed,Name,Description -au Sealed Name Description ------ ---- ----------- True Microsoft.SystemCenter.InstanceGroup.Library Microsoft System Center Instance Group Librar... True Microsoft.Windows.Peripheral.Library Microsoft Windows Peripheral Library: This ma... False Woodgrove.AutomatedActivity.AddUserToUserGroup True ServiceManager.ActivityManagement.Library True System.Software.Library System Software Library: This Management Pack... True Microsoft.SystemCenter.Deployment.Library True ServiceManager.KnowledgeManagement.Library True Microsoft.EnterpriseManagement.ServiceManager.UI.Administration ServiceManager Administration ManagementPack False ServiceManager.LinkingFramework.Configuration True ServiceManager.LinkingFramework.Library True System.Snmp.Library SNMP Library: Contains SNMP definitions. True ServiceManager.Core.Library True Microsoft.EnterpriseManagement.ServiceManager.UI.Console Service Manager Console ManagementPack True System.ApplicationLog.Library System Application Log Library: This Manageme... True Microsoft.EnterpriseManagement.ServiceManager.UI.Authoring Service Manager Authoring ManagementPack True Microsoft.SystemCenter.Library Microsoft System Center Library: This Managem... False Microsoft.EnterpriseManagement.ServiceManager.Default True Microsoft.SystemCenter.WorkItemGroup.Library Microsoft System Center Instance Group Librar... True System.Library System Library: Root for all Management Packs... True Microsoft.Windows.Library Microsoft Windows Core Library: This Manageme... True Microsoft.SystemCenter.ConfigurationManager Microsoft System Center Configuration Manager... True Microsoft.EnterpriseManagement.ServiceManager.Connector.Sms False ServiceManager.ChangeManagement.Configuration True System.Health.Library System Health Library: This Management Pack c... True Microsoft.SystemCenter.WorkflowFoundation.Library Microsoft System Center Workflow Foundation L... True Microsoft.SystemCenter.Report.Library True ServiceManager.Datawarehouse.Library True Microsoft.EnterpriseManagement.ServiceManager.Connector.AD True ServiceManager.ConfigurationManagement.Library False ServiceManager.ActivityManagement.Configuration True System.Notifications.Library System Notification Library: This Management ... True ServiceManager.IncidentManagement.Library True Microsoft.SystemCenter.Subscriptions True ServiceManager.ChangeManagement.Library True System.Performance.Library System Performance Library: This Management P... False ServiceManager.IncidentManagement.Configuration Viola! Now we can get access to the management packs that are installed without having to use the GUI! At this point, all of the usual PowerShell utility can be used. For example, if I wanted to see only those management packs which are unsealed I can use where-object (aliased to “?”) to look for where the Sealed property is false (not true). PS> $MGroup.ManagementPacks.GetManagementPacks()|?{ ! $_.Sealed}|format-Table Sealed,Name,Description -au
Sealed Name Description
------ ---- -----------
False Woodgrove.AutomatedActivity.AddUserToUserGroup
False ServiceManager.LinkingFramework.Configuration
False Microsoft.EnterpriseManagement.ServiceManager.Default
False ServiceManager.ChangeManagement.Configuration
False ServiceManager.ActivityManagement.Configuration
False ServiceManager.IncidentManagement.Configuration
We’ve barely scratched the surface with the way that PowerShell can interact with Service Manager. In the next article we’ll take a closer look at the contents of a management pack. Here are those PowerShell links I promised: http://blogs.msdn.com/powershell/ 1/21/2009 Sed fugit interea fugit irreparabile tempusI can’t believe it’s been more than a year since I’ve written here – man - am I a slacker! Lot’s of changes for me since my last post – I turned 50, my son got married got married to a wonderful young woman, I returned from Microsoft Research back to a product team in Microsoft (System Center Service Manager team). Some things haven’t changed - I’m still using PowerShell all the time, and still conducting the Microsoft Orchestra (http://www.msorchestra.org/). I’m planning a number of postings about PowerShell and Service Manager which you’ll see here. I’ll also be saying more about some of the musical things I’ve been doing. I’ve been doing some research on the Vaughan Williams Pastoral Symphony (#3) which the Microsoft Orchestra will be performing in April. I’ve also got other PowerShell posts I want to make – I’ve got a PowerShell media player that I’ve been using which I will be sharing as well as some other scripts and cmdlets that might entertain. Mind you, this is not a “new years’ resolution” – I detest the things. I don’t even resolve to not have resolutions! One thing I did was to eliminate the capability to comment on my posts – Sorry about that - I got very tired of culling the spam. Spaces doesn’t seem to have a way to approve comments – if somebody knows how to do that, please send me an email. 10/15/2007 And now for a little RossiniMore music performed by the 1978 PCC Chamber Singers. This time it's a little Rossini:
Accompanied again by Twyla Meyer and the soprano (heard around 4:20) is the spectacular Rebecca Sherburn. This group was really quite impressive, I know that a number of the singers in this group have careers as singers. Twyla is in great demand throughout LA and Bill Hatcher has had stellar career in choral music. I feel extremely fortunate that I had the opportunity to learn from these amazing musicians. 10/11/2007 more from PCC - 1978Audite Nova by Lassus this time - it's a very silly piece.
I remember really liking this piece and having a great time performing it. Listening to it now, it seems so heavy, i suppose it should, with 6 on a part. I've performed and listened to so much one-on-a-part that it's clear how much I've changed in the past 30 years. Thank goodness for that - it would be horrible to think that I hadn't changed.
I've got a few more of these and also some recordings of the Pasadena Chorale from around 79 to 83. I'll get them posted after I finish the Chamber Singers stuff
8/31/2007 I love TwitterI love the idea of twitter - I think it's the logical intersection between blogging and reduced attention spans. I started to play about with it and I was frustrated with the way I needed to create entries. Having a separate app to create twitter updates seemed wrong to me. I don't want to change focus from my current shell to create an update and best case, I want to update my status automatically so I don't think about it, it just happens.
I know that there are some command line tools to do this, but I I want a native solution for PowerShell, so I created a Send-TwitterStatus cmdlet to allow me to send updates directly from my shell. Not only that, but I can use this cmdlet in other scripts to automatically push my activity to Twitter as well. I created media player script and it seems like a natural thing to do is to push my playlist to Twitter so my friends can see what I'm listening to (if they want). I have a line now in the script when I append an album to my playlist is a line that calls my cmdlet and pushes my update:
Send-TwitterStatus "Adding to office playlist: $album" $credential
I grabbed the Yedda.Twitter code to do the actual Twitter interaction and the rest is just the code to stitch the cmdlet together. I also convert the XML results into a custom object so I can eventually create the appropriate formatting.
Anyway - here you go.
Here's the Yedda.Twitter library
and the cmdlet code
My post http://jtruher.spaces.live.com/blog/cns!7143DA6E51A2628D!119.entry will show how to compile and install a snapin and use.
Next steps are to create the various twitter getters and create the format file so I can get activity directly from the shell.
More media - PCC Chamber Singers, 1978It seems like the time just flies - it's been a couple months since I updated here, and I would like to post more often.
In any event, I have another audio file to post! This is the third of the Trois Chanson Britonnes Soir d'été. My memory of the recording circumstances is a little fuzzy, since it was nearly 30 years ago, but I seem to remember that we did this recording around the end of the calendar year, and we hadn't yet learned the second chanson, so I only have this one (the last) and the first.
Conducted by Bill Hatcher and accompanied by the magnificent Twyla Meyer .
Here it is: Soir d'été
Some of the other pieces from this recording session are:
There are more, and I'll post them as I can
7/15/2007 Tracing the script stackIt's not uncommon that after I've created a fairly complicated script after a while of using it, something bad happens that I wasn't expecting. And I would really like to know how I got to this state, so a stacktrace of my script would be really, really nice. Sadly, this isn't something that is a default behavior or PowerShell, but fortunately, this sort of thing is actually possible to do with just a little bit of script! Most of the real world examples are more complicated than we really need to use to illuminate the problem. So, I've created a simple example that is useful for discussion. Take the following script: When I run this script, depending on the value of my argument, the script will run or fail: PS# c:\temp\test-stacktrace 5 The message is ok - it tells me that I had a problem on the appropriate line in the script, but I don't know how I got there by looking at the message. What I would really like to see is both the error and the way I got there. Here are some examples of what I want to see: This example is the normal behavior This example will show what happens when an error occurs deep in the stack:
PS# c:\temp\test-stacktrace2 2 0.5 1 func3 : Attempted to divide by zero. At c:\temp\test-stacktrace2.ps1:25 char:10 + func3 <<<< $startVal At c:\temp\test-stacktrace2.ps1:31 char:42+ trap { write-error $_; get-stacktrace <<<< } At c:\temp\test-stacktrace2.ps1:25 char:10+ func3 <<<< $startVal At c:\temp\test-stacktrace2.ps1:17 char:10+ func2 <<<< $startVal At c:\temp\test-stacktrace2.ps1:36 char:6+ func1 <<<< $startVal At line:1 char:25+ c:\temp\test-stacktrace2 <<<< 2 Notice that I see the functions that I called on the way to this error - This way I can see the path of woe that generated the error - which means I have a much better chance of actually fixing the problem. Here's another example of what happens when an error occurs sooner in the stack, notice that we only see func1 and func2 calls: And finally what happens when an error occurs right away, notice that we only see func1 in the stack: So, here's the code - and a brief discussion follows: param ( $startVal ) Notice the addition of the "get-stacktrace" function: function get-stacktrace This function takes advantage of the fact that the PowerShell scoping rules allow you to inspect variables in different scopes from your current scope. This isn't available via syntax, but it is available via the get-variable cmdlet (aliased to gv). So our little get-stacktrace function just drills down our scopes looking for the myInvocation property which has the information about what line on the script we're on. There are some other things that are going on. The trap statement assures me that if I get any terminating errors that I ignore them and I've placed the call of gv in a script block - this allows me to really throw away any messages that get-variable may throw that aren't terminating errors. Lastly, I want to be sure that my message is on a single line, so I replace the carriage returns with empty strings. Notice also that each function now has a trap statement. I think that this is generally good practice regardless, but these do two things. First they write the error and then call the get-stacktrace function. We need to write the error because the get-stacktrace function is going to exit, so if we didn't have this write-error we wouldn't actually see what the error was, just the stack trace which isn't enough info. So, if you've got a complicated script and you would really like to discover how you got where you are, I hope this little bit of script will help! Jim
6/22/2007 Blasts from the pastWow - I haven't written for a long time - however, I think I'll be doing more because while I was cleaning up my home office, I found a number of cassette tapes to which I've been hanging for an even longer time.
I've been incredibly fortunate to perform with a number of very excellent ensembles over the years. I found recordings dating back to when I I first started singing at Pasadena City College in 1976-78 - I'm going to be sharing them here in my blog so the folks can get access to them as I can convert them from tape to digital.
So, I'll try to reduce the noise, and do some of the usual mastering activities. We'll see how successful I am at that, it will be a learning experience for me. With the first one, I haven't done any noise reduction of mastering, it's just right off the tape, so you'll hear some tape hiss. Also, as I recall, the recording was done by some pretty crummy omnidirectional mics (and I seem to remember that it was recorded on reel-to-reel, so this cassette might be a copy of that). This is a recording from 1978 of the first movement of Trois Chanson by Henk Badings performed by the Pasadena City College Chamber Singers, conducted by Bill Hatcher who inspired me greatly and accompanied by the amazing Twyla Meyer.
Here you go: La Nuit En Mer
jim 2/19/2007 PowerShell Extended Types (includes a TYPES.XSD)One of my favorite features of PowerShell is the extended type system. This system allows us to extend the .NET objects that are returned by the underlying .NET framework with bits of interesting stuff. There's two way to go about this. First, by using the add-member cmdlet, it's possible to add methods and properties to an instance of an object. If we start with a "blank" object, we can create an object out of whole cloth. Take a look at the following output
PS> get-stock|ft Symbol,Last,Change,@{l="ChangeP";f="{0:N2}";e={$_.ChangeP}} -auto
Symbol Last Change ChangeP
------ ---- ------ ------- MSFT 28.74 -0.72 -2.51 SCO 2.77 -0.02 -0.72 ^DJI 12767.57 2.56 0.02 INFY 59.84 0.23 0.38 SUNW 6.29 -0.02 -0.32 I have a little script that collects the stock quotes for a number of companies. (I have a special formatting file for the output, but that's another blog). Here's the script, you can see how it takes advantage of the extendable type system.
$SYMS = "MSFT","SCO","^DJI","INFY","SUNW"
$wc = new-object net.webclient foreach ( $SYM in $SYMS ) { $yahoo = "http://finance.yahoo.com/d/quotes.csv?s=" $url = "${yahoo}${SYM}&f=sl1d1t1c1ohgv&e=.csv" $string = $wc.DownloadString($url) if ( $string ) { trap { continue } $stock = $string.replace("`"","").replace("N/A","0").Trim().split(",") $obj = new-object System.Management.Automation.PSObject $obj.psobject.typenames[0] = "Custom.Stock" $obj | add-member NoteProperty Symbol ([string]$stock[0]) $obj | add-member NoteProperty Last ([double]$stock[1]) $obj | add-member NoteProperty Date ([datetime]$stock[2]) $obj | add-member NoteProperty Time ([datetime]$stock[3]) $obj | add-member NoteProperty Change ([double]$stock[4]) $obj | add-member NoteProperty ChangeP ([double]$stock[4]/[double]$stock[1] * 100) $obj | add-member NoteProperty Open ([double]$stock[5]) $obj | add-member NoteProperty High ([double]$stock[6]) $obj | add-member NoteProperty Low ([double]$stock[7]) $obj | add-member NoteProperty Volume ([int]$stock[8]) $obj | add-member NoteProperty InPort ($pf -contains $SYM) $obj } } So, that's a way to use the add-member cmdlet to dynamically extend an object. This could be done with any object, in this example, I'm creating an object out of nothing, but you can do the same thing with any object. Here's another example, where I interact with some performance counters, specifically the idle time.
PS> $idle = get-idle
PS> $idle CPUCount : 2 Percent : 100 CategoryName : Process CounterHelp : % Processor Time is the percentage of elapsed time that all of process threads used the processor to execution instructi ons. An instruction is the basic unit of execution in a comp uter, a thread is the object that executes instructions, and a process is the object created when a program is run. Code executed to handle some hardware interrupts and trap condit ions are included in this count. CounterName : % Processor Time CounterType : Timer100Ns InstanceLifetime : Global InstanceName : Idle ReadOnly : True MachineName : JIMTRUD4 RawValue : 10053571562500 Site : Container : PS> $idle.getidle() # I'll call my custom script method!
99 PS> $idle.getidle() 88 # the reason this fell so much is that I put some load on the system PS> $idle.getidle() 90 Here's the script - I'm sure it could be written better, but that's not the point.
param ( $systems = @( $env:computername ))
$PerfCnt = "System.Diagnostics.PerformanceCounter" $PerfCat = "System.Diagnostics.PerformanceCounterCategory" foreach($system in $systems ) { $Info = "Process","% Processor Time","Idle",$system $obj = new-object $PerfCnt $Info $pcc = new-object $PerfCat Processor,$system $idColCol = $pcc.ReadCategory() [int]$CPUCount = $idColcol['% idle time'].keys.count - 1 $per = $obj.nextvalue() / $CPUCount # for some reason, we need to sleep here and then check again # I haven't bothered to find out why sleep 1 [int]$per = $obj.nextvalue() / $CPUCount # add some members to the the performance counter $obj | add-member NoteProperty CPUCount $CPUCount $obj | add-member NoteProperty Percent $per $obj | add-member ScriptMethod GetIdle { $this.Percent = [int]($this.NextValue() / $this.CPUCount) $this.Percent } # emit the object $obj } Viola! I've extended the instances of the PerformanceCounter objects created in this script
However, there is another way to extend an object instance. You can create a blob of XML (in a file) and then load that file into your session with the update-typedata cmdlet - whammo! - everytime you create an instance of a specific object, it will have your custom extensions. We have a number of these extensions in the standard release to ease using the shell. The best case in point is the difference between System.Array and System.Collections.ArrayList. "Length" is the property in System.Array that provides the count of the elements of the array, but System.Collections.ArrayList uses "Count". We extended the System.Array type with a "Count" property that is an alias to the Length property which actually exists. This way, regardless of whether you've got an array or arraylist, "Count" will work! Here's the blob of XML that does the trick.
<Types>
<Type>
<Name>System.Array</Name> <Members> <AliasProperty> <Name>Count</Name> <ReferencedMemberName>Length</ReferencedMemberName> </AliasProperty> </Members> </Type>
<Types>
Once you have this bit of XML in a file, you can use the update-typedata cmdlet to add the blob to your environment. Let's make our own little extension so you can see how it works.
PS> cat mynewtype.ps1xml
<Types>
<Type>
<Name>System.Array</Name> <Members> <AliasProperty> <Name>HappyAlias</Name> <ReferencedMemberName>Length</ReferencedMemberName> </AliasProperty> </Members> </Type>
</Types> As you can see, it's pretty simple. Now let's load it:
PS> update-typedata mynewtype.ps1xml
PS> ,(1,2,3,4)|gm TypeName: System.Object[] Name MemberType Definition
---- ---------- ---------- Count AliasProperty Count = Length HappyAlias AliasProperty HappyAlias = Length ... SyncRoot Property System.Object SyncRoot {get;}
PS> ,(1,2,3,4).happyalias 4 PS> We've extended the array type! However, figuring out what is possible isn't documented anywhere, so it's a little tricky to create these. We allow all sorts of extensions; a bunch of different property extensions, methods (both script and code). With this in mind, I created an XSD that allows me to create types extensions much more easily. Now I can edit my type extensions in Visual Studio and they nearly write themselves. Note that this XSD may have some errors, and as time goes on, I'll correct it as I can. However, in the mean time, it's better than a poke in the eye with a sharp stick.
I'm also working on an XSD for our formatting - stay tuned for that
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Name" type="xs:string" />
<xs:complexType name="NoteProperty">
<xs:all> <xs:element ref="Name" /> <xs:element name="Value" type="xs:string" /> </xs:all> </xs:complexType> <xs:complexType name="AliasProperty">
<xs:all> <xs:element ref="Name" /> <xs:element name="ReferencedMemberName" type="xs:string" /> </xs:all> </xs:complexType> <xs:complexType name="ScriptMethod">
<xs:all> <xs:element ref="Name" /> <xs:element name="Script" type="xs:string" /> </xs:all> </xs:complexType> <xs:complexType name="ScriptProperty">
<xs:sequence> <xs:element minOccurs="1" maxOccurs="1" ref="Name" /> <xs:element minOccurs="0" maxOccurs="1" name="GetScriptBlock" type="xs:string" /> <xs:element minOccurs="0" maxOccurs="1" name="SetScriptBlock" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:complexType name="CodeReference"> <xs:all> <xs:element name="TypeName"/> <xs:element name="MethodName"/> </xs:all> </xs:complexType> <xs:complexType name="CodeMethod"> <xs:sequence> <xs:element name="Name" type="xs:string"/> <xs:element name="CodeReference" type="CodeReference"/> </xs:sequence> </xs:complexType> <xs:complexType name="CodeProperty">
<xs:all> <xs:element name="Name" type="xs:string" /> <xs:element minOccurs="0" maxOccurs="1" name="GetCodeReference" type="CodeReference" /> <xs:element minOccurs="0" maxOccurs="1" name="SetCodeReference" type="CodeReference" /> </xs:all> </xs:complexType> <xs:complexType name="PropertySet">
<xs:sequence> <xs:element ref="Name" /> <xs:element name="ReferencedProperties" /> </xs:sequence> </xs:complexType> <xs:complexType name="Members">
<xs:sequence> <xs:choice maxOccurs="unbounded"> <xs:element name="NoteProperty" type="NoteProperty" /> <xs:element name="AliasProperty" type="AliasProperty" /> <xs:element name="ScriptProperty" type="ScriptProperty" /> <xs:element name="CodeProperty" type="CodeProperty" /> <xs:element name="ScriptMethod" type="ScriptMethod" /> <xs:element name="CodeMethod" type="CodeMethod" /> <xs:element name="MemberSet" type="MemberSet" /> <xs:element name="PropertySet" type="PropertySet" /> </xs:choice> </xs:sequence> </xs:complexType> <xs:complexType name="MemberSet"> <xs:all> <xs:element name="Name" type="xs:string"/> <xs:element name="Members" type="Members" /> </xs:all> </xs:complexType> <xs:element name="Types"> <xs:complexType> <xs:sequence> <xs:element maxOccurs="unbounded" name="Type"> <xs:complexType> <xs:sequence> <xs:element name="Name" type="xs:string" /> <xs:element name="Members" type="Members" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> I'm sorry about the length - i should learn to stop typing.
12/2/2006 Getting more out of helpAs we were developing PowerShell, we knew that we wanted to provide a capability for searching through the help. Unfortunately, we don't always get to everything, and this is one of those things that we couldn't get to. However, I still sometimes need to search through the help, so I created this little function to do the search. It searches through the conceptual topics for the string for which I'm looking and with the switch parameter "-all", I search through the descriptions of the cmdlet help as well. Like most things in PowerShell, it turns out to be pretty simple. Here's how I would look for help that has the word "process" in it, I use -all to retrieve cmdlet help in addition to the conceptural (about*) topics. PS> search-help process -all HelpTopic Reference --------- --------- about_arithmetic_operators The command then processes the parameters as it w... about_array .NET Framework. For example, the objects that Get... about_assignment_operators current process. For example, the following comma... about_automatic_variables Contains objects for which an error occurred whil... about_commonparameters the command during processing. This variable is about_environment_variable system path, the number of processors used by the... about_filter processes that begin with the letters a through m... about_foreach displays any processes whose working-set (memory ... about_function filters. The primary difference between the two i... about_location As a result, all commands are processed from this... about_logical_operator When PowerShell processes this statement, it eval... about_object receives the objects from the first command, proc... about_operator fact that PowerShell processes operators in a ver... about_parsing When processing a command, the PowerShell parser ... about_pipeline down the pipeline to the second command. The seco... about_provider Alias ShouldProcess ... about_quoting_rules is passed to the command for processing. Consider... about_shell_variable example, the $PID variable stores the process ID ... about_signing export process. about_switch The keyword "break" indicates that no more proces... about_wildcard in order to return specific results. The process ... default get-help get-process : Displays help about the ... ForEach-Object Performs an operation against each of a set of in... Where-Object Creates a filter that controls which objects will... Get-Process Gets the processes that are running on the local ... Stop-Process Stops one or more running processes. Set-Content Writes or replaces the content in an item with ne... Export-Csv Creates a comma-separated values (CSV) file that ... Sort-Object Sorts objects by property values. Get-TraceSource Gets the Windows PowerShell components that are i... Here’s the search-help script - as you can see, it's just a few lines. The interesting bit is the use of Select-Object. With Select-Object, I create custom objects from both about* help and cmdlet help. Select-Object allows me to specify which properties I want, but it also allows me to "rename" the property. This way I can take two disparate bits of information (the bits I get back from Select-String and the bits I get out of the help object) and create objects that will act consistently regardless of their origin. function Search-Help I hope this is useful for you! jim |
|
|