System Center Operations Manager (SCOM) Knowledge Article Extraction Tool (with MAML to HTML transform)

This article describes a simple .Net command line program that can be used to extract System Center Operations Manager (SCOM) R2 Knowledge Articles from a Management Pack into an HTML file. The program utilizes an XLST transform that converts the Knowledge Article MAML into HTML.

Have you ever been working on a Management Pack and wanted to have an application SME review all the knowledge articles in your new MP? Or maybe you would like to learn more about the internals of a vendor supplied MP by reading the Knowledge Articles they have included in the MP? Having a tool that would dump all the Knowledge Articles into a basic HTML document would make these scenarios simpler.

There's probably a tool like this out there somewhere already, but I have been unable to find it.  So I endeavored to build one. Links to the Visual Studio project and the compiled program are included at the end of the article.

The tool is a simple command line program.  It takes two arguments. The first specifies the input management pack file (either .xml or .mp format). The second is the output file name. The output will be a text file containing the HTML for the extracted Knowledge Articles.

For example:

GetKnowledge.exe Microsoft.SQLServer.2000.Monitoring.mp c:\SqlMpKnowledge.html

Implementation Details

The program is written in C# and was developed using Visual Studio 2010.

As you probably know, using the SCOM SDK, it's easy to extract the MAML for MP Knowledge Articles.  You might even have seen the sample code in the reference page for ManagementPackKnowledgeArticle.MamlContent property. It shows how to access the property, but right where the XSLT transform should be there's a mysterious comment: "Get the MAML content. You must write an XSLT to transform the MAML to HTML." Then you hunt for an XSLT transform, and find none.  My mission here is to fill that void by providing a simple tool based on this sample code that includes an actual working XSLT transform for MAML to HTLM.

The XSLT transform I created is quite basic and is not guaranteed to process all known MAML. In fact, it only processes the MAML fragments that I have found in actual management pack Knowledge Articles.  Over time, you should expect to have to evolve this transform, but for now it seems to work on all of the Microsoft provided Management Packs that I have tried it on.

The program extracts all Knowledge articles for rules and monitors in the specified Management Pack file, and outputs them as an HTML document.  It should be pretty easy to extend the program to output Knowledge Articles for other MP elements, but at the time I wrote the program, I was only interested in Rules and Monitors.

The generated HTML is very basic and should be easy to enhance either through extension of the program or via direct editing in an HTML editor. It is also straight forward to copy / paste from the browser into a Word document for more extensive formatting.

