Tasks Part 2 – Custom Console Tasks for Create, Edit, Delete

Tasks Part 2 – Custom Console Tasks for Create, Edit, Delete

  • Comments 8
  • Likes

Awhile back I started a series of blog posts on explaining tasks – check out the first post which was an overview of tasks:

http://blogs.technet.com/servicemanager/archive/2010/02/11/tasks-part-1-tasks-overview.aspx

In this blog post, I’ll describe how to create some of the most common types of custom console tasks that you might want to create.  I’ll show an example of using one of the standard “platform” tasks by just referencing it in the MP and also how you can create  your own custom console task handlers.  Along the way, I’ll also show you a few tricks about how to enable tasks to support double click events in a view and how to enable tasks for multi-select.

First – let’s set the context of this blog post.  I’ve extended the Service Request solution to support a new class called ‘Cost Center’ for purposes of this example.  Cost Center derives from System.Entity just to show you how to do this without inheriting anything special from the model.  Generally speaking you don’t want to derive your classes directly from System.Entity.  This is mostly just as an example.  Although the Service Request solution is functional and people can use it, the Service Request solution is intended more for showing how to use the platform to extend Service Manager.

Step 0 – Include some MP references for MPs that we will need to reference for this solution

      <Reference Alias="System">
        <ID>System.Library</ID>
        <Version>7.0.6555.0</Version>
        <PublicKeyToken>31bf3856ad364e35</PublicKeyToken>
      </Reference>
      <Reference Alias="Console">
        <ID>Microsoft.EnterpriseManagement.ServiceManager.UI.Console</ID>
        <Version>7.0.6555.0</Version>
        <PublicKeyToken>31bf3856ad364e35</PublicKeyToken>
      </Reference>
      <Reference Alias="Authoring">
        <ID>Microsoft.EnterpriseManagement.ServiceManager.UI.Authoring</ID>
        <Version>7.0.6555.0</Version>
        <PublicKeyToken>31bf3856ad364e35</PublicKeyToken>
      </Reference>

Step 1 – Create the new class:

        <ClassType ID="Microsoft.Demo.CostCenter" Accessibility="Public" Abstract="false" Base="System!System.Entity" Hosted="false" Singleton="false" Extension="false">
          <Property ID="CostCenterID" Type="int" AutoIncrement="false" Key="true" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" />
          <Property ID="ChargebackRate" Type="decimal" AutoIncrement="false" Key="false" CaseSensitive="false" MaxLength="30" MinLength="0" Required="true" />
        </ClassType>

Step 2 – Next we are going to add a view for our Cost Centers.  The way I usually do this is to import the MP after I have defined the class.  Then in the Work Items or Configuration Items view I make a view for the Cost Center class.  Then I export the MP and change the parent folder as needed.  I’m not going to go into creating views in this blog post.   For now, just know that we have created a view that looks like this.

<View ID="CostCentersView" Accessibility="Public" Enabled="true" Target="Microsoft.Demo.CostCenter" TypeID="Console!GridViewType" Visible="true">

You can see the details of the view in the MP XML file linked to on the Code Plex site below.

Step 3 – Now we are going to create a couple of Console Tasks – one for Edit(aka Properties) and one for Delete.  The Console Task Definitions look like this.

      <ConsoleTask ID="EditCostCenterTask" Accessibility="Public" Enabled="true" Target="Microsoft.Demo.CostCenter" RequireOutput="false">
        <Assembly>Console!SdkDataAccessAssembly</Assembly>
        <Handler>Microsoft.EnterpriseManagement.UI.SdkDataAccess.ConsoleTaskHandler</Handler>
        <Parameters>
          <Argument Name="Assembly">Microsoft.Demo.ServiceRequest</Argument>
          <Argument Name="Type">Microsoft.Demo.ServiceRequest.CostCenterTaskHandler</Argument>
          <Argument>Edit</Argument>
        </Parameters>
      </ConsoleTask>
      <ConsoleTask ID="DeleteCostCenterTask" Accessibility="Public" Enabled="true" Target="Microsoft.Demo.CostCenter" RequireOutput="false">
        <Assembly>Console!SdkDataAccessAssembly</Assembly>
        <Handler>Microsoft.EnterpriseManagement.UI.SdkDataAccess.ConsoleTaskHandler</Handler>
        <Parameters>
          <Argument Name="Assembly">Microsoft.Demo.ServiceRequest</Argument>
          <Argument Name="Type">Microsoft.Demo.ServiceRequest.CostCenterTaskHandler</Argument>
          <Argument>Delete</Argument>
        </Parameters>
      </ConsoleTask>

The ID property of the task can be anything you want it to be like any MP element.  The target of these two tasks should be the ClassType ID of the class for which you want to enable these tasks.  That way whenever an object of that class is selected these console tasks will show up in the tasks pane.  The Assembly and Handler should always be the same.  The Assembly Argument should be the name of the assembly which contains the ConsoleCommand.ExecuteCommand() method you want to invoke.  More on that later.  The Type Argument is the fully qualified ConsoleCommand class name (including namespace) in the assembly.  Again, more on that later.  The argument can be anything you want to “pass in” to the ExecuteCommand() method that provides context of what you want the task to do when it is clicked.  More on that later.

Step 4 – Now we need to create some special Category elements in the MP to do certain things.

The first Category is one that tells Service Manager that the MP that it is dealing with is specifically intended to be run in SCSM:

    <Category ID="Category.SCSM.MP.ServiceRequest" Value="Console!Microsoft.EnterpriseManagement.ServiceManager.ManagementPack">
      <ManagementPackName>Microsoft.Demo.ServiceRequest</ManagementPackName>
      <ManagementPackVersion>1.0.0.0</ManagementPackVersion>
    </Category>

Without this category SM will hide any ConsoleTasks defined in this MP from the console because it will think that you are importing an MP designed originally for SCOM and it shouldn’t show the Console Tasks defined in the MP because they might not be appropriate for use in SCSM.

If you are going to be sealing your management pack you will need to include the Public Key Token as part of the MP Category declaration like this:

    <Category ID="Category.SCSM.MP.ServiceRequest" Value="Console!Microsoft.EnterpriseManagement.ServiceManager.ManagementPack">
      <ManagementPackName>Microsoft.Demo.ServiceRequest</ManagementPackName>
      <ManagementPackVersion>1.0.0.0</ManagementPackVersion>
      <ManagementPackPublicKeyToken>9396306c2be7fcc4</ManagementPackPublicKeyToken>
    </Category>

The next category will make a ‘Create <Class Name>’ task show up in the task pane in the view that it is targeted at.  When the user clicks on the task, the task handler will determine what class or type projection the view is targeted at and will then open the form that corresponds to that class or type projection.

<Category ID="Category.CostCenterCreateType" Target="CostCentersView" Value="Authoring!Microsoft.EnterpriseManagement.ServiceManager.UI.Authoring.CreateTypeCategory" />

Notice how the Target attribute points to the ID of the view that we created above.  This is all you need to do to get a Create task in your view to create objects.

You may want to enable the view to support double click on the rows.  You just need to specify which task should handle the double click event by including a Category like this:

<Category ID="Category.DoubleClickEditCostCenter" Target="EditCostCenterTask" Value="Console!Microsoft.EnterpriseManagement.ServiceManager.UI.Console.DoubleClickTask" />

Just point the Target attribute at the task that you want to be the handler for a double click event on a row in the view.

Lastly, you may want to enable some of your tasks to support multi-select.  To do so you just need to include a Category like this:

<Category ID="Category.MultiSelectTask.DeleteCostCenter" Target="DeleteCostCenterTask" Value="Console!Microsoft.EnterpriseManagement.ServiceManager.UI.Console.MultiSelectTask" />

The Target attribute just needs to point at the task that you want to be able to support multi-select.

Step 5 – You will need to specify DisplayStrings for your Class Type, Console Tasks and the View

        <DisplayString ElementID="EditCostCenterTask">
          <Name>Properties</Name>
        </DisplayString>
        <DisplayString ElementID="DeleteCostCenterTask">
          <Name>Delete</Name>
        </DisplayString>
        <DisplayString ElementID="CostCentersView">
          <Name>Cost Centers</Name>
        </DisplayString>

More information on language packs can be found here.

Step 6 – You may want to include some Image References for the tasks so that they show pretty icons that look consistent with the rest of the Service Manager console.  This is optional.

      <ImageReference ElementID="EditCostCenterTask" ImageID="Console!Microsoft.EnterpriseManagement.ServiceManager.UI.Console.Image.Properties"/>
      <ImageReference ElementID="DeleteCostCenterTask" ImageID="Console!Microsoft.EnterpriseManagement.ServiceManager.UI.Console.Image.Remove"/>

Step 7 – Now you need to create a new Visual Studio project with a Class in it.  Add references to the following assemblies which can be found in either the C:\Program Files\Microsoft System Center\Service Manager 2010 directory or the C:\Program Files\Microsoft System Center\Service Manager 2010\SDK Binaries directory:

  • Microsoft.EnterpriseManagement.Core
  • Microsoft.EnterpriseManagement.ServiceManager.Application.Common
  • Microsoft.EnterpriseManagement.UI.FormsInfra
  • Microsoft.EnterpriseManagement.UI.Foundation
  • Microsoft.EnterpriseManagement.UI.SdkDataAccess

Now add using statements for the following:

using System;
using System.Collections.Generic;
using System.Windows;
using Microsoft.EnterpriseManagement;
using Microsoft.EnterpriseManagement.Common;
using Microsoft.EnterpriseManagement.ConnectorFramework;
using Microsoft.EnterpriseManagement.ConsoleFramework;
using Microsoft.EnterpriseManagement.UI.SdkDataAccess;
using Microsoft.EnterpriseManagement.UI.DataModel;
using Microsoft.EnterpriseManagement.ServiceManager.Application.Common;

Next, you’ll need to change your Class so that it derives from ConsoleCommand:

namespace Microsoft.Demo.ServiceRequest
{
    class CostCenterTaskHandler : ConsoleCommand

Notice how this Namespace and class are used in the Type Argument above in the MP XML declaration of the Console Task.

Then create an overload for the ExecuteCommand method like this:

public override void ExecuteCommand(IList<NavigationModelNodeBase> nodes, NavigationModelNodeTask task, ICollection<string> parameters)

Then handle the command by first creating an EnterpriseManagementGroup from the existing console context:

//*** IMPORTANT NOTE: The IManagementGroupSession is not a part of the publicly document/supported official SDK and is subject to change in a future release.
IManagementGroupSession session = (IManagementGroupSession)FrameworkServices.GetService<IManagementGroupSession>();
EnterpriseManagementGroup emg = session.ManagementGroup;

Then you can decide what to do based on the Argument (remember this from the Console Task XML declaration above?) that is passed in:

if (parameters.Contains("Edit"))
...do something
else if (parameters.Contains("Delete"))
...do something else

In the case of Edit we will do this:

//There will only ever be one item because we are going to limit this task to single select
foreach (NavigationModelNodeBase node in nodes)
{
    //*** IMPORTANT NOTE: The ConsoleContextHelper class is not a part of the publicly document/supported official SDK and is subject to change in a future release.
    ConsoleContextHelper.Instance.PopoutForm(node);
}

This basically just gets the selected item and uses a console helper method to open that object in the appropriate form.

In the case of Delete we will do this:

MessageBoxResult result = MessageBox.Show("Are you sure you want to delete the selected Cost Centers?", "Confirm Delete", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result != MessageBoxResult.Yes)
    return;

//Create an IncrementalDiscoveryData "bucket" for capturing all the deletes that will be processed at the same time
IncrementalDiscoveryData idd = new IncrementalDiscoveryData();

foreach (NavigationModelNodeBase node in nodes)
{
    EnterpriseManagementObject emoCostCenter = emg.EntityObjects.GetObject<EnterpriseManagementObject>(new Guid(node["$Id$"].ToString()), ObjectQueryOptions.Default);
    idd.Remove(emoCostCenter);
}

idd.Commit(emg);
RequestViewRefresh();

This basically just confirms the deletion first.  Then it creates a “bucket” called an IncrementalDiscoveryData that we can put things in to be bulk processed later when we call .Commit().  In this case, we just need to get a EnterpriseManagementObject for each selected item (aka “node”) in the view.  Then we add it to the bucket as a remove action that will happen later.  At the end we can call a console helper method to update the view.

Step 8 – Now we need to build the assembly.  Then we need to add a Reference to the assembly in the MP.

  <Resources>
    <Assembly ID="Assembly.ServiceRequest" Accessibility="Public" FileName="Microsoft.Demo.ServiceRequest.dll" HasNullStream="false" QualifiedName="Microsoft.Demo.ServiceRequest, Version=1.0.0.0" />
  </Resources>

Step 9 – Because we are referencing the assembly we need to include the MP .xml file and the assembly file in a Management Pack Bundle.  See this blog post for more information on creating management pack bundles and getting the PowerShell script New-MPBundle.ps1.  Starting in SP1 if your console task executes a executable file or assembly that is not signed and the console task is defined in an MP that is not sealed you will get this warning message when running the task:

image

So – if you want this dialog to not show up when users click your console task you need to seal your MP and/or sign the assembly.

Now you can import the MP Bundle and after restarting the console you’ll see a new view:

image

And a new console task for creating a cost center:

image

And when you have a cost center selected you’ll see two tasks:

image

So – this blog showed you some examples of creating custom console tasks that use some of the built in framework helper methods.  You could execute any code you wanted to in the task handler code though.  For those cases above where there are console helper methods being used that are subject to change – these methods are probably going to change namespaces in the “r2” release of SCSM.  That is going to break any code that uses them until the namespace declarations are changed.  You’ll probably need to update your code and make a new release that will work with the “R2” release.  More information will be announced about these changes later.

Some other examples of creating custom console task handlers can be found in these blog posts:

CSV Connector: http://blogs.technet.com/b/servicemanager/archive/2010/05/28/new-code-plex-project-csv-connector.aspx

SLA Management: http://blogs.technet.com/servicemanager/archive/2010/05/06/incident-sla-management-in-service-manager.aspx

These updates to the Service Request Code Plex project have been uploaded to the Code Plex site as Change Set #82260.  They are not part of a new release yet.  I’m going to do some additional modification to this project soon and then create a new release.

Continue on in this Tasks series to learn more about tasks:

Tasks - Part 3: Command Line Console Tasks

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi Travis,

    I have a standard console task that is using "Microsoft.EnterpriseManagement.UI.SdkDataAccess.CommandLineHandler" to call an exe. Is it possible to add this exe as a resource into an mpb so that the console downloads it as per custom tasks that use dll assemblies? I've tried various methods but the exe is either called via the path, not found or the console crashes.

    Cheers,

    Rob

  • @Rob -

    I personally havent spent much time trying to get that to work, but some other people in the community have and havent been able to get it going.  Do you have the source code to the .exe?  You could convert it to a class in an assembly as described above and distribute it that way instead.

  • Travis, it never ceases to amaze me how quickly you reply to questions, thank you :)

    It's not a major, it works fine running off the network. At least I now know it's not worth trying further.

  • Hi Travis,

    I have a question about the image references in Step 6. Is there a list (of some kind) available with all the icons one can use in custom tasks/forms? For instance, I'm developing a custom form and want to use the incident icon on that form...

    Regards,

    Kris

  • @Kris -

    Well, you can query the database like this:

    select * from Resource where ResourceType = 2 and Accessibility = 0

    Once you find the Resource you want you'll need to figure out what MP it is in - just look up the ManagementPackId foreign key on the ManagementPack table like this:

    select * from ManagementPack where ManagementPackId = '0A4B0FB9-202E-FE68-4F0E-995706EDB2DA'

  • Can I add Icon for the Create Task? How do I do this?

  • @Diego -

    Yes, you can.  The process is similar to this blog post but you are dealing with ConsoleTasks instead of views and folders.

    blog.scsmfaq.ch/.../custom-icons-for-views-and-folders

  • Hi,

    is it possible to open a custom form via task (instead of .net code)?