{"id":23,"date":"2023-03-21T03:28:30","date_gmt":"2023-03-21T03:28:30","guid":{"rendered":"https:\/\/brunocm.azurewebsites.net\/?page_id=23"},"modified":"2025-10-09T14:13:00","modified_gmt":"2025-10-09T12:13:00","slug":"part-6-create-a-property-page","status":"publish","type":"page","link":"https:\/\/brunocm.azurewebsites.net\/?page_id=23","title":{"rendered":"Part 6 &#8211; create a property page"},"content":{"rendered":"<p>Creating an extension to list collections a device is a member of in a messageBox is nice, but why don&#8217;t we try to integrate things a little bit more?<\/p>\n<p>Wouldn&#8217;t it be better to get this list directly in a new tab of the native property windows?<\/p>\n<p>Re-open your visual studio project and add a new project (file\/new\/project..), choose &#8220;Windows Form Control Library&#8221; as the project type and give it a nice name. Here I will call it &#8220;devCollMemberTab&#8221;<\/p>\n<p>Now you&#8217;re face to a blank control&#8230;.. First, start to give it a new and more accurate size ( i.e. 380&#215;350);<br \/>\nAdd a tableLayoutPanel and change some of its properties:<\/p>\n<ul>\n<li>dock: fill<\/li>\n<li>columnCount: 1<\/li>\n<\/ul>\n<p>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.<\/p>\n<p>Some adjustments<br \/>\n&#8211; for the button: name it &#8220;btnSearch&#8221;, set the anchor property to &#8220;none&#8221; and change his &#8220;Text&#8221; property to something like &#8220;Search for collections&#8221;.<br \/>\n&#8211; for the listBox: name it &#8220;lbCollections&#8221; and set the dock property to &#8220;Fill&#8221;<\/p>\n<p>Your brand new control should normally look like this :<br \/>\n<img decoding=\"async\" loading=\"lazy\" class=\"alignnone wp-image-64 size-full\" src=\"\/wp-content\/uploads\/2023\/03\/image25.png\" alt=\"\" width=\"400\" height=\"370\" srcset=\"\/wp-content\/uploads\/2023\/03\/image25.png 400w, \/wp-content\/uploads\/2023\/03\/image25-300x278.png 300w\" sizes=\"(max-width: 400px) 85vw, 400px\" \/><\/p>\n<p>Switch back to the code.<\/p>\n<p>Rename the file UserControl1.cs to something more understandable :\u00a0devCollMemberTab.cs<br \/>\nand rename the class to\u00a0devCollMemberTab (right click on the class name =&gt; rename)<\/p>\n<p>The namespace is currently \u201cdevCollMemberTab\u201d. Change it to\u00a0something different to ensure there will not be any name collision: i.e.\u00a0\u201cMyCompany.devCollMemberTab\u201d.<br \/>\nDon&#8217;t forget to reproduce this correction in\u00a0devCollMemberTab.designer.cs also.<\/p>\n<p>Add references to the following files:<br \/>\n&#8211; microsoft.configurationmanagement.exe<br \/>\n&#8211; Microsoft.ConfigurationManagement.DialogFramework.dll<br \/>\n&#8211; Microsoft.configurationmanagement.managementprovider.dll<\/p>\n<p>Go back to devCollMemberTab.cs\u00a0and add &#8220;using Microsoft.ConfigurationManagement.ManagementProvider;&#8221; to the using section.<\/p>\n<p>Last modifications before start the real work \ud83d\ude09 ,\u00a0change the inherited class to &#8220;SmsPageControl&#8221; instead of the default &#8220;UserControl&#8221; and correct the constructo as follow:<\/p>\n<p>Public devCollMemberTab(SmsPageData pageData) : base(pageData)<\/p>\n<p>Finally add a new function to initialize the control:<\/p>\n<pre><span style=\"color: #0000ff;\">public override void<\/span> InitializePageControl()\n{\n   <span style=\"color: #0000ff;\">base<\/span>.InitializePageControl();\n}<\/pre>\n<p>Now your code must look like this<\/p>\n<pre><span style=\"color: #00ccff;\">using<\/span> System;\n<span style=\"color: #00ccff;\">using<\/span> System.Collections.Generic;\n<span style=\"color: #00ccff;\">using<\/span> System.ComponentModel;\n<span style=\"color: #00ccff;\">using<\/span> System.Drawing;\n<span style=\"color: #00ccff;\">using<\/span> System.Data;\n<span style=\"color: #00ccff;\">using<\/span> System.Linq;\n<span style=\"color: #00ccff;\">using<\/span> System.Text;\n<span style=\"color: #00ccff;\">using<\/span> System.Threading.Tasks;\n<span style=\"color: #00ccff;\">using<\/span> System.Windows.Forms;\n<span style=\"color: #00ccff;\">using<\/span> Microsoft.ConfigurationManagement.ManagementProvider;\n\n<span style=\"color: #0000ff;\">namespace<\/span> Mycompany.devCollMemberTab\n{\n<span style=\"color: #0000ff;\">   public partial class<\/span> <span style=\"color: #33cccc;\">devCollMemberTab: SmsPageControl<\/span>\n   {\n<span style=\"color: #0000ff;\">      public<\/span> devCollMemberTab(SmsPageData pageData) : <span style=\"color: #0000ff;\">base<\/span>(pageData)\n      {\n         InitializeComponent();\n      }\n\n<span style=\"color: #0000ff;\">      public override void<\/span> InitializePageControl()\n      {\n         <span style=\"color: #0000ff;\">base<\/span>.InitializePageControl();\n      }\n<span style=\"color: #0000ff;\">      private void<\/span> btnSearch_Click(<span style=\"color: #0000ff;\">object<\/span> sender, <span style=\"color: #33cccc;\">EventArgs<\/span> e)\n      {\n\n      }\n   }\n}<\/pre>\n<p>Congratulations! you have a beautiful SMSPage control! useless, but beautiful!<\/p>\n<p>Now let&#8217;s make it do what we need.<\/p>\n<p>Quick and easy! Just add the following code to the &#8220;btnSearch_click&#8221; function:<\/p>\n<pre>lbCollections.Items.Clear();\n\n<span style=\"color: #0000ff;\">string<\/span> resourceid = PropertyManager[<span style=\"color: #993300;\">\"ResourceID\"<\/span>].StringValue;\n<span style=\"color: #99cc00;\">IResultObject<\/span> collectionList = <span style=\"color: #0000ff;\">base<\/span>.QueryProcessor.ExecuteQuery(<span style=\"color: #993300;\">\"SELECT c.CollectionID,c.Name FROM SMS_FullCollectionMembership fcm inner join SMS_Collection c on fcm.CollectionID=c.CollectionID where fcm.ResourceID =\"<\/span> + resourceid);\n<span style=\"color: #0000ff;\">foreach<\/span> (<span style=\"color: #99cc00;\">IResultObject<\/span> collID <span style=\"color: #0000ff;\">in<\/span> collectionList)\n{\n   lbCollections.Items.Add(collID.Properties[<span style=\"color: #993300;\">\"Name\"<\/span>].StringValue+<span style=\"color: #993300;\">\" (\"<\/span>+ collID.Properties[<span style=\"color: #993300;\">\"CollectionID\"<\/span>].StringValue+<span style=\"color: #993300;\">\")\"<\/span>);\n}<\/pre>\n<p>Two\u00a0new things here :<br \/>\n&#8211; the &#8220;PropertyManager&#8221; is a class member of SMSPageControl that contains the properties of the selected object.<br \/>\n&#8211; 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 &#8220;QueryProcessor&#8221; and &#8220;ConnectionManager&#8221; already initialized.<\/p>\n<p>Build it! ( don&#8217;t forget to set the target architecture to x86 as seen in part 2)<\/p>\n<p>The Output Dll file needs to be integrated into the console. As always, this means to create an XML console extension file.<br \/>\nIn 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.<\/p>\n<p>Search your console installation folder for a file named &#8220;CollectionResource.xml&#8221;. (should be located in &#8220;\\Program Files (x86)\\Microsoft Configuration Manager\\XmlStorage\\Forms&#8221;)\u00a0 and copy it to &#8220;XmlStorage\\<strong>Extensions<\/strong>\\Forms&#8221; folder.<\/p>\n<p>If the target file already exists, take care to not overwrite it as you may break a 3rd party tool already installed.<\/p>\n<p>Add the following line at the end of the &#8220;&lt;Page&gt;&#8221; group ( between &#8220;&lt;Form&gt;&#8221; &amp; &lt;\/Form&gt;&#8221;).<\/p>\n<pre>&lt;Page xmlns=\"http:\/\/schemas.microsoft.com\/SystemsManagementServer\/2005\/03\/ConsoleFramework\" VendorId=\"me\" Id=\"{<strong><em>Generate your own GUID<\/em><\/strong>}\" Assembly=\"C:\\path_to_my_dll\\deviceCollectionMembershipProp.dll\" Namespace=\"Microsoft.ConfigurationManagement.AdminConsole.devCollMemberTab\" Type=\"<strong>devCollMemberTab<\/strong>\" ReadOnly=\"false\" MultiSelection=\"false\" Visible=\"false\"\/&gt;<\/pre>\n<p>The Namespace must reflect the one in your source code and Type should be your class name.<br \/>\n( reminder: the GUID can be generated in Visual Studio in the &#8220;Tools&#8221; menu =&gt; &#8220;Create GUID&#8221;, then option 4: &#8220;Registry Format&#8221;)<\/p>\n<p>Save the modification and restart the console. it should work!<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-medium wp-image-65\" src=\"\/wp-content\/uploads\/2023\/03\/proppage-293x300.png\" alt=\"\" width=\"293\" height=\"300\" srcset=\"\/wp-content\/uploads\/2023\/03\/proppage-293x300.png 293w, \/wp-content\/uploads\/2023\/03\/proppage.png 654w\" sizes=\"(max-width: 293px) 85vw, 293px\" \/><\/p>\n<p>Short advice: add a function to retrieve the collection location in the folder tree on a double-click ^^<br \/>\nsomething like :<\/p>\n<pre><span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">private<\/span> <span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">void<\/span><span style=\"font-family: Consolas; font-size: small;\"> lstCollections_MouseDoubleClick(<\/span><span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">object<\/span><span style=\"font-family: Consolas; font-size: small;\"> sender, <\/span><span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">MouseEventArgs<\/span><span style=\"font-family: Consolas; font-size: small;\"> e)<\/span>\n{\n   <span style=\"color: #008000; font-family: Consolas; font-size: small;\">\/\/retrieve the collection number and call the locate sub function<\/span>\n<span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">   Regex<\/span><span style=\"font-family: Consolas; font-size: small;\"> r = <\/span><span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">new<\/span> <span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">Regex<\/span><span style=\"font-family: Consolas; font-size: small;\">(<\/span><span style=\"color: #800000; font-family: Consolas; font-size: small;\">@\"\\(([a-zA-Z0-9]*)\\)$\"<\/span><span style=\"font-family: Consolas; font-size: small;\">);<\/span>\n<span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">   Match<\/span><span style=\"font-family: Consolas; font-size: small;\"> colidmatch = r.Matches((<\/span><span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">string<\/span><span style=\"font-family: Consolas; font-size: small;\">)lstCollections.SelectedItem)[0];<\/span>\n<span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">   if<\/span><span style=\"font-family: Consolas; font-size: small;\">(colidmatch.Groups.Count&gt;=1)<\/span>\n      showCollectionLocation(colidmatch.Groups[1].Captures[0].Value);\n}\n\n<span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">private<\/span> <span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">void<\/span><span style=\"font-family: Consolas; font-size: small;\"> showCollectionLocation(<\/span><span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">string<\/span><span style=\"font-family: Consolas; font-size: small;\"> collid)<\/span>\n{\n<span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">   IResultObject<\/span><span style=\"font-family: Consolas; font-size: small;\"> containerList = <\/span><span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">base<\/span><span style=\"font-family: Consolas; font-size: small;\">.QueryProcessor.ExecuteQuery(<\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"SELECT ContainerNodeID FROM SMS_ObjectContainerItem where InstanceKey ='\"<\/span><span style=\"font-family: Consolas; font-size: small;\"> + collid+<\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"'\"<\/span><span style=\"font-family: Consolas; font-size: small;\">);<\/span>\n<span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">   string<\/span><span style=\"font-family: Consolas; font-size: small;\"> path = <\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"\"<\/span><span style=\"font-family: Consolas; font-size: small;\">;<\/span>\n<span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">   foreach<\/span><span style=\"font-family: Consolas; font-size: small;\"> (<\/span><span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">IResultObject<\/span><span style=\"font-family: Consolas; font-size: small;\"> container <\/span><span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">in<\/span><span style=\"font-family: Consolas; font-size: small;\"> containerList)<\/span>\n   {\n<span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">      string<\/span><span style=\"font-family: Consolas; font-size: small;\"> containernodeid = container.Properties[<\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"ContainerNodeID\"<\/span><span style=\"font-family: Consolas; font-size: small;\">].StringValue;<\/span>\n<span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">      string<\/span><span style=\"font-family: Consolas; font-size: small;\"> curpath = <\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"\"<\/span><span style=\"font-family: Consolas; font-size: small;\">;<\/span>\n<span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">      while<\/span><span style=\"font-family: Consolas; font-size: small;\"> (containernodeid != <\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"0\"<\/span><span style=\"font-family: Consolas; font-size: small;\">)<\/span>\n     {\n<span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">         IResultObject<\/span><span style=\"font-family: Consolas; font-size: small;\"> node = <\/span><span style=\"color: #0000ff; font-family: Consolas; font-size: small;\">base<\/span><span style=\"font-family: Consolas; font-size: small;\">.ConnectionManager.GetInstance(<\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"SMS_ObjectContainerNode.ContainerNodeID=\"<\/span><span style=\"font-family: Consolas; font-size: small;\"> + containernodeid);<\/span>\n<span style=\"font-family: Consolas; font-size: small;\">         curpath = <\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"\/\"<\/span><span style=\"font-family: Consolas; font-size: small;\">+node.Properties[<\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"name\"<\/span><span style=\"font-family: Consolas; font-size: small;\">].StringValue + curpath;\n         <\/span><span style=\"font-family: Consolas; font-size: small;\">containernodeid = node.Properties[<\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"ParentContainerNodeID\"<\/span><span style=\"font-family: Consolas; font-size: small;\">].StringValue;<\/span>\n     }\n<span style=\"font-family: Consolas; font-size: small;\">     path += curpath + <\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"\\r\\n\"<\/span><span style=\"font-family: Consolas; font-size: small;\">;<\/span>\n   }\n<span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">   MessageBox<\/span><span style=\"font-family: Consolas; font-size: small;\">.Show( path, <\/span><span style=\"color: #a31515; font-family: Consolas; font-size: small;\">\"Path to \"<\/span><span style=\"font-family: Consolas; font-size: small;\"> + collid, <\/span><span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">MessageBoxButtons<\/span><span style=\"font-family: Consolas; font-size: small;\">.OK, <\/span><span style=\"color: #2b91af; font-family: Consolas; font-size: small;\">MessageBoxIcon<\/span><span style=\"font-family: Consolas; font-size: small;\">.Information);<\/span>\n}<\/pre>\n<p>As always, this is surely not the most optimized solution, but it works!<\/p>\n<p>Edit : Since SCCM Current branch, there is a new property on objects named &#8220;ObjectPath&#8221; that makes the above function useless. Just add the &#8220;c.ObjectPath&#8221; to your query and add it directly into your result box.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Creating an extension to list collections a device is a member of in a messageBox is nice, but why don&#8217;t we try to integrate things a little bit more? Wouldn&#8217;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 &hellip; <a href=\"https:\/\/brunocm.azurewebsites.net\/?page_id=23\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Part 6 &#8211; create a property page&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"open","ping_status":"closed","template":"","meta":{"_sitemap_exclude":false,"_sitemap_priority":"","_sitemap_frequency":""},"_links":{"self":[{"href":"https:\/\/brunocm.azurewebsites.net\/index.php?rest_route=\/wp\/v2\/pages\/23"}],"collection":[{"href":"https:\/\/brunocm.azurewebsites.net\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/brunocm.azurewebsites.net\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/brunocm.azurewebsites.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/brunocm.azurewebsites.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=23"}],"version-history":[{"count":1,"href":"https:\/\/brunocm.azurewebsites.net\/index.php?rest_route=\/wp\/v2\/pages\/23\/revisions"}],"predecessor-version":[{"id":86,"href":"https:\/\/brunocm.azurewebsites.net\/index.php?rest_route=\/wp\/v2\/pages\/23\/revisions\/86"}],"wp:attachment":[{"href":"https:\/\/brunocm.azurewebsites.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=23"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}