The XSLT transform code is embedded as a resource in the .Net program (and I've included it in the text of this article).

The program references SCOM .Net assemblies Microsoft.EnterpriseManagement.OperationsManager and Microsoft.EnterpriseManagement.OperationsManager.Common. It should run on computers where the Operations Console or the Authoring Console are been installed.

Here is the XSLT Transform:

<?xml version="1.0" encoding="utf-8"?>

 <xsl:stylesheet version="1.0" xmlns:xsl="https://www.w3.org/1999/XSL/Transform"

                           xmlns:msxsl="urn:schemas-microsoft-com:xslt"

                           xmlns:maml="https://schemas.microsoft.com/maml/2004/10"

                           exclude-result-prefixes="msxsl maml">

       <xsl:output method="html" indent="no"/>

        <xsl:template match="/root">

              <xsl:apply-templates />

       </xsl:template>

        <xsl:template match="maml:section">

              <xsl:apply-templates />

       </xsl:template>

        <xsl:template match="maml:navigationLink">

              <a>

                     <xsl:attribute name="href">

                           <xsl:value-of select="maml:uri/@href" />

                     </xsl:attribute>

                     <xsl:value-of select="maml:linkText"/>

              </a>

       </xsl:template>

   

       <xsl:template match="maml:listItem">

              <li>

                     <xsl:apply-templates />

              </li>

       </xsl:template>

     

       <xsl:template match="maml:list">

              <ul>

                     <xsl:apply-templates />

              </ul>

       </xsl:template>

     

       <xsl:template match="maml:title">

              <h2>

                     <xsl:value-of select="."/>

              </h2>

       </xsl:template>

 

       <xsl:template match="text()">

              <xsl:value-of select="."/>

       </xsl:template>

 

       <xsl:template match="maml:para">

              <p>

                     <xsl:apply-templates />

              </p>

       </xsl:template>

 

       <xsl:template match="maml:ui">

              <xsl:choose>

                     <xsl:when test="normalize-space(.)">

                           <span style="text-decoration:underline">

                                  <xsl:apply-templates />

                           </span>

                     </xsl:when>

                     <xsl:otherwise>

                     </xsl:otherwise>

              </xsl:choose>

       </xsl:template>

 

       <xsl:template match="maml:entry">

              <td>

                     <xsl:apply-templates />

              </td>

       </xsl:template>

 

       <xsl:template match="maml:row">

              <tr>

                     <xsl:apply-templates />

              </tr>

       </xsl:template>

 

       <xsl:template match="maml:table">

              <table  border="1">

                     <xsl:apply-templates />

              </table>

       </xsl:template>

      

</xsl:stylesheet>

 

 

Here is the c# code:

 

using System;

using System.Text;

using System.Collections.Generic;

using Microsoft.EnterpriseManagement;

using Microsoft.EnterpriseManagement.Common;

using Microsoft.EnterpriseManagement.Administration;

using Microsoft.EnterpriseManagement.Configuration;

using System.Collections.ObjectModel;

using System.IO;

using System.Xml;

using System.Xml.Xsl;

using System.Reflection;

 

 

namespace GetKnowledge

{

    class Program

    {

        static void TransformMamlFragmentToHtml(string mamlContent, StreamWriter writer)

        {

            // The maml from OpsMgr has no root node, so one must be added or else the xml is not valid.

            mamlContent = "<root>" + mamlContent + "</root>";

 

            // Wrap the maml string in a reader that is usable by the transform

            using (XmlReader doc = XmlReader.Create(new System.IO.StringReader(mamlContent)))

            {

                // Get the transfrom from a string resource in this assembly

                using (System.IO.Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream("GetKnowledge.KATransform.xslt"))

                {

                    using (System.Xml.XmlTextReader xsltReader = new System.Xml.XmlTextReader(s))

                    {

                        // Load the transform

                        XslCompiledTransform xslt = new XslCompiledTransform();

                        xslt.Load(xsltReader);

 

                        // Setup for writing the results

                        XmlWriterSettings settings = new XmlWriterSettings();

                        settings.ConformanceLevel = ConformanceLevel.Fragment;

 

                        // Do the transform

                        xslt.Transform(doc, XmlWriter.Create(writer, settings));

                    }

                }

            }

        }

 

        static void Main(string[] args)

        {

            if (args.GetLength(0) != 2)

            {

                Console.WriteLine("Error: unexpected commandline argument count. Usage: GetKnowledge <mp file> <outputfile>");

                System.Environment.Exit(1);

            }

 

            ManagementPack mp = null;

 

            try

            {

                // Load the MP from the file specified in the argument list.

                mp = new ManagementPack(args[0]);

            }

            catch (System.Exception excp)

            {

                Console.WriteLine("Error: Could not load the management pack: {0}", excp.Message);

                System.Environment.Exit(1);

            }

 

            // A hint of code that could be used to load the MP from management group instead of from the file

            //ManagementGroup mg = new ManagementGroup("localhost");

            //ManagementPack mp = mg.GetManagementPacks(args[0])[0];

 

            ManagementPackElementCollection<ManagementPackRule> rules;

            ManagementPackElementCollection<ManagementPackMonitor> monitors;

 

            rules = mp.GetRules();

            monitors = mp.GetMonitors();

 

            StreamWriter writer = null;

 

            try

            {

                writer = new StreamWriter(args[1]);

            }

            catch (System.Exception excp)

            {

                Console.WriteLine("Error: Could not create output file: {0}", excp.Message);

                System.Environment.Exit(1);

            }

 

            // Write out the HTML page header section

            writer.WriteLine("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");

            writer.WriteLine("<html xmlns=\"https://www.w3.org/1999/xhtml\" >");

            writer.WriteLine("<head>");

            writer.WriteLine("<title>Untitled Page</title>");

            writer.WriteLine("</head>");

 

            writer.WriteLine("<body>");

 

            // Output Knowledge article for all rules

            foreach (ManagementPackRule rule in rules)

            {

                ManagementPackKnowledgeArticle article;

                try

                {

                    article = rule.GetKnowledgeArticle(System.Globalization.CultureInfo.CurrentCulture);

                }

                catch (Microsoft.EnterpriseManagement.Common.ObjectNotFoundException error)

                {

                    //There is no knowledge article for this rule.

                    continue;

                }

                if (article != null)

                {

                    if (article.HtmlContent != null)

                    {

                        //Get the HTML content.

                    }

                    else if (article.MamlContent != null)

                    {

                        writer.WriteLine("<h1>Rule: {0}</h1>", rule.DisplayName);

                        TransformMamlFragmentToHtml(article.MamlContent, writer);

                    }

                }

            }

 

            // Output knowledge article for all monitors

            foreach (ManagementPackMonitor monitor in monitors)

            {

                ManagementPackKnowledgeArticle article;

                try

                {

                    article = monitor.GetKnowledgeArticle(System.Globalization.CultureInfo.CurrentCulture);

                }

                catch (Microsoft.EnterpriseManagement.Common.ObjectNotFoundException error)

                {

                    //There is no knowledge article for this monitor.

                    continue;

                }

                if (article != null)

                {

                    if (article.HtmlContent != null)

                    {

                        //Get the HTML content.

                    }

                    else if (article.MamlContent != null)

                    {

                        writer.WriteLine("<h1>Monitor: {0}</h1>", monitor.DisplayName);

                        TransformMamlFragmentToHtml(article.MamlContent, writer);

                    }

                }

            }

 

            writer.WriteLine("</body>");

            writer.WriteLine("</html>");

            writer.Close();

 

            Console.WriteLine("All done!");

        }

    }

}

 

 

A zip file containing the Visual Studio 2010 project can be downloaded from here: https://blogs.msdn.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-01-03-40-SCOM+Knowledge+Extraction/7624.GetKnowledgeProject.zip

A zip file containing the program executable can be downloaded from here:
https://blogs.msdn.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-01-03-40-SCOM+Knowledge+Extraction/4162.GetKnowledgeExecutable.zip