Skip to content

Create custom UserControl for SCSM 2010 SP1

Create custom UserControl for SCSM 2010 SP1 published on 29 Comments on Create custom UserControl for SCSM 2010 SP1

image

Intro

I’ve already saw many questions on forums about extending SCSM with new forms and\or custom user’s controls. So I wrote this article to show how we can extend SCSM with user control only. As for me, I prefer to use custom control without rewriting entire form because User Control provide excellent ability to change form’s logic and visibility, but they require much less work unlike the creation of new form and replace existing one. Another reason – new version of SCSM, which expected at Q4 2011 – Q1 2012. It’s a very small chance what if you rewrote entire form it will be work with SCSM 2012.

DISCLAIMER. Most of provided solutions are not supported by Microsoft. All information provided AS IS, without any warranties.

You must learn some basics about these technologies before read this article:

  • WPF (Windows Presentation Foundation), especially  a “Binding“
  • DependencyProperty
  • XML schema of management pack

To create the user control for SCSM you must accomplish these tasks:

  1. Create new User Control with Visual Studio
  2. Attach this control to existing form in SCSM
  3. Deliver assembly with control to all computer with SCSM’s console installed

Creation of custom UserControl

To create new user control you need launch Visual Studio, create new project and choose “WPF User Control Library” as project type:
image

Names of new project and solution you can choose by yourself. I’v use “SCSMControl” as an example for this article. After create new project you must change name of the class and name of the control.

Now you must add some reference to project. Right click to “Reference” in Solution Explorer and select “Add reference”:
image
Switch to “Browse” tab.

You must add next assemblies:

  1. Microsoft.EnterpriseManagement.Core.dll (located in c:\Program Files\Microsoft System Center\Service Manager 2010\SDK Binaries folder on SCSM’s server)
  2. Microsoft.EnterpriseManagement.UI.Foundation.dll (located in c:\Program Files\Microsoft System Center\Service Manager 2010\)
  3. Microsoft.EnterpriseManagement.UI.SdkDataAccess.dll (located in c:\Program Files\Microsoft System Center\Service Manager 2010\)

If you plan use the existing SCSM’s controls in your project you also may need these assemblies: Microsoft.EnterpriseManagement.UI.Controls.dll, Microsoft.EnterpriseManagement.UI.ExtendedControls.dll, Microsoft.EnterpriseManagement.UI.SMControls.dll и WPFToolKit.dll (all located in c:\Program Files\Microsoft System Center\Service Manager 2010\)

At the next step you must add ContentProperty attribute to the control’s class. These attribute use in real control to set the default property (Text for TextBox, User for UserPicker and so on). But in case of current control you can use any property as attribute value. To accomplish this task we must create new property for our class with DependencyProperty. This property we will use as value for ContentProperty attribute. Type and name of the property can be any. I’ve choose “SelectedItem” of string type. Also this property must support both public “set” and “get”.

After all modifications you must get something looking like this:

namespace SCSMControls
{
    [ContentProperty("SelectedItem")]
    public partial class SCSMControl : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        public SCSMControl()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(string), typeof(SCSMControl), new UIPropertyMetadata(null, new PropertyChangedCallback(SCSMControl.OnSelectedItemChanged)));

        public string SelectedItem
        {
            get
            {
                return (string)base.GetValue(SelectedItemProperty);
            }
            set
            {
                base.SetValue(SelectedItemProperty, value);
                NotifyPropertyChanged("SelectedItem");
            }
        }

        private static void OnSelectedItemChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            //TODO
        }

        /// <summary>
        /// INotifyPropertyChanged implementation
        /// </summary>
        /// <param name="propertyName"></param>
        private void NotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

At line 3 you can see the ContentAttribute which refers to SelectedItem property (this property defined at line 15-26).

I’ve added INotifyPropertyChanged interface. That interface provides support to handle changing value of our property by over control on form (in current project this is unnecessary but can be helpful in the future). And also I’ve show you show we can handle changing property inside of our control with PropertyChangedCallback. Both approaches added only as an example of different techniques and doesn’t require to you use them.

Technically, now we can add our control to SCSM’s form, but control still didn’t do anything useful. Let’s display some data inside this control. To do that we must use powerful mechanism of WPF: binding. But reasonable question – what is the names of properties we must use to bind?

Form’s DataContext (and therefore our control too) filled by object of IDataItem type. This type aren’t documented, so you must explore it by yourself or believe me ))) ). IDataItem object store all properties of the object for which form are opened as HashTable. You must use [] operator to access the object’s properties. As indexer you must use internal name of the property (or internal name of type projection’s component). Examples:

