Wednesday, September 04, 2013

PowerShell Reporting With XML– Resources

 

This page will hopefully be a collecting point for useful PowerShell XML resources.

Jeffery Hicks aka The Lonely Administrator and Professor PowerShell has a few good blogs and articles this month on XML in PowerShell.  I recommend checking them out if you intend to use XML.

XML Marks the Spot Part 1: Conversion

Be sure to check the resource for XML on W3C.org and on W3Schools.com.

Sapien has an excellent text editor for XML and XSL: PrimalXML

Microsoft has a free but very basic XML editor: XML Notepad

Of course if you are a real pro and need to consume a lot of XML then there is Altova: XMLSpy

Monday, September 02, 2013

PowerShell Reporting With XML– Part 1

With PowerShell we are able to easily generate HTML from objects and, with some amount of adjusting, publish presentable reports.  This post is not about html publishing from PowerShell.  It is about exporting information as XML and then using PowerShell to generate a conversion to another format using XSLT in numerous ways.

What is XSLT?

XSLT stands for eXtensible StyleSheet Language Transormation.  It is a declaritive XML extension.  XML is the “eXensible” in XML and XSL/XSLT.  XSL is the XML specification for the  language used to manipulate XML data.  It can manipulate any XML variation including XSL.

Background Definitions:

W3C:

W3Schools:

PowerShell and XML

There are numerous examples and tutorials on the web.  The above information I but a very small part of what is available so why add more information and tutorials?

PowerShell can an does natively consume and emit XML.  Under the covers Microsoft Windows consumes and emits XML nearly everywhere.  Nearly all tools and tool products for Windows are XML based to some degree.  PowerShell support files are all XML.

Given the amount of XML everywhere it should be easy to generate reports that are readable and printable using the XML, but, in PowerShell there are no tools for doing this.  We will build some simple tools that will makes access to this XML data much easier.

CovertTo-Xml – A Simple XML Generator

http://technet.microsoft.com/library/hh849889.aspx

This CmdLet takes an object and converts it to XML.  By default it converts all top level properties of any object.

Try it:

$xml=get-process | ConvertTo-Xml
$xml.Objects.Object.Property

This will show you how the object is constructed.  The basic wrapper looks like this:

<Objects>
     <Object>
       <Property ..../>
        <Property ..../>
    </Object>
    ...
</Objects>

What can we do with this?

PowerShell can convert this into any other format required very easily by treating it as a collection of objects.  We can also just turn it directly into a report that can be published on a web site or sent by email.  We can do this without manipulating strings or using the PowerShell ConvertTo-Html which has many limitations when we need to produce complex dynamic reports.  An XSLT will allow us to merge two powerful tools into a solution.

The Code

# generate XML
$xml=get-process | ConvertTo-Xml
$xml.Save("$pwd\processes.xml")
 
#transform to HTML report
$xslt=New-Object System.Xml.Xsl.XslCompiledTransform
$xslt.Load("$pwd\processes.xsl")
$xslt.Transform("$pwd\processes.xml","$pwd\processes.html") 

That is all of the code it takes to dump a collection of objects mand run a transform to produse an HTML report.

What is the Advantage?

The advantage is that we can dump as many collections of XML objects as we need in out report and run one transform – the same transform as above – just that few lines of code.

Yes.  We can add as many XML data dumps as we need to gather the data for our report or reports.  We can deign a reporting system that just runs simple data dumps and not have to be concerned about how the data can be combined. The data can be combined in PowerShell or dumped independently.  It all depends on what our reporting system needs to consume.  What we buy is that the reporting system does not have to be data smart.  It just has to consume and transform XML.

Other Advantages

There are almost an unlimited number of other advantages to using XML/XSLT as the presentation tool.  Here are a few:

  1. We can easily inject extra info like table name or report name.
  2. XML can be converted to almost any other format for use by external systems
  3. XSL can generate dynamic web pages.
  4. XML can be reused in different reports and different sections.
  5. XML can be used to generate tables and lists with collapsible elements
  6. ….More..

I am not recommending that all reporting should be done this way.  For many simple reports just dumping text or direct use of would be more convenient.  Your choice of XML will depend on what we discuss in the next few issues of this set of blog posts.

Sunday, September 01, 2013

PowerShell Reporting With XML– Part 2

In this part I will show how to inject the XSLT into the XML document so it can be directly viewed in a browser.  This is a very simple operation and takes almost no code.

How to inject an XSLT reference into an XML file

.First we generate of load XML into an XML object:

$xmlfile="$pwd\services.xml"
$xml=gwmi win32_service | ConvertTo-Xml
$xml.Save($xmlfile)
. $xmlfile

This should open a browser and display XML assuming the browser you use is capable of this.  All currentversions of IE candispaly native XML.

You should see something like this:

image

Notice that each object is wrapped in a set of tags <Object> and eachproperty is called out in detail.  We only need two things from this.  We need the path to the object and the path to the text node of the property.

To get the objects in a list we can just query for them.  In PowerShell that query looks like this:

$xml.SelectNodes('//Objects/Object').Property|select name, '#text'

This produces a list of property names and values.  In XSLT we will do the same thing but we will do it in the browser using XSLT.

Enabling XSLT in the Browser

Here I the code that enables the XSLT in the browser:

# generate xml of objects
$xmlfile="$pwd\services.xml"
$xml=gwmi win32_service | ConvertTo-Xml
 
# inject an XSLT processing instrinction into the XML
$pi=$xml.CreateProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="services.xsl"')
$xml.InsertBefore($pi,$xml.DocumentElement)
 
# save and launch in default browser
$xml.Save($xmlfile)
. $xmlfile

Of course we don’t yet have a XSL file so we will get only raw text output which is the default for a missing XSL file.

Here is a simple XSL file.  Copy and save it as “services.xsl” in the folder where you are testing this.  Currently the code uses the $pwd variable to find the current folder. Create a new folder so you can find the files more easily.  Make the new folder your current PowerShell folder.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
     <xsl:template match="/">
       <html>
       <body>
           <xsl:apply-templates/>
       </body>
       </html>
     </xsl:template>
</xsl:stylesheet>

This is about the most fundamental XSL we can get for generating HTML.  It has one template and one “apply-templates”  instruction.  Create the file and rerun the code snippet.  YOu can actually just rerun the command to open in the browser:

. $xmlfile

How XSL Loops Through Data

Not much different but we can now demo how the XSL loops through data and generates HTML output.

Now copy this into your “services.xsl” file by replacing all text with this text:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
     <xsl:template match="/">
       <html>
       <body>
           <xsl:apply-templates/>
       </body>
       </html>
     </xsl:template>
     
     <xsl:template match="Objects">
         <xsl:for-each select="Object">
             <p style="color: red;">Hello from Template</p>
         </xsl:for-each>
     </xsl:template>
</xsl:stylesheet> 
 

I have added another template and inserted some output.  The template selects ‘Object’ which says that whenever a tag of that name is found we should apply this template.  Inside the template we have an XSL  “for-each” instruction which says that for each of the selected tags (Object) we should perform the contents of the “for-each” block.  Inside of the “for-each” I have placed a simple HTML tag “<P>”

<p style="color: red;">Hello from for--each</p>

The style is set so we can show that all HTML is legal here.  The output now shows one red line for each object found.

Now this is pretty useless.  We should be outputting something useful so change the <p> line to this:

<p style="color: red;">
        <xsl:value-of select="Property[@Name='DisplayName']"/
</p>

If you make a mistake in the closure the display will default back to listing all text because of a processing error in the transform so be careful to make the change without disrupting the structure.

All tags in XML must be closed.  You cannot ignore this or bad things will happen. If we are injecting HTML into a transform it also has to be closed.

<tagname/>  - self closing
<tagname></tagname> – explicit closure
<tagname>Hello<newtag>…ILLEGAL

Notice that we have now created a paragraph with the DisplayName of the service in red.

Now lets make a table.  Replace the XSL with this:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
     <xsl:template match="/">
       <html>
       <body>
           <xsl:apply-templates/>
       </body>
       </html>
     </xsl:template>
     
     <xsl:template match="Objects">
     
       <table border="1">
         <thead>
         <tr>
           <th>Display Name</th>
         </tr>
         </thead>
 
         <xsl:for-each select="Object">
            <tr>
                <td><xsl:value-of select="Property[@Name='DisplayName']"/></td>
            </tr>
         </xsl:for-each>
         
        </table>
        
     </xsl:template>
</xsl:stylesheet> 
 

Now we have a simple table with one column.  Note that we have injected the header on entry into the template but before we enumerate the “Object” collection.  In the “for-each” we emit the table rows.

Try it:

Now place a few more items of interest into the table.  Be careful. XML and XSL are case sensitive. All tags and all XPath expressions must respect the case of the names.  “displayname” and “DisplayName” are not the same.  You must match the case of the tags and attributes as well as the match case of the values being references.

Every bit of this line is case sensitive. If you change the case of any character the line will either fail or cause a syntax error.

<xsl:value-of select="Property[@Name='DisplayName']"/>

A More Complete Table

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
     <xsl:template match="/">
       <html>
       <body>
           <xsl:apply-templates/>
       </body>
       </html>
     </xsl:template>
     
     <xsl:template match="Objects">
     
       <table border="1">
         <thead>
         <tr>
           <th>Display Name</th>
           <th>Started</th>
           <th>Start Mode</th>
           <th>Description</th>
        </tr>
         </thead>
 
         <xsl:for-each select="Object">
            <tr>
                <td><xsl:value-of select="Property[@Name='DisplayName']"/></td>
                <td><xsl:value-of select="Property[@Name='Started']"/></td>
                <td><xsl:value-of select="Property[@Name='StartMode']"/></td>
                <td><xsl:value-of select="Property[@Name='Description']"/></td>
            </tr>
         </xsl:for-each>
         
        </table>
        
     </xsl:template>
</xsl:stylesheet> 
 

Now we have four columns and good information.  Perhaps now is a good time to take a look at the source of this page.  In the browser right click on the page and select “View Source”.  What do you see?  Why is there no HTML?  Can you see the injected processing instruction?

So browsers consume XML natively. They look at it just like it and follow the instructions.  In our case the instructions are to convert the XML for display.

In ‘Part 1’ we generated HTML output from a transform.  In this part we are only generating HTML.  The browser is taking care of the rest.

Adding CSS Style

We can style this table to make it ore readable and prettier by adding one line to our XSLT.

Create a file called services.css.  Add the following content:

body{
    background-color: bisque;
}
 
thead{
    background-color: #9acd32;
}
 
table{
    border-collapse: collapse;
}

Now add this line to the XSL file.

<link href="services.css" rel="stylesheet" type="text/css"/>

Here is the complete XSL:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
     <xsl:template match="/">
       <html>
       <link href="scripts.css" rel="stylesheet" type="text/css"/>
       <body>
           <xsl:apply-templates/>
       </body>
       </html>
     </xsl:template>
     
     <xsl:template match="Objects">
     
       <table border="1">
         <thead>
         <tr>
           <th>Display Name</th>
           <th>Started</th>
           <th>Start Mode</th>
           <th>Description</th>
        </tr>
         </thead>
 
         <xsl:for-each select="Object">
            <tr>
                <td><xsl:value-of select="Property[@Name='DisplayName']"/></td>
                <td><xsl:value-of select="Property[@Name='Started']"/></td>
                <td><xsl:value-of select="Property[@Name='StartMode']"/></td>
                <td><xsl:value-of select="Property[@Name='Description']"/></td>
            </tr>
         </xsl:for-each>
         
        </table>
        
     </xsl:template>
</xsl:stylesheet> 
 

Now you should see the XML with some basic formatting for easier reading.  We can use the CSS to do all manner of custom formatting.

That is enough for this part.  In the next part I will investigate more formatting and layout options as well as using PowerShell to add extra info such as the table name and the page title.

For all links to this series use this tag: PowerShell Reporting(XML)