Part 6 – create a property page

Creating an extension to list collections a device is a member of in a messageBox is nice, but why don’t we try to integrate things a little bit more?

Wouldn’t it be better to get this list directly in a new tab of the native property windows?

Re-open your visual studio project and add a new project (file/new/project..), choose “Windows Form Control Library” as the project type and give it a nice name. Here I will call it “devCollMemberTab”

Now you’re face to a blank control….. First, start to give it a new and more accurate size ( i.e. 380×350);
Add a tableLayoutPanel and change some of its properties:

  • dock: fill
  • columnCount: 1

Move the separator a little bit up then add two new controls: a button on the top cell and a listBox in the bottom cell.

Some adjustments
– for the button: name it “btnSearch”, set the anchor property to “none” and change his “Text” property to something like “Search for collections”.
– for the listBox: name it “lbCollections” and set the dock property to “Fill”

Your brand new control should normally look like this :

Switch back to the code.

Rename the file UserControl1.cs to something more understandable : devCollMemberTab.cs
and rename the class to devCollMemberTab (right click on the class name => rename)

The namespace is currently “devCollMemberTab”. Change it to something different to ensure there will not be any name collision: i.e. “MyCompany.devCollMemberTab”.
Don’t forget to reproduce this correction in devCollMemberTab.designer.cs also.

Add references to the following files:
– microsoft.configurationmanagement.exe
– Microsoft.ConfigurationManagement.DialogFramework.dll
– Microsoft.configurationmanagement.managementprovider.dll

Go back to devCollMemberTab.cs and add “using Microsoft.ConfigurationManagement.ManagementProvider;” to the using section.

Last modifications before start the real work 😉 , change the inherited class to “SmsPageControl” instead of the default “UserControl” and correct the constructo as follow:

Public devCollMemberTab(SmsPageData pageData) : base(pageData)

Finally add a new function to initialize the control:

public override void InitializePageControl()
{
   base.InitializePageControl();
}

Now your code must look like this

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.ConfigurationManagement.ManagementProvider;

namespace Mycompany.devCollMemberTab
{
   public partial class devCollMemberTab: SmsPageControl
   {
      public devCollMemberTab(SmsPageData pageData) : base(pageData)
      {
         InitializeComponent();
      }

      public override void InitializePageControl()
      {
         base.InitializePageControl();
      }
      private void btnSearch_Click(object sender, EventArgs e)
      {

      }
   }
}

Congratulations! you have a beautiful SMSPage control! useless, but beautiful!

Now let’s make it do what we need.

Quick and easy! Just add the following code to the “btnSearch_click” function:

lbCollections.Items.Clear();

string resourceid = PropertyManager["ResourceID"].StringValue;
IResultObject collectionList = base.QueryProcessor.ExecuteQuery("SELECT c.CollectionID,c.Name FROM SMS_FullCollectionMembership fcm inner join SMS_Collection c on fcm.CollectionID=c.CollectionID where fcm.ResourceID =" + resourceid);
foreach (IResultObject collID in collectionList)
{
   lbCollections.Items.Add(collID.Properties["Name"].StringValue+" ("+ collID.Properties["CollectionID"].StringValue+")");
}

Two new things here :
– the “PropertyManager” is a class member of SMSPageControl that contains the properties of the selected object.
– no need to work hard to find the connectionManager object. As our control will be launch directly inside the console, the base class contains two members named “QueryProcessor” and “ConnectionManager” already initialized.

Build it! ( don’t forget to set the target architecture to x86 as seen in part 2)

The Output Dll file needs to be integrated into the console. As always, this means to create an XML console extension file.
In fact, this is not exactly a creation. this file is already present in the console program files. We have to duplicate it to a specific folder ( if this has not been done by another 3rd party extension) and modify this copy to include our Page.

Search your console installation folder for a file named “CollectionResource.xml”. (should be located in “\Program Files (x86)\Microsoft Configuration Manager\XmlStorage\Forms”)  and copy it to “XmlStorage\Extensions\Forms” folder.

If the target file already exists, take care to not overwrite it as you may break a 3rd party tool already installed.

Add the following line at the end of the “<Page>” group ( between “<Form>” & </Form>”).

<Page xmlns="http://schemas.microsoft.com/SystemsManagementServer/2005/03/ConsoleFramework" VendorId="me" Id="{Generate your own GUID}" Assembly="C:\path_to_my_dll\deviceCollectionMembershipProp.dll" Namespace="Microsoft.ConfigurationManagement.AdminConsole.devCollMemberTab" Type="devCollMemberTab" ReadOnly="false" MultiSelection="false" Visible="false"/>

The Namespace must reflect the one in your source code and Type should be your class name.
( reminder: the GUID can be generated in Visual Studio in the “Tools” menu => “Create GUID”, then option 4: “Registry Format”)

Save the modification and restart the console. it should work!

Short advice: add a function to retrieve the collection location in the folder tree on a double-click ^^
something like :

private void lstCollections_MouseDoubleClick(object sender, MouseEventArgs e)
{
   //retrieve the collection number and call the locate sub function
   Regex r = new Regex(@"\(([a-zA-Z0-9]*)\)$");
   Match colidmatch = r.Matches((string)lstCollections.SelectedItem)[0];
   if(colidmatch.Groups.Count>=1)
      showCollectionLocation(colidmatch.Groups[1].Captures[0].Value);
}

private void showCollectionLocation(string collid)
{
   IResultObject containerList = base.QueryProcessor.ExecuteQuery("SELECT ContainerNodeID FROM SMS_ObjectContainerItem where InstanceKey ='" + collid+"'");
   string path = "";
   foreach (IResultObject container in containerList)
   {
      string containernodeid = container.Properties["ContainerNodeID"].StringValue;
      string curpath = "";
      while (containernodeid != "0")
     {
         IResultObject node = base.ConnectionManager.GetInstance("SMS_ObjectContainerNode.ContainerNodeID=" + containernodeid);
         curpath = "/"+node.Properties["name"].StringValue + curpath;
         containernodeid = node.Properties["ParentContainerNodeID"].StringValue;
     }
     path += curpath + "\r\n";
   }
   MessageBox.Show( path, "Path to " + collid, MessageBoxButtons.OK, MessageBoxIcon.Information);
}

As always, this is surely not the most optimized solution, but it works!

Edit : Since SCCM Current branch, there is a new property on objects named “ObjectPath” that makes the above function useless. Just add the “c.ObjectPath” to your query and add it directly into your result box.

Leave a Reply

Your email address will not be published. Required fields are marked *