IDataItem item = this.DataContext as IDataItem;
string title = (string)item["Title"];
string status = (string)(item["Status"] as IDataItem)["DisplayName"];
string affectedUser = (string)(item["AffectedUser"] as IDataItem)["DisplayName"];

If property are not simple (enum, type projection  component and so on) the property value’s also return the IDataItem type (line 3, 4).

Now we can bind to object’s property. For binding you must set Path to name of property. Let’s add several controls to our control in XAML:

<TextBox Name="boxID" Grid.Column="1" Grid.Row="0" Text="{Binding Path=$Id$, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Stretch" IsReadOnly="True"  />
<TextBox Name="boxName" Grid.Column="1" Grid.Row="1" Text="{Binding Path=Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Stretch" />
<TextBox Name="boxAffectedUser" Grid.Column="1" Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Stretch" IsReadOnly="True"  >
  <TextBox.Text>
    <Binding Path="AffectedUser.DisplayName" Mode="OneWay" FallbackValue="No Affected User"/>
  </TextBox.Text>
</TextBox>
<TextBox Name="boxItem" Grid.Column="1" Grid.Row="3" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=SelectedItem}" VerticalAlignment="Center" HorizontalAlignment="Stretch" />

Here you can see several different methods of binding. In first case (line 1, 2) we bind directly to simple properties. “$Id$” is a internal id of current object (Guid). Line 3 shows you how to set text when property has no value (note: this is NOT a default value). Line 4 shows you how to bind to a control’s property.

Excellent, now our control can display some data. But often we must also changes data programmatically. IDataItem interface isn’t good candidate to manipulate the data. We must somehow got the object of type which belong to the standard SDK (as the best – EnterpiseManagementGroup) to manipulate the data. And we can do it (see GetSession function):

[ContentProperty("SelectedItem")]
public partial class SCSMControl : UserControl, INotifyPropertyChanged
{
        EnterpriseManagementGroup mg;
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        public SCSMControl()
        {
            InitializeComponent();
            GetSession();
        }

        void GetSession()
        {
            // Get the current session, more info: http://blogs.technet.com/servicemanager/archive/2010/02/11/tasks-part-1-tasks-overview.aspx
            IServiceContainer container = (IServiceContainer)FrameworkServices.GetService(typeof(IServiceContainer));
            IManagementGroupSession curSession = (IManagementGroupSession)container.GetService(typeof(IManagementGroupSession));
            if (curSession == null)
                throw new ValueUnavailableException("curSession is null");
            mg = curSession.ManagementGroup;
        }
}

Now we have access to SDK, let’s do some useful. For example, let’s set some default value for new incidents. We must set value only if form opened for creation, not for editing. And again we must get IDataItem object from DataContext, check that form opened for creation and set the values. We can’t use the “FormLoaded” event handler because when it fired the DataContext aren’t contains object of IDataItem. So we must use DataContextChanged event handler and set the values when DataContext will be contain the object of IDataItem type:

private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    // wait binding
    if (this.DataContext is IDataItem)
    {
        instance = (this.DataContext as IDataItem);
        // If this is new incident, set some default properties
        if ((bool)instance["$IsNew$"])
        {
            instance["Title"] = "WOW! Now we can set the default value for property!!!";
            instance["Description"] = "And we can use SDK. Current management group: " + mg.Name;
            //IncidentTierQueuesEnum.Tier2
            instance["TierQueue"] = mg.EntityTypes.GetEnumeration(new Guid("df3896f5-3145-0546-4d25-e485de6765af"));
        }
    }
}

“$IsNew$” property  is a “true” when form opened for creation and “false” for editing. As you can see, we can mix IDataItem and standard SDK objects to set values (line 13).

Now our control is ready to use. The link to Visual Studio 2010 project you can find at the end of this article.

How to add custom control to form

Now we must add our control to the form. But using Authoring Tool we can add only standard controls. So we must create new form customization with Authoring Tool, and then directly edit XML code of management pack with any text editor. Here are steps to do that:

  1. Create new form customization. That already described many times (here, here and even with video here). 
  2. Choose the place where you want to saw your custom control. Drag-and-Drop to that place any standard control (Lable for example)

  3. Save management pack and then open it at any text editor.
  4. Find “Forms” section and added control inside here.
  5. Replace “Assembly” and “Type” attribute of added control to the same parameters of our control. You can get “PublicKey” value with that command: 
    sn.exe –T <assembly path> (sn.exe utility located in c:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\). Also you can add this command as tool in Visual Studio to easily find Public Key.
  6. Add values for control’s property with <PropertyChange>, if necessary.

Example of modification for incident’s form:

<Form ID="CustomForm_650994d4_1a69_4f1a_975f_7d060b90a3f3" Accessibility="Public" Target="CustomForm_650994d4_1a69_4f1a_975f_7d060b90a3f3_TypeProjection" BaseForm="Alias_cf5e40e8_e299_4731_9572_41860eb78176!System.WorkItem.Incident.ConsoleForm" TypeName="Microsoft.EnterpriseManagement.ServiceManager.Incident.Forms.IncidentFormControl">
<Category>Form</Category>
<Customization>
  <AddControl Parent="StackPanel206" Assembly="PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Type="System.Windows.Controls.Label" Left="92" Top="14.2" Right="0" Bottom="0" Row="0" Column="0" />
  <PropertyChange Object="Label_1" Property="HorizontalAlignment">
	<NewValue>Left</NewValue>
  </PropertyChange>
  <PropertyChange Object="Label_1" Property="VerticalAlignment">
	<NewValue>Bottom</NewValue>
  </PropertyChange>
  <PropertyChange Object="Label_1" Property="Content">
	<NewValue>Custom control:</NewValue>
  </PropertyChange>
  <AddControl Parent="StackPanel206" Assembly="SCSMControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e2bef97f2bc99659" Type="SCSMControls.SCSMControl" Left="100.8" Top="9.40000000000003" Right="46" Bottom="0" Row="0" Column="0" />
  <PropertyChange Object="SCSMControl_1" Property="Width">
	<NewValue>Auto</NewValue>
  </PropertyChange>
  <PropertyChange Object="SCSMControl_1" Property="Height">
	<NewValue>Auto</NewValue>
  </PropertyChange>
  <PropertyChange Object="SCSMControl_1" Property="VerticalAlignment">
	<NewValue>Bottom</NewValue>
  </PropertyChange>
  <PropertyChange Object="SCSMControl_1" Property="Margin">
	<NewValue>0,0,0,0</NewValue>
  </PropertyChange>
  <PropertyChange Object="SCSMControl_1" Property="SelectedItem">
	<NewValue>Cool control</NewValue>
  </PropertyChange>
</Customization>
</Form>

Result:

image

“Title” and “Support Group” filled for new incidents:

image

Delivery assembly

Now we must copy assembly to all computers with SCSM’s console installed. It can be easily done manually when you have 3-4 computers with SCSM console. But what if we have 50 or 100 or even 500 computers? And how update our assembly later? It can be nightmare for administrator.

But the developers of SCSM took care about this. SCSM use management pack bundle to deliver the resources. This bundle may contains sealed or unsealed management packs and several types of resources such as assemblies, images, scripts and etc..

We must put reference to resource (assembly in our case) in our management pack before create the bundle. All resources added to bundle will be copy locally to computer in  %USERPROFILE%\AppData\Local\Microsoft\System Center Service Manager 2010\%SERVERNAME%\%MPVERSION% folder

where

       %SERVERNAME% – name of management server
       %MPVERSION% – version of management pack which contain the reference.

So now we must add reference for out assembly at the end of management pack, just after <LanguagePacks> section:

  </LanguagePacks>
  <!-- Section For Assembly -->
  <Resources>
    <Assembly ID="SCSMControlAssembly" Accessibility="Public" QualifiedName="SCSMControl" FileName="SCSMControl.dll" />
  </Resources>
</ManagementPack>

and then create the bundle. To create the bundle you can use PowerShell script (see link above) or my utiliy MPBMaker:

MPBMaker.exe SCSMControlBundle “d:\Examples\Example.SCSMControl.xml”

Note: in any case do not forget copy assembly to the folder with management pack

This bundle we must import to SCSM.

Troubleshooting

If you get error when importing management pack with form customization sounds like this:

: Failed to verify form: CustomForm_650994d4_1a69_4f1a_975f_7d060b90a3f3

The form base is not valid. Form CustomForm_650994d4_1a69_4f1a_975f_7d060b90a3f3 extends form System.WorkItem.Incident.ConsoleForm, which already has another extension (CustomForm_8c4ec25b_1dc3_4b58_bd43_7c8a83f619a0)

that mean what form already have been customized in over management pack. Delete that MP from SCSM and reimport your MP.

If your control look like this:

image

then you must check the existing of ContentProperty attribute.

As summary

With custom control you can easily do many things:

  • Set the default values
  • Change the behavior of over control on form (enable or disable validation check for example)
  • Disable or hide over controls based on user’s rights or based on properties of item

Read-to-use Visual Studio 2010 project, management pack and bundle you can download here.

Share

%d bloggers like this: