PowerShell: Dynamically Color PosH Generated HTML:Part 2

July 23, 2012 1 comment

PowerShell: Dynamically Color PosH Generated HTML:Part 1

In the first part of this article I discussed using CSS styles, table ids and classes to colorize individual tables.  I ended with a question about how we might be able to colorize individual rows of a table in a way that would show up in an email message.

How about coloring individual lines?  Can we use this to set a lines color depending on the value of a column? Of course.  Next time I will show how easy it is to do that although I have already given you almost all of the pieces for doing it.  We just need about three more lines of code and some knowledge about how XML works and how to make HTML do some of the work for us.

Here is the code that will colorize the individual rows based on a columns value in that row.

If you remember from before we loaded the HTML fragment into an XML DOM for manipulation.  We will use the same DOM to find and colorize the rows.

$rows=$xml.table.selectNodes('//tr')
for($i=1;$i -lt $rows.count; $i++){
$value=$rows.Item($i).LastChild.'#text'
if($value.Length -gt 0 -and [int]$value -le $threshhold){
Write-Host "Candidate found - setting color to red at $value" -fore green
$attr=$xml.CreateAttribute('style')
$attr.Value='background-color: red;'
[void]$rows.Item($i).Attributes.Append($attr)
}
}

As you can see we are just adding an attribute to the row.

First we find all of the row objects which are represented by the ‘tr’ tag:

