On the Road Again

It’s that time of year again: conferences! I’ll be out and about a few places in the next few months and I hope our paths will cross. Here’s a short summary of where I’ll be speaking and when. If you are interested in having me speak for your group or perhaps you are interested in some private training or mentoring, please feel free to contact me.

First up is the PowerShell Deep Dive which is part of The Experts Conference April 29-May 2nd in always sunny and beautiful San Diego. I will be doing a session on integrating Microsoft Office into PowerShell. This will be an extension of some of the articles I’ve done recently for the Petri IT Knowledgebase on creating Word documents and Excel spreadsheets from PowerShell. I have discovered some new techniques that I’m excited to share.

On Saturday, May 12 I’ll be presenting several sessions at the SQL Saturday event in Rochester, NY. I’ll be doing sessions on background jobs, jobs and tasks in PowerShell 3.0 and one on PowerShell 3.0 workflows. I’m looking forward to this event because it is in my own backyard and the SQL Saturday folks are a new group for me. Should be a lot of fun.

Finally, I will be at TechEd in Orlando the week of June 11th. I have a session that I’m co-presenting with Group Policy MVP Jeremy Moskowitz. We’ll be talking about Group Policy Reporting and Analysis with Windows PowerShell. The focus is on what sort of things you want to look for in Group Policy that might give you problems and how you can find this information using PowerShell. I think Jeremy and I are going to have a lot of fun with this. The rest of the time at TechEd you’ll most likely find me wandering the expo hall or hanging out with the Scripting Guys. If more formal events are scheduled for me I’ll be sure to post the information and tweet about it. You do follow me on Twitter, right?

As far as I know there is still time to register for all of these events. I hope you’ll find time to sit in on one of my talks or at least come up and say hi.

Create HTML Bar Charts from PowerShell

I saw a very nice mention on Twitter today where someone had taken an idea of mine and created something practical and in production. It is always nice to hear. The inspiring article was something I worked up that showed using the PowerShell console as a graphing tool. Of course someone immediately wanted to know about turning this into HTML. Actually he wanted this in a web page that would automatically refresh. I don’t think I can manage that, but I came up with a demo script that will create a colorized bar graph in ah HTML page. If you want to update it, simply re-run your command.

Here’s the demo code.


#requires -version 2.0

Param (
[string[]]$computers=@($env:computername),
[string]$Path="drivereport.htm"
)

$Title="Drive Report"

#embed a stylesheet in the html header
$head = @"

$Title

"@

#define an array for html fragments
$fragments=@()

#get the drive data
$data=get-wmiobject -Class Win32_logicaldisk -filter "drivetype=3" -computer $computers

#group data by computername
$groups=$Data | Group-Object -Property SystemName

#this is the graph character
[string]$g=[char]9608

#create html fragments for each computer
#iterate through each group object

ForEach ($computer in $groups) {

$fragments+="

$($computer.Name)

"

#define a collection of drives from the group object
$Drives=$computer.group

#create an html fragment
$html=$drives | Select @{Name="Drive";Expression={$_.DeviceID}},
@{Name="SizeGB";Expression={$_.Size/1GB -as [int]}},
@{Name="UsedGB";Expression={"{0:N2}" -f (($_.Size - $_.Freespace)/1GB) }},
@{Name="FreeGB";Expression={"{0:N2}" -f ($_.FreeSpace/1GB) }},
@{Name="Usage";Expression={
$UsedPer= (($_.Size - $_.Freespace)/$_.Size)*100
$UsedGraph=$g * ($UsedPer/2)
$FreeGraph=$g* ((100-$UsedPer)/2)
#I'm using place holders for the < and > characters
"xopenFont color=Redxclose{0}xopen/FontxclosexopenFont Color=Greenxclose{1}xopen/fontxclose" -f $usedGraph,$FreeGraph
}} | ConvertTo-Html -Fragment

#replace the tag place holders. It is a hack but it works.
$html=$html -replace "xopen","<" $html=$html -replace "xclose",">"

#add to fragments
$Fragments+=$html

#insert a return between each computer
$fragments+="
"

} #foreach computer

#add a footer
$footer=("
Report run {0} by {1}\{2}" -f (Get-Date -displayhint date),$env:userdomain,$env:username)
$fragments+=$footer

#write the result to a file
ConvertTo-Html -head $head -body $fragments | Out-File $Path

The key concept here is that I’m building a final HTML file from a collection of HTML fragments. I think most of the script should be self-explanatory. The core part of the script takes the collection of drives for a computer, selects some custom properties and converts it into an HTML fragment.


#create an html fragment
$html=$drives | Select @{Name="Drive";Expression={$_.DeviceID}},
@{Name="SizeGB";Expression={$_.Size/1GB -as [int]}},
@{Name="UsedGB";Expression={"{0:N2}" -f (($_.Size - $_.Freespace)/1GB) }},
@{Name="FreeGB";Expression={"{0:N2}" -f ($_.FreeSpace/1GB) }},
@{Name="Usage";Expression={
$UsedPer= (($_.Size - $_.Freespace)/$_.Size)*100
$UsedGraph=$g * ($UsedPer/2)
$FreeGraph=$g* ((100-$UsedPer)/2)
#I'm using place holders for the < and > characters
"xopenFont color=Redxclose{0}xopen/FontxclosexopenFont Color=Greenxclose{1}xopen/fontxclose" -f $usedGraph,$FreeGraph
}} | ConvertTo-Html -Fragment

The tricky part is the value for “Usage” In the Expression scriptblock I’m calculating a value for how much disk space is used and how much is free. I then create a string that is my graphing character ([CHAR]9608) times a value. The value is the corresponding percentage divided by 2 because I want to put the used graphic right next to the free graphic. And this leads me to the hack: I want the used graph to be in Red and the free graph string to be in Green. I’m taking the easy way and using a FONT tag. But, I can’t use the < and > characters because Convertto-HTML “translates” them. I couldn’t find anyway around this. So instead I use a place holder.


"xopenFont color=Redxclose{0}xopen/FontxclosexopenFont Color=Greenxclose{1}xopen/fontxclose" -f $usedGraph,$FreeGraph

All I need to do is replace the placeholders from the HTML output.


#replace the tag place holders. It is a hack but it works.
$html=$html -replace "xopen","<" $html=$html -replace "xclose",">"

#add to fragments
$Fragments+=$html

Perhaps not the most elegant solution, but it works. The script wraps up by taking all of the HTML code and creates the final file.


ConvertTo-Html -head $head -body $fragments | Out-File $Path

To run this for a group of computers I can run a command like:


PS C:\scripts> .\demo-HtmlBarChart.ps1 "jdhit-dc01","serenity","quark"

Here is the result:

I haven’t found a way to make this process more generic and my script, while it works, isn’t really production ready; that’s why I call it a demo script. But perhaps you’ll take inspiration and come up with some wonderful. If you do, I hope you’ll let me know and share with the PowerShell community.

Download Demo-HtmlBarChart.

Export and Import Hash Tables

I use hash tables quite a bit and with the impending arrival of PowerShell 3.0 I expect even more so. PowerShell v3 allows you to define a hash table of default parameter values. I’m not going to to cover that feature specifically, but it made me realize I needed a better way to export a hash table, say to a CSV file. So I put together a few functions to do just that.

To walk you through them, here’s a simple hash table.


$hash=@{Name="jeff";pi=3.14;date=Get-Date;size=3 }
$hash
Name Value
---- -----
Name jeff
pi 3.14
date 2/2/2012 10:04:54 AM
size 3

I want to export this to a CSV file, but because PowerShell is all about the objects, I want to be sure to get the type information as well. Otherwise when I go to importing, everything will be a string. Here’s what I can expect to export:


$hash.GetEnumerator() | Select Key,Value,@{Name="Type";Expression={$_.value.gettype().name}}

Key Value Type
--- ----- ----
Name jeff String
pi 3.14 Double
date 2/2/2012 10:05:57 AM DateTime
size 3 Int32

That looks good. I can take this command and run it through Export-CSV which gives me this file:

#TYPE Selected.System.Collections.DictionaryEntry
"Key","Value","Type"
"Name","jeff","String"
"pi","3.14","Double"
"date","2/2/2012 10:05:57 AM","DateTime"
"size","3","Int32"

Perfect. Later, I will need to import this file and recreate my hash table. I can use Import-CSV as a starting point.


PS C:\> import-csv hash.csv

Key Value Type
--- ----- ----
Name jeff String
pi 3.14 Double
date 2/2/2012 10:05:57 AM DateTime
size 3 Int32

Good so far. All I need to do is create a hash table and add each entry to it. I could do something like this:


Import-csv hash.csv | foreach -begin {$hash=@{}} -process {$hash.Add($_.Key,$_.Value)} -end {$hash}

But if I do this, everything will be a string. Since I have Type information, let’s use it.


Import-Csv -Path $path | ForEach-Object -begin {
#define an empty hash table
$hash=@{}
} -process {
<# if there is a type column, then add the entry as that type otherwise we'll treat it as a string #>
if ($_.Type) {

$type=[type]"$($_.type)"
}
else {
$type=[type]"string"
}
Write-Verbose "Adding $($_.key)"
Write-Verbose "Setting type to $type"

$hash.Add($_.Key,($($_.Value) -as $type))

} -end {
#write hash to the pipeline
Write-Output $hash
}

Here I’m taking the Type value from the import and turning it into a System.Type object which I can then use to cast each value to the correct type. I’m checking for the Type property because I might have a CSV file without it. But as long as I have column headings for Key and Value this will work.

I turned all of this into a pair of advanced functions, Export-HashtoCSV and Import-CSVtoHash.


PS C:\> $hash | Export-HashtoCSV myhash.csv
PS C:\> $newhash=Import-CSVtoHash .\myhash.csv -verbose
VERBOSE: Importing data from .\myhash.csv
VERBOSE: Adding Name
VERBOSE: Setting type to string
VERBOSE: Adding pi
VERBOSE: Setting type to double
VERBOSE: Adding date
VERBOSE: Setting type to System.DateTime
VERBOSE: Adding size
VERBOSE: Setting type to int
VERBOSE: Import complete
PS C:\> $newhash

Name Value
---- -----
Name jeff
pi 3.14
date 2/2/2012 10:05:57 AM
size 3
PS C:\> $newhash.date

Thursday, February 02, 2012 10:05:57 AM
PS C:\> $newhash.pi.gettype().name
Double

This certainly fulfills my needs. You can download a script file with both functions, including help. As always, enjoy and I hope you’ll let me know how these work out for you.

Maximizing the PowerShell Console Title Bar

A few days ago Boe Prox posted some very nifty PowerShell modules for using the title bar as a ticker for RSS feeds like the weather. I thought this was an awesome idea and an easy way to take advantage of what would otherwise be unused screen space. I was especially intrigued with his use of timer objects and event subscriptions to manage the updating.

Naturally I decided to run with this. My main goal was to take Boe’s fundamental idea and turn it into something more re-usable or extensible. My result is a module called ConsoleTitle.


PS C:\> get-command -Module ConsoleTitle | Select Name

Name
----
Get-Inspiration
Get-SystemStat
Get-Timer
New-Timer
Remove-Timer
Set-ConsoleTitle
Set-TimerInterval
Start-TitleTimer

The overall premise is pretty simple, define a global variable $PSConsoleTitle and use a timer to periodically update the console title bar with this value. During the refresh interval you can run whatever code you like, however you like, to provide a new value to the variable. In the module I’ve included two sample commands, Get-SystemStat and Get-Inspiration. The former uses WMI to gather system information from the local computer.

The other command defines an array of slogans, sayings and suggestions and randomly selects one to use as the title bar text.

The module includes a few commands for working with timer objects. You can use New-Timer in your own scripts. Here’s the function.


Function New-Timer {

<# .Synopsis Create an event timer object .Description Create an event timer object, primarily to be used by the ConsoleTitle module. Each timer job will automatically be added to the global variable, $ConsoleTitleEvents unless you use the -NoAdd parameter. This variable is used by Remove-Timer to clear console title related timers. This function is called from within other module functions but you can use it to create non-module timers. .Parameter Identifier A source identifier for your timer .Parameter Refresh The timer interval in Seconds. The default is 300 (5 minutes). Minimum value is 5 seconds. .Parameter Action The scriptblock to execute when the timer runs down. .Parameter NoAdd Don't add the timer object to the $ConsoleTitleEvents global variable. #>

Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a source identifier for your timer")]
[ValidateNotNullorEmpty()]
[string]$Identifier,
[Parameter(Position=1)]
[validatescript({$_ -ge 5})]
[int]$Refresh=300,
[Parameter(Position=2,Mandatory=$True,HelpMessage="Enter an action scriptblock")]
[scriptblock]$Action,
[switch]$NoAdd
)

Write-Verbose ("Creating a timer called {0} to refresh every {1} seconds." -f $Identifier,$Refresh)

#create a timer object
$timer = new-object timers.timer
#timer interval is in milliseconds
$timer.Interval = $Refresh*1000
$timer.Enabled=$True

#create the event subscription and add to the global variable
$evt=Register-ObjectEvent -InputObject $timer -EventName elapsed –SourceIdentifier $Identifier -Action $Action

if (-Not $NoAdd) {
#add the event to a global variable to track all events
$global:ConsoleTitleEvents+=$evt
}
#start the timer
$timer.Start()

} #Function

And here’s how you might use it.


Function Get-Inspiration {

Param(
[Parameter(Position=0)]
[ValidateScript({$_ -ge 5})]
[int]$Refresh=600
)

#Define an array of pithy sayings, slogans and quotes

#we'll create as a globally scoped variable so you can add to it anytime you want from PowerShell
$global:slogans=@(
"PowerShell Rocks!",
"Energize!!",
"To Shell and Back",
"I am the Shell",
"PowerShell to the People",
"Powered by PS",
"PowerShell Rulez!",
"PowerShell Fanboy",
"I am the walrus",
"Those who forget to script are doomed to repeat their work.",
"Have you backed up files lately?",
"Is your resume up to date?",
"Is it Beer O'Clock yet?",
"With great power comes great responsibility",
"I came, I saw, I scripted.",
"$env:username, Open the pod bay doors."
)

$sb={ $global:PSConsoleTitle=$global:slogans | get-random }
#invoke the scriptblock
Invoke-Command $sb

New-Timer -identifier "SloganUpdate" -action $sb -refresh $refresh

#start the update timer if not already running
if (-Not (Get-EventSubscriber -SourceIdentifier "TitleTimer" -ea "SilentlyContinue")) {
Start-TitleTimer -refresh $refresh
}

} #function

Think of the module as a framework or SDK for building your own solutions. The module also includes an about topic. I hope you’ll download ConsoleTitleand let me know what you think.

Friday Fun Get Content Words

Recently I was tracking down a bug in script for a client. The problem turned out to be a simple typo. I could have easily avoided that by using Set-StrictMode, which I do now, but that’s not what this is about. What I realized I wanted was a way to look at all the for “words” in a script. If I could look at them sorted, then typos would jump out. At least in theory.

My plan was to get the content of a text file or script, use a regular expression pattern to identify all the “words” and then get a sorted and unique list. Here’s what I came up with.


Function Get-ContentWords {

[cmdletbinding()]

Param (
[Parameter(Position=0,Mandatory=$True,
HelpMessage="Enter the filename for your text file",
ValueFromPipeline=$True)]
[string]$Path
)

Begin {
Set-StrictMode -Version 2.0

Write-Verbose "Starting $($myinvocation.mycommand)"

#define a regular expression pattern to detect "words"
[regex]$word="\b\S+\b"
}

Process {

if ($path.gettype().Name -eq "FileInfo") {
#$Path is a file object
Write-Verbose "Getting content from $($Path.Fullname)"
$content=Get-Content -Path $path.Fullname
}
else {
#$Path is a string
Write-Verbose "Getting content from $path"
$content=get-content -Path $Path
}

#add a little information
$stats=$content | Measure-Object -Word
Write-Verbose "Found approximately $($stats.words) words"

#write sorted unique values
$word.Matches($content) | select Value -unique | sort Value
}

End {
Write-Verbose "Ending $($myinvocation.mycommand)"
}

} #close function

The function uses Get-Content to retrieve the content (what else?!) of the specified file. At the beginning of the function I defined a regular expression object to find “words”.


#define a regular expression pattern to detect "words"
[regex]$word="\b\S+\b"

This is an intentionally broad pattern that searches for anything not a space. The \b element indicates a word boundary. Because this is a REGEX object, I can do a bit more than using a basic -match operator. Instead I’ll use the Matches() method which will return a collection of match objects. I can pipe these to Select-Object retrieving just the Value property. I also use the -Unique parameter to filter out duplicates. Finally the values are sorted.


$word.Matches($content) | select Value -unique | sort Value

The matches and filtering are NOT case-sensitive, which is fine for me. With the list I can see where I might have used write-host instead of Write-Host and go back to clean up my code. Let me show you how this works. Here’s a demo script.


#Requires -version 2.0

$comp = Read-Host "Enter a computer name"

write-host "Querying services on $comp" -fore Cyan
$svc = get-service -comp $comp

$msg = "I found {0} services on $comp" -f $svc.count
Write-Host "Results" -fore Green
Write-Host $mgs -fore Green

The script has some case inconsistencies as well as a typo. I’ve dot sourced the function in my PowerShell session. Here’s what I end up with.

For best results, you need to make sure there are spaces around commands that use the = sign. But now I can scan through the list and pick out potential problems. Sure, Set-StrictMode would help with variable typos but if I had errors in say comment based help, that wouldn’t help. Maybe you’ll find this useful in your scripting work, maybe not. But I hope you learned a few things about working with REGEX objects and unique properties.

Download Get-ContentWords and enjoy.