I’ve had this request quite a few times over the last year to be able to copy an incident and I wanted to see how hard it would be to do. I started by trying to do it with SMLets and got about 95% of the way there but no matter which way I tried I couldn’t quite get all the way there. I’m either just not enough of a PowerShell expert or there are some limitations in SMLets/SDK that I couldn’t work around. So – I went back to my trusty C#. Don’t get me wrong. I LOVE PowerShell, but there is just something very comforting about C# for me. It just works the way I expect it to. C# is my first love.
So – for this project I set it up so that it was a more generic solution that could be used to copy any kind of object. It was pretty straightforward to just loop through all the properties of an object and copy them to a new object. The reality though is that there are subtleties depending on which class of object that you are working with that need to be taken into consideration. For example, if an incident is currently in a Status = Resolved state do you really want to copy that to the new incident record? Probably not. You want it to start out in the active state. You also can’t blindly copy the incident work item ID property over to the new object since that is the key property and you don’t want to end up with two incidents with the same ID (that’s technically not even possible).
On the other hand you want to be able to copy over extended class properties that I don’t even know about right now. Given that requirement, I can’t hardcode which properties to copy over since it would necessarily not include extended class properties.
What to do?
Simple – assume that all extended class properties should be copied over and simply exclude certain properties which I know right now should not be copied over.
So – I created a library of common functions which can be called for copying any kind of object. The first method in that Common class is a function to copy all of the properties from an old object to a new object except a list of properties that are passed in that should not be.
public static CreatableEnterpriseManagementObject CreateNewObjectFromExistingObject(EnterpriseManagementObject emoToBeCopied, ManagementPackClass mpc, string[] strPropertiesToExclude, EnterpriseManagementGroup emg)Then right before I call this method I create a list of properties to exclude and pass it in:
string[] strPropertiesToExclude = new string[] {
As you can see there are quite a few properties that should not be copied over for an incident.
The next issue to deal with was whether or not to copy over a given relationship. Some of the relationships for an incident are maxcardinality = 1 relationship types like Assigned To User, Affected User, etc. and some are maxcardinality > 1 like Affected Configuration Items.
I ended up treating each of these the same way actually. I basically just took every existing related object and created a new relationship from the new incident to that object like this:
foreach (IComposableProjection icpAffectedUser in emopIncident[mprAffectedUser.Target])In this case it will only loop at most once because there is only one affected user.
In the case of config items, it will loop through for as many configuration items as there are.
foreach (IComposableProjection icpAffectedConfigItem in emopIncident[mprAboutConfigItem.Target])After I wrote the code that way though I realized that I really should just create a generic way of handling relationships. So, I created a common method similar to the CreateNewObjectProjectionFromExistingObjectProjection that takes a list of type projection component aliases to ignore. For example – here is the type projection XML for the incident type projection:
<Component Path="$Target/Path[Relationship='CoreIncident!System.WorkItem.IncidentPrimaryOwner']$" Alias="PrimaryOwner" />Notice how each component has an Alias name? Well – there are some relationships that don’t make sense to be copied over to the new object so we can make an array of those that don’t make sense:
//A list of Relationships to exclude was not passed in so we are going to go with a default list.Then we can pass those aliases in to our CopyRelationships method:
//Copy all the relationships defined in the type projection, except for those specified
And here is what the CopyRelationships method does. It loops through each component of the type projection. If it is not in the list of aliases to exclude then it loops through each related object and creates a new relationship to the same object in the new projection object.
public static void CopyRelationships(EnterpriseManagementObjectProjection emopToBeCopiedFrom, ref EnterpriseManagementObjectProjection emopToBeCopiedTo, ManagementPackTypeProjection mptp, string[] strAliasesToExclude)Now, here is an interesting case… what to do about the created by user relationship. We don’t want to copy that over since we want to set it to whoever the current user is that is doing the copying not to who created the incident that we are copying from right?
So – to do that I created a common helper method to get the current user and return it as an EnterpriseManagementObject:
public static EnterpriseManagementObject GetLoggedInUserAsObject(EnterpriseManagementGroup emg)Then I just add the returned user EnterpriseManagementObject in the same way I did with the other relationships:
//Set CreatedByUser to be the user that is logged inThe last thing I did is just wrote a one liner to add the incident that we are copying to as a related work item to the incident that we are creating:
//Relate the original incident to the new oneThat’s pretty much it. At the end, the new incident object projection is committed to the database like this:
emopNewIncident.Commit();Now, what I did is wrap up all this logic in a C# code class called Incident. That can then be instantiated either from a simple command line program or from a console task.
Command line example:
Incident incident = new Incident();Console task example:
public override void ExecuteCommand(IList<NavigationModelNodeBase> nodes, NavigationModelNodeTask task, ICollection<string> parameters)So – now I can use this from the command line like this:
or in the UI like this:
I’ve already discussed how to create console tasks like this before so I wont go into detail on that here, but I did have someone ask me how to create console task icons. I’ll explain that next.
First you need to find or create a 16x16 .pixel png file. Then you need so save that file in the same folder as your MP .xml file.
Then you need to declare the image file in your MP in the Resources section at the very end of the management pack (after the LanguagePacks section even):
<Resources>You just need to give your image an ID and put the file name in FileName.
Then you need to make an association in your MP XML file between the console task and the image in the Presentation section of the MP XML:
<Presentation>Notice how the ElementID corresponds to the ConsoleTask ID attribute value and the ImageID corresponds to the Image ID attribute value.
Just for fun I also made sure that this task supports multi-select. To do that I needed to do two things:
1) Add a Category in the management pack so that the task will show up when multiple objects are selected in a view:
<Category ID="Category.CopyIncidentCommandMultiselect" Target="CopyIncident" Value="Console!Microsoft.EnterpriseManagement.ServiceManager.UI.Console.MultiSelectTask" />management pack.
2) In the console task handler ExecuteCommand override I looped through each of the NavigationalModelNodeBase objects in the nodes collection. This is highlighted above.
Lastly you need to create a management pack bundle. For more information on that see this blog post:
http://blogs.technet.com/b/servicemanager/archive/2009/09/04/introducing-management-pack-bundle...
I have uploaded this solution to CodePlex as a reference implementation. You are more than welcome to use the solution in your own environment, but please be aware that this is not something that is officially supported by Microsoft support . You can file feature requests (including other classes that you would like to see copy support for) and bugs on the CodePlex site on the Issue Tracker section and I will do what I can to address them.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.