$rows=$xml.table.selectNodes(‘//tr’)

This is the XPath that will return all rows of a table.  Next I enumerate the rows but skip the first row as it is the header. I use an explicit ‘for’ loop because we need the index to return the row object. The index is $i and is incremented in the ‘for’ statement.  The index allows us to dereference the ‘#text’ node of the ‘last child’ which is the end column of the row.

$value=$rows.Item($i).LastChild.’#text’

Now that I have the value of the column it is time to test the value against our ‘threshold’.  We have to cast it to an integer for this to work correctly. We also need to filter out all nodes that may be blank so I check for zero length.

if($value.Length -gt 0 -and [int]$value -le $threshhold){

Once we find a column that meets our criteria we can add our attributes and set it as a color style:  This is done by creating an attribute named ‘style’ and assigning its value to be ‘background-color: red;’  just like we would type it into the HTML tag

$attr=$xml.CreateAttribute(‘style’)

$attr.Value=’background-color: red;’

There we have successfully edited HTML without playing with strings again.  Creating and modifying HTML this way prevents breaking the HTML because the XML DOM will never generate broken XML or broken XHTML.

Send Email Table With Rows Colored to Show Alerts
$threshhold=10
$html=gwmi win32_logicaldisk |
Select-Object __SERVER,deviceid,@{N='PercentFree';E={[math]::Round($_.Freespace/$_.Size * 100,0)}} |
ConvertTo-Html -Fragment
$xml=[xml]$html
$rows=$xml.table.selectNodes('//tr')
for($i=1;$i -lt $rows.count; $i++){
$value=$rows.Item($i).LastChild.'#text'
if($value.Length -gt 0 -and [int]$value -le $threshhold){
Write-Host "Candidate found - setting color to red at $value" -fore green
$attr=$xml.CreateAttribute('style')
$attr.Value='background-color: red;'
[void]$rows.Item($i).Attributes.Append($attr)
}
}
$html=$xml.OuterXml|Out-String
$style='<style type=text/css>#diskTbl { background-color: blue; }</style>'
$body=ConvertTo-Html -head $style -body $html -Title "Disk Usage Report"|Out-String
$msg=@{
To=$to
From=$from
Subject="Disk usage report for $([datetime]::Now)"
BodyAsHTML=$true
Body=$body
}

This is what the email looks like:

image

Of course we can do more but look at what has been accomplished in only a couple of lines.  We can use theses two techniques to add more tables all colorized and alerted or highlighted in any manner we choose and all without playing with strings. We can be as fancy as we like now because the data is being stuffed into html fragments for us and we can then decorate the data in a very controlled fashion.

What is next?

Next time I will attempt to combine these techniques into a web page with multiple tables and FTP it to a public web site.  I will also show how to add external style sheets and how to inject VBScript or JavaScript into the pages generated by PowerShell.  Maybe we can even look at PowerShell generated ASP.NET pages.

Until next time….

Part 1: http://jvierra.wordpress.com/2012/07/22/powershell-dynamically-color-posh-generated-html-part-1/

Categories: Uncategorized

PowerShell: Dynamically Color PosH Generated HTML. – Part 1

July 22, 2012 2 comments

PowerShell is excellent at dumping data into HTML tables.  We can gather ‘fragments’ of tabled data and combine these tables into a web page to create very complex reports. With PowerShell tools we can do this almost effortlessly and with little knowledge of HTML.  So why is this not used more often?  How come we keep seeing scripters generating hand gathered html written line by line into a file?

The best way to manage HTML is through the DOM (Document Object Model).  In IE we can access the DOM through the ‘window.document’ object.  Why not just load the document into IE and create or modify reports?

There are issues with doing this. Besides being cumbersome it does not play well when the script is run unattended under the Task Scheduler.  We also may only want to set a color or two then send the HTML as an email.  IE is overkill in most cases.

PowerShell has the ConvertTo-Html CmdLet which can do many things including generating fragments and combining fragments into a page.Here is a link to a great two-part article on how to generate HTML multi-table reports:

http://blogs.technet.com/b/heyscriptingguy/archive/2012/06/04/powershell-in-depth-part-1.aspx
http://blogs.technet.com/b/heyscriptingguy/archive/2012/06/04/powershell-in-depth-part-2.aspx

The strength in the above-linked approach is that it allows us to gather all manner of data into simple tables and throw them onto a page.  The page display can be modified via the injected CSS with one drawback.  If we need to send the HTML by email much of the power of CSS2 will be lost as most email HTML supports only a subset of CSS1.

Here are a couple of simple ideas and techniques that can help to extend the usefulness of the PowerShell HTML CmdLet.

  1. Use the XML DOM to edit the HTML fragment.
  2. Add an ID to each table fragment which will allow you to control the style of individual tables independently.
  3. Use XPath to select rows and set color based on the row’s value.

Here are the techniques to accomplish the above items.

Load HTML Fragment into XML DOM
# get some data for demo and generate a fragment
$html=gwmi win32_logicaldisk |
Select-Object deviceid,@{N='PercentFree';E={[math]::Round($_.Freespace/$_.Size * 100,0)}} |
ConvertTo-Html -Fragment
# load HTML into XML DOM
$xml=[xml]$html

That’s it. Just generate the output to the HTML converter and capture in a variable.  Send the variable through the XML type accelerator and save it in the variable $xml.

But you are thinking it is HTML and not XML right?  Well it is really HTML. PowerShell generates XHTML compliant HTML which is also XML.  While we cannot load a full page because of the ‘DOCTYPE’ header line we can load a fragment because it is raw and legal XML.

The HTML we just loaded looks like this:

<table>
<colgroup>
<col/>
<col/>
</colgroup>
<tr><th>deviceid</th><th>PercentFree</th></tr>
<tr><td>A:</td><td></td></tr>
<tr><td>C:</td><td>3</td></tr>
<tr><td>D:</td><td></td></tr>
<tr><td>E:</td><td>87</td></tr>
<tr><td>F:</td><td>89</td></tr>
</table>

Perfectly legitimate XML because all tags are closed correctly and there is no conflicting DOC header.

Now we can modify the HTML very easily using out XML editing tools.

Add an ID to the Table

First let’s put an ID on the table. We will call the table id=”diskTbl”.  To add an ID we need to add an attribute to the table tag and set its value.  We do this by using the XML CreateAttribute method.

$attr=$xml.CreateAttribute(‘id’)

$attr.Value=’diskTbl’

Next we get the table tag and append the new attribute.

$xml.table.Attributes.Append($attr)

That is it.  The table now has an attribute called ‘id’ with our value.  Want to see?

<table id="diskTbl"><colgroup><col /><col /></colgroup><tr><th>deviceid</th><th>PercentFree</th></tr><tr><td>A:</td><td
></td></tr><tr><td>C:</td><td>3</td></tr><tr><td>D:</td><td></td></tr><tr><td>E:</td><td>87</td></tr><tr><td>F:</td><td
>89</td></tr></table>

See.  The attribute gets nicely tucked into the table tag and no playing with messy strings and broken HTML.  Ok.  The HTML is no longer ‘pretty’ printing.  We can fix that later.  HTML and XML don’t care what they look like so we can change it around using our XML Stream Writer later.

So boys and girls, that is the story of how to manipulate HTML in the DOM.  You can now add a tag “id” into your CSS and make every table a different color.

Add Other Attributes The Same Way

#diskTbl { background-color: blue; }

You could also use the technique to add a class attribute and add multiple cascading classes to your table.

$attr=$xml.CreateAttribute('class')
$attr.Value=’red box wrap’

Now our CSS can look like this:

,red { background-color: red; }

.blue {…. }

.box { border-collapse: collapse; border-style: solid; border-width: 1px; }

.nobox {…..}

.wrap { ….}

.nowrap {….}

Using that technique we can add additive styles to an object from a general purpose style sheet.  Now ConvertTo-Html is becoming potentially very useful.

We can use the returned html to send an HTML mail message

Send Colorized Email With PowerShell
$html=gwmi win32_logicaldisk | 
Select-Object deviceid,@{N='PercentFree';E={[math]::Round($_.Freespace/$_.Size * 100,0)}} |
ConvertTo-Html -Fragment
$xml=[xml]$html
$attr=$xml.CreateAttribute('id')
$attr.Value=’diskTbl’
$xml.table.Attributes.Append($attr)
$html=$xml.OuterXml|Out-String
$style='<style type=text/css>#diskTbl { background-color: blue; }</style>'
$body=ConvertTo-Html -head $style -body $html -Title "Disk Usage Report"|Out-String
$msg=@{
To=$to
From=$from
Subject="Disk usage report for $([datetime]::Now)"
BodyAsHTML=$true
Body=$body
}
Send-MailMessage @msg

Of course this only shows one table but, if we can color one, we can color as many as we like.  The important thing is that we can set style elements for individual table.  We can also add additive style for convenience.  We have also done this without dumping to and from a file and without playing with strings.

More?

How about coloring individual lines?  Can we use this to set a lines color depending on the value of a column? Of course.  Next time I will show how easy it is to do that although I have already given you almost all of the pieces for doing it.  We just need about three more lines of code and some knowledge about how XML works and how to make HTML do some of the work for us.

Part 2: http://jvierra.wordpress.com/2012/07/23/powershell-dynamically-color-posh-generated-htmlpart-2/

Categories: Uncategorized

ActiPro Syntax Highlighter – (updated)

March 4, 2007 Leave a comment

The ActiPro Syntax Highlighter with PowerShell script.  Highlighting is correct.  Module was added to ActiPro by ActiPro support in a very short time showing that the ActiPro Editor is very flexible.  This code was highlighted and copied to HTML using the ActiPro SyntaxEditor.  The new semantic definition was added by simply adding a "dynamic" language definition and then opening the script file.  I saved the file as HTML and pasted it into WLW to get a test of the HTML conversion.  I also added a DIV to force the containment of the lines.

AcriPro Website

ActiPro has done an excellent job on this product suite.

(Thank you Keith Hill for the use of your CmdLet code for use as a test case.  It was the largest and most complex script I could find when I went looking)

# ---------------------------------------------------------------------
# Author:    Keith Hill
# Desc:      CMDLET to pretty print XML.
# Usage:     This file contains a function-based CMDLET.  In order to use
#            it, you must dot source the file into your shell e.g.:
#            PoSH> . c:\bin\format-xml.ps1
# Date:      08/09/2006
# ---------------------------------------------------------------------
function Format-Xml {
    param([string[]]$Path)
        
    begin {
        function PrettyPrintXmlString([string]$xml) {
            $tr = new-object System.IO.StringReader($xml)
            $settings = new-object System.Xml.XmlReaderSettings
            $settings.CloseInput = $true
            $settings.IgnoreWhitespace = $true
            $reader = [System.Xml.XmlReader]::Create($tr, $settings)
            
            $sw = new-object System.IO.StringWriter
            $settings = new-object System.Xml.XmlWriterSettings
            $settings.CloseOutput = $true
            $settings.Indent = $true
            $writer = [System.Xml.XmlWriter]::Create($sw, $settings)
            
            while (!$reader.EOF) {
                $writer.WriteNode($reader, $false)
            }
            $writer.Flush()
            
            $result = $sw.ToString()
            $reader.Close()
            $writer.Close()
            $result
        }
        
        function PrettyPrintXmlFile($path) {
            $rpath = resolve-path $path
            $contents = gc $rpath
            $contents = [string]::join([environment]::newline, $contents)
            PrettyPrintXmlString $contents
        }
    
        function Usage() {
            ""
            "USAGE"
            "    Format-Xml -Path <pathToXmlFile>"
            ""
            "SYNOPSIS"
            "    Formats the XML into a nicely indented form (ie pretty printed)."
            "    Outputs one <string> object for each XML file."
            ""
            "PARAMETERS"
            "    -Path <string[]>"
            "        Specifies path to one or more XML files to format with indentation."
            "        Pipeline input is bound to this parameter."
            ""
            "EXAMPLES"
            "    Format-Xml -Path foo.xml"
            "    Format-Xml foo.xml"
            "    gci *.xml | Format-Xml"  
            "    [xml]`"<doc>...</doc>`" | Format-Xml"
            ""
        }
        if (($args[0] -eq "-?") -or ($args[0] -eq "-help")) {
          Usage
        }
    }
    
    process {
        if ($_) {
          if ($_ -is [xml]) {
            PrettyPrintXmlString $_.get_OuterXml()
          }
          elseif ($_ -is [IO.FileInfo]) {
            PrettyPrintXmlFile $_.FullName
          }
          elseif ($_ -is [string]) {
            if (test-path -type Leaf $_) {
                PrettyPrintXmlFile $_
            }
            else {
                PrettyPrintXmlString $_
            }
          }
          else {
            throw "Pipeline input type must be one of: [xml], [string] or [IO.FileInfo]"
          }
        }
    }
      
    end {
        if ($Path) {
          foreach ($aPath in $Path) {
            PrettyPrintXmlFile $aPath
          }
        }
    }
}

 

 

Categories: CodeHighlighter

Test of Code Snippet WLWPI

February 22, 2007 Leave a comment

 The follwing is a Windows Live Writer code highlighter plugin test that works only in WLW.  Once published teh styles are not applied.  Looking at teh HTML I find that the only style ever included is the csharp style no matter waht is selected or displayed in the wlw editor.

I have not published the identity of the plubin because I do think it is a very good attempt at building a plugin.  I like th eway it works in WLW.

It would be nice if it also had some ability to edit teh style of the containing DIV.  The "overflow" should be setable.  In BlogSpot/blogger we need to be able to verride styles in the templates.  Many have added a patch for IE6 to prevent text runoff to the right.  This patch has a devastating effect on most CSS styles.  In this blog I have removed the patch from the template in favor of manually managing the overflow so that is not an issue here.  The issue appears to be that the styles are being merged with the blog styles and should block inheritence so they will be applied.

In Copy Code To Clipboard I show how a DIV can be controlled very easily and how to add code to allow it to be copied to teh clipboard with formatting intact.

In Steve Dunn’s Code Formatter 1.0.0.2 I show how a code formatter that doesn’t support proper DIV containment can be altered to support auto scroll and div containment.

Under IE7 the DIVs and teh blogs behave correctly.  Under IE6 without the patch the blog right area floats far off to teh left due to the line overflow on the unconstrained div.    The patch mentioned above prevents this but breaks many other things.  To see this and test it use the blog post history on the far right to select a post that does not contain the unconstrained div.   This one will work: PowerShell: Designing a CmdLet (Part II) and then select either the main blog or this entry to see everything float way off to the right: PowerShell: Designing a CmdLet (Part IIb – the cod…  The display puts the right area on he bottom due to overflow.  If you use IE7 or most other browsers this will not happen.

If you build code and test with IE6 on a blog with patches for IE6 the output won’t behave correctly.  If the patches aren’t present the div won’t be constrained without adding a border or providing absolute width and an overflow setting.  There are probably other workaraounds and these probably don’t work under all circumstances.

The style problem is a combination of a bug (only csharp styles are included) and inheritance overridin teh styles.  The Steve Dunn formatter has overcome this issue well but still does not manage the enclosing DIV well or correctly for blogspot.

This code highlighter only displays in the following way:

using System;
using System.IO;

namespace HtmlAgilityPack.Samples
{
    class Html2Xml
    {
        [STAThread]
        static void Main(string[] args)
        {
            Test();
        }

        static void Test()
        {
            HtmlToText htt = new HtmlToText();
            string s = htt.Convert(@"..\..\mshome.htm");
            StreamWriter sw = new StreamWriter("mshome.txt");
            sw.Write(s);
            sw.Flush();
            sw.Close();
        }
    }
}

I have tested this on spaces.live.com and it fails in exactly the same way.

Categories: CodeHighlighter

Live Writer – Steve Dunn’s Code Formatter

October 3, 2006 Leave a comment

Here is an example of the Live Writer PluIn from Steve Dunn.  As you can see I have added height and width restrictions to the DIV style alongwith a "autoflow:auto ".  This causes the scrollbars to be turned on whenever the textflow exceeds the DIV size.  Adding these choices to the control by retrieveing the heghth and width of the formatter control and allowing a checkbox to turn on scrollbars would make this Writer AddIn awesome.

1 Option Explicit 2 Const ForReading = 1 3 Const ForAppending = 8 4 Const sLogFileName = "d:\log.txt" 5 Const sServerFileName = "d:\servers.txt" 6 'On Error Resume Next 7 Dim objDictionary, objFSO, objOU, objComputer, strComputer 8 Dim colNetcards, objWMIService, objNetcard, arrWINSServers 9 Dim i, objItem, sName, objTextFile, strNextLine 10 11 Set objDictionary = CreateObject("Scripting.Dictionary") 12 Set objFSO = CreateObject("Scripting.FileSystemObject") 13 Set objTextFile = objFSO.OpenTextFile(sServerFileName, ForReading) 14 i = 0 15 Do Until objTextFile.AtEndOfStream 16 strNextLine = objTextFile.Readline 17 objDictionary.Add i, strNextLine 18 i = i + 1 19 Loop 20 objTextFile.close 21 22 Dim oLogFile 23 Set oLogFile = objFSO.OpenTextFile(sLogFileName, ForAppending) 24 For Each objItem in objDictionary 25 26 StrComputer = objDictionary.Item(objItem) 27 WScript.Echo "Begin pinging:" & strComputer 28 If TestPing(strComputer) = False Then 29 WScript.Echo now() & " Couldn't reach " & strComputer 30 oLogFile.WriteLine now() & " Couldn't reach " & strComputer 31 Else 32 WScript.Echo "Ping successful!" 33 Set objWMIService = GetObject("winmgmts:\\" & strComputer& "\root\cimv2") 34 Set colNetCards = objWMIService.ExecQuery("Select * From Win32_NetworkAdapterConfiguration Where IPEnabled = TRUE AND NOT Description LIKE '%ISCSI'") 35 For Each objNetCard In colNetcards 36 WScript.Echo objNetCard.Description 37 arrWINSServers = Array("Null", "Null") 38 'objNetCard.SetWINSServerSearchOrder(arrWINSServers) 39 Next 40 End If 41 42 Next 43 44 oLogFile.Writeline "" 45 oLogFile.Close 46 WScript.echo "Script Finished" 47 48 Function TestPing(sName) 49 TestPing = false 50 If sName = "" Then 51 WScript.Echo "Bad computer name string!" 52 else 53 Dim cPingResults, oPingResult 54 Set cPingResults = GetObject("winmgmts://./root/cimv2").ExecQuery("SELECT * FROM Win32_PingStatus WHERE Address = '" & sName & "'") 55 For Each oPingResult In cPingResults 56 If oPingResult.StatusCode = 0 Then 57 TestPing = True 58 End If 59 Next 60 End if 61 End Function 62 63
Categories: CodeHighlighter

Blogging with Live Writer Beta

August 17, 2006 Leave a comment

Setup was easy.  One click to add a web page reference – or self-reference in this case.

The publish function works well and more seamlessly than most.

Now for the big test.  Does Live Writer publish to BlogSpot well?

Well it works just great. I started this entry on Live and just re-pointed to tech-comments and duplicated it here in one click.  Now let’s see if we can switch back….

PS – we get spelling and proper WYSIWYG.

It’s also very nice to be able to edit right in the template and not have to do previews.  Editing right in the whole page would be even better.  Template editing would be the cat’s pjs…

The editing back and forth between blogs is very nice.  It would be nicer if we could publish to multiple target simultaneously.

 PROBLEM:

Switching back and forth between blogs causes duplicate entries forcing me to delete the older entry.  Apparently Live Writer doesn’t honor the post ID when publishing.

Categories: Uncategorized

Rory And Scott Go To Tech Ed A Love Story

April 9, 2005 Leave a comment
Categories: TechHumor
Follow

Get every new post delivered to your Inbox.