PowerShell Cookbook

Search

Categories

 

On this page

PowerShell Cookbook – O’Reilly EBook Deal of the Day
PowerShell Cookbook V2 Now Available
Aurora Monitoring with PowerShell
Hang Drum for Propellerheads Reason
Which Files Support Authenticode Signatures?
Open PowerShell Cookbook Beta Available Online
HTML Agility Pack Rocks Your Screen Scraping World
Responding to USB Devices in PowerShell
PowerShell Hotkey to Pause Pandora
Looking for Reviewers for PowerShell Cookbook v2

Archive

Blogroll

Disclaimer
I work for Microsoft.

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 262
This Year: 13
This Month: 0
This Week: 0
Comments: 818

Sign In

 Thursday, August 26, 2010
Thursday, August 26, 2010 9:30:42 AM (Pacific Standard Time, UTC-08:00) ( )

For today only, O’Reilly is offering the electronic version of the Windows PowerShell Cookbook for only $9.99. This gives you unrestricted access to DRM-free PDF, MOBI (Kindle, etc) and ePub versions of the book.

If you have an iDevice, the Stanza book reader (among several others) supports both the PDF and ePub file formats.

Get it while it’s hot!

Use code DDWPC http://oreil.ly/cFRco0

Comments [0] | | # 
 Monday, August 16, 2010
Monday, August 16, 2010 11:57:22 AM (Pacific Standard Time, UTC-08:00) ( )

On Friday, we wrapped up the final details of the PowerShell Cookbook, V2. O’Reilly has already made the electronic edition available, which is a great resource for searching, copying, pasting, and working with the content in its native form. If you or your company subscribes to Safari, it should be available shortly.

If you’re a fan of the printed version, it's off to the printers now, and has a scheduled "in-stock" date of 8/24: the date when it'll be in boxes in the shipping / receiving area of book stores. It takes about a week for books to migrate onto shelves, so the official "when can I get it" date is 8/31.

It's almost 900 pages now, despite my best efforts to keep it svelte :) I dropped the chapters on Exchange and MOM (since there are dedicated books and support materials for those now), and dropped a few appendixes that are now covered much more effectively in the in-box help. Since much of the V1 book is about filling "missing pieces" from V1, and we filled tons of "missing pieces" from V1, much of the new content directly replaced recipes that existed in the V1 book.

Dropping the chapters on Exchange and MOM was also done in response to feedback. While they provided cursory coverage of the technologies, they were intended primarily to introduce Admins to the wider PowerShell ecosystem. Now that there are entire books written to cover PowerShell-based products (i.e.: VMWare, Exchange, MOM, etc), the Cookbook can now focus on the core technology. Aside from the Exchange / MOM updates, the "Description" sections of many recipes have been beefed up in response to feedback. Most go into significantly more detail, or discuss more of the underpinnings of the recipe or what it covers.

And then, of course, there's the "new stuff". The book now has about 430 recipes, almost double what the first version had. It's sometimes easy to forget how transformative PowerShell V2 is, but it consisted of over two thousand source code checkins!

All-told, the second edition took about 560 hours. That's a lot, but what surprised me is that it ended up being only about an hour per print-ready page: including research, drafts, reviews, copy editing, and everything else wrapped in the project.

If you liked the first version of the PowerShell Cookbook, you’ll like this version even more :)

Comments [2] | | # 
 Wednesday, August 04, 2010
Wednesday, August 04, 2010 3:58:59 PM (Pacific Standard Time, UTC-08:00) ( )

Over the past few days, Astronomy resources have been making a big splash about a couple of coronal mass ejections (CME) primed to cause widespread visibility of the Aurora Borealis. Last night was when the first one hit, and its effects extended deeply into southern Canada and parts of the northern US.

There may be another round tonight, so maybe this post can help you catch your first Aurora :)

NOAA has a couple of good resources, and one I was keeping special track of shows where the effects are the greatest:

aurora_2010-08-04-14-49

Areas that are red are seeing the greatest magnetic activity.

After refreshing this page a bunch last night and watching as the red started getting closer and closer to Washington state. It was getting late and I still couldn’t see it, so I decided instead to write a script to alert me when it peaked.

This script measures the amount of red over (and above) Washington State, and keeps monitoring until it senses a decrease. Once it senses a 5% decline, it alerts me. Crude image recognition at its finest!

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
## Launch the interesting sites
Start-Process "http://www.swpc.noaa.gov/pmap/pmapN.html"
Start-Process "http://www.swpc.noaa.gov/rt_plots/pro_3d.html"

Add-Type -Assembly System.Drawing
$wc = New-Object System.Net.WebClient

## The monitor region. Get these coordinates from the X,Y positions
## your mouse shows when hovering around any saved file (pmapN.gif) in mspaint.
$startX = 130
$startY = 300
$endX = 144
$endY = 339

## Get the "Aurora Index" for the given region
function Get-AuroraIndex
{
    ## Download the file
    $url = "http://www.swpc.noaa.gov/pmap/gif/pmapN.gif"
    $null = New-Item -Type Directory c:\temp\aurora -Force
    $destination = "c:\temp\aurora\aurora_" + ((Get-Date).ToString("yyyy-MM-dd-HH-mm")) + ".gif"

    try
    {
        $wc.DownloadFile($url, $destination)
    }
    catch { return }

   
    $image = [System.Drawing.Bitmap]::FromFile($destination)

    $totalRed = 0
    for($x = $startX; $x -lt $endX; $x++)
    {
        for($y = $startY; $y -lt $endY; $y++)
        {
            $totalRed += $image.GetPixel($x, $y).R
        }
    }

    $totalRed
}


$lastValue = $null

while($true)
{
    $currentValue = Get-AuroraIndex
    if($currentValue)
    {
        ## Catch the rising aurora
        if($currentValue -gt ($lastValue * 1.05))
        {
            $lastValue = $currentValue
            "Aurora is rising @ " + (Get-Date) + ". Current value: $currentValue"
        }

        ## Catch a falling aurora
        elseif($currentValue -lt ($lastValue * 0.95))
        {
            "Aurora is falling @ " + (Get-Date) + ". Current value: $currentValue"
            while($true)
            {
                C:\windows\Media\notify.wav
                Start-Sleep 5
            }
        }
       
        ## Aurora is stable
        else
        {
            "Aurora is stable @ " + (Get-Date) + ". Current value: $currentValue"
        }
    }

    Start-Sleep 30
}

Comments [1] | | # 
 Thursday, June 17, 2010
Thursday, June 17, 2010 12:27:20 AM (Pacific Standard Time, UTC-08:00) ( )

One instrument that’s got great allure is the PanArt Hang – a metallic drum-like form with great complexity and resonance:

They are difficult to obtain, and expensive should you want to try.

Looking online, I found some WAV samples graciously provided by Andreas Bick at http://www.andreas-bick.de/en/downloads/film_music/hang_samples.php. He also included the sampler settings to convert this to a virtual instrument for Apple Logic Pro’s ESX-24 sampler.

If you have Propellerhead Reason, the sampler settings won’t work – although you can still use the raw WAV files and do your own mapping for the Reason NN-XT sampler. Luckily, I spent the couple of hours doing it so that you don’t have to :) You can get the files here:

http://cid-7874cfd565b38d4b.office.live.com/browse.aspx/Shared/Hang%20Drum%20for%20Reason%20NN-XT

If you don’t have the WAV samples, you’ll need to download the entire ZIP and extract it. It’s in 7-zip format, since I couldn’t get regular zip files to go below the 50MB limit on SkyDrive. If you have the WAV files already, you only need the .sxt file also in the directory.

I also put up a Reason project (Egyptian Mystery.rns) for a song that uses only this instrument: Egyptian Mystery.

Comments [0] | | # 
 Thursday, June 03, 2010
Thursday, June 03, 2010 7:33:13 AM (Pacific Standard Time, UTC-08:00) ( )

An interesting question came up today about the Set-AuthenticodeSignature cmdlet. It actually supports more than just .ps1 files, but what files exactly?

It turns out that this isn’t actually possible to know programmatically. Your best bet is to hard-code a list of things you know or can scrounge from the internet :) The ones I am aware of off-hand are:

.CAB, PE formats (.EXE, .DLL, etc) , .CAT, .MSI, .JAR,.OCX, .PS1, .PSM1, .PSD1, .PS1XML, .PSC1

Windows’ Authenticode infrastructure is based on plugins. All DLLs that support Authenticode signing are registered under:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptSIPDllGetSignedDataMsg

Each DLL has a set of messages it needs to respond to, one of which is called IsMyFileType(). When you want to sign a file or verify the signature on a file, Windows walks along that list of registered GUIDs, asking each DLL: “Is this your file type?” If a DLL responds “Yes”, then it gets asked to verify the signature of that file.

The important point is that this decision is implemented as compiled code in the DLL, not some external mapping table.

Interesting aside:

This “ordered by GUID” enumeration of Authenticode plugins was the cause of our “RC1 Refresh” if you remember our initial V1 release. It turns out that the sample code from which we based our original Monad Authenticode DLL had a bug where it answered “Yes” to every file type. This never showed up because we had a GUID late in the chain. During the Monad-to-PowerShell rename, an obscure typo (capitalization of a single letter) caused our GUID to get registered as NULL.

Now, with a NULL GUID, our Authenticode plugin was guaranteed to be enumerated first. Since our plugin said that it understood how to manage signatures for all files on Windows, this broke MSI signing, EXE signing, DLL signing, and a bunch of other fun stuff!

Comments [0] | | # 
 Friday, March 19, 2010
Friday, March 19, 2010 10:35:34 AM (Pacific Standard Time, UTC-08:00) ( )

With the first draft of the Windows PowerShell Cookbook complete, we’re running an open beta through a cutting-edge platform called the Open Feedback Publishing System: http://powershell.labs.oreilly.com. In this system, we put the entire book online and let you read to your heart’s content.

Not only is the book available online, but you can also influence its future. The Open Feedback Publishing System lets you attach comments to any paragraph as though you would comment on a blog.

If you are interested in participating in the deeper technical review of the book, welcome aboard! If you have expertise in a specific chapter, please concentrate on that one first, making a comment on its first paragraph that you will be reviewing it. If you have more general expertise or interest, please first select a chapter that has not been reviewed, making a comment on the first paragraph that you will be reviewing it.

To start, visit http://powershell.labs.oreilly.com, create a new account, and start submitting feedback! The Open Feedback Publishing System overview page gives more information on how to contribute. Reviewers that provide significant feedback get a chance to see the impact of their comments on bookshelves worldwide, and will receive a complimentary copy of the book when it goes to press. We’ll be accepting comments until roughly the end of March.

Enjoy!

Comments [0] | | # 
 Friday, March 05, 2010
Friday, March 05, 2010 12:24:20 AM (Pacific Standard Time, UTC-08:00) ( )

I had to do some deep data extraction from a web page today, and naturally leaned on PowerShell for some assistance. PowerShell is a great language for text munging, and web content is no different. There are tons of examples online, but here’s an example from earlier in this blog: http://www.leeholmes.com/blog/PowerShellTheOracleInstantAnswersFromYourPrompt.aspx.

As I looked at the underlying HTML of this page, though, my heart sank. I cared about four pieces of data, and they were arranged without much structure on the web page. The information I cared about was in a couple of different tables, a couple of different table rows, and sometimes in different columns. You can parse your way around this, but it’s simply error-prone and annoying.

At that point, I remembered something called the HTML Agility Pack that I’ve been meaning to experiment with for some time. The HTML Agility Pack lets you navigate an HTML document as though it were well-formed XML, even though the underlying HTML usually isn’t. It doesn’t leverage PowerShell’s XML adapter, but the .NET objects act just like the XML classes from the .NET Framework.

On the down-side, data navigation and selection in XML comes via the XPath language. Like Regular Expressions, XPath queries are an esoteric art and difficult to get right. Luckily, you don’t need much knowledge of XPath for simple XML navigation.

This whole experience gives a great example of the “admin development model.” 15 minutes after thinking about parsing the web page with the HTML Agility Pack, I had a working version. PowerShell’s Get-Member cmdlet was all I used for discovery – no documentation was harmed in the making of this script. Here is the literal text of my history buffer, experimentation and all. On line 251 and 252, I put the history into the ISE so that I can hack out the experimentation bits and keep the stuff that worked.

221 cd C:\temp\HtmlAgilityPack.1.4.0.beta2.binaries
222 dir
223 add-type -Path .\HtmlAgilityPack.dll
224 $types = add-type -Path .\HtmlAgilityPack.dll -PassThru
225 $types
226 $types | ? { $_.IsPublic }
227 $doc = new-object HtmlWeb
228 ($types | ? { $_.IsPublic })[1]
229 ($types | ? { $_.IsPublic })[1].FullName
230 $doc = New-Object HtmlAgilityPack.HtmlDocument
231 $doc
232 $doc | gm
233 $result = $doc.Load("C:\temp\texts.html")
234 $result
235 $doc
236 $doc | gm
237 $doc.DocumentNode
238 $doc.DocumentNode | gm
239 $doc.DocumentNode.SelectNodes("//h1")
240 $doc.DocumentNode.SelectNodes("//table[@class='table-gen']")
241 $doc.DocumentNode.SelectNodes("//table[@class='table-gen']/tr[2]")
242 $doc.DocumentNode.SelectNodes("//table[@class='table-gen']")
243 $texts = $doc.DocumentNode.SelectNodes("//table[@class='table-gen']")
244 $texts[0]
245 $testText = $texts[0]
246 $testText | clip
247 $testText.SelectSingleNode("/tr[1]/td")
248 $testText.SelectSingleNode("tr[1]/td")
249 $testText.SelectSingleNode("tr[1]/td").InnerTExt
250 $testText.SelectSingleNode("tr[1]/td").InnerText.Trim()
251 ise
252 h
253 $time = [DateTime] $testText.SelectSingleNode("tr[1]/td").InnerText.Trim()
254 $testText.SelectSingleNode("tr[2]/td").InnerText.Trim()
255 $testText.SelectSingleNode("tr[2]/td").InnerText.Replace('Description:','').Trim()
256 $testText.SelectSingleNode("tr[6]/td").InnerText
257 $testText.SelectSingleNode("tr[5]/td").InnerText
258 $testText.SelectSingleNode("tr[4]/td").InnerText
259 $testText.SelectSingleNode("tr[5]/td").InnerText
260 $testText.SelectSingleNode("tr[5]/td[1]")
261 $testText.SelectSingleNode("tr[5]/td[2]")
262 $time = $testText.SelectSingleNode("tr[1]/td").InnerText.Trim()
263 $inOut = $testText.SelectSingleNode("tr[2]/td").InnerText.Replace('Description:',...
264 $to = $testText.SelectSingleNode("tr[5]/td").InnerText.Replace('Number Called:','...
265 $from = $testText.SelectSingleNode("tr[5]/td[2]").InnerText.Replace('Calling Numb...
266 New-Object PsObject -Property @{ Time = $time; Type = $inOut; From = $from; To = ...
267 New-Object PsObject -Property @{ Time = $time; Type = $inOut; From = $from; To = ...
268 $texts | % {...
269 C:\temp\textparser.ps1

The final script:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
cd C:\temp\HtmlAgilityPack.1.4.0.beta2.binaries
add-type -Path .\HtmlAgilityPack.dll
$doc = New-Object HtmlAgilityPack.HtmlDocument
$result = $doc.Load("C:\temp\texts.html")
$texts = $doc.DocumentNode.SelectNodes("//table[@class='table-gen']")

$result = $texts | % {
    $testText = $_
    $time = $testText.SelectSingleNode("tr[1]/td").InnerText.Trim()
    $time = $time.TrimEnd(" CST")
    $time = ([DateTime] $time).AddHours(-2)
    $inOut = $testText.SelectSingleNode("tr[2]/td").InnerText.Replace('Description:','').Trim()
    $to = $testText.SelectSingleNode("tr[5]/td").InnerText.Replace('Number Called:','').Trim()
    $from = $testText.SelectSingleNode("tr[5]/td[2]").InnerText.Replace('Calling Number:','').Trim()

    New-Object PsObject -Property @{ Time = $time; Type = $inOut; From = $from; To = $to } |
        Select From,To,Type,Time
}

$result | Sort Time | ft -auto | out-string -width 75

All in all, the HTML Agility Pack is a very attractive approach that I plan to start using more often.

Comments [0] | | # 
 Monday, March 01, 2010
Monday, March 01, 2010 10:02:24 PM (Pacific Standard Time, UTC-08:00) ( )

I recently got a cool USB MIDI keyboard (M-Audio Axiom 49) and software (Propellerhead Reason) combo that lets me play keyboard with all kinds of cool sounds effects and general fun. Despite how cool the system is, it’s not quite as spontaneous as a plain ol’ electronic keyboard. Just to piddle around for a bit, you have to turn on the keyboard, launch Reason, create a new song, add a virtual instrument, and then start playing.

How can PowerShell of all things help the anguish of a struggling keyboard hack?

The answer comes in two parts – creating a template song, and then having PowerShell launch it when you turn on the keyboard.

It turns out that you can resolve a lot of the issues in Reason by just creating a good starter song. Add in the mixer, a synthesizer, and save it to disk – I called it “Default.rns.” When you double-click on this file, Reason starts up with all of the presets you had configured.

The second part is more tricky, unless of course you happen to have Real Ultimate Power.

The core of this solution comes in three parts:

  1. WMI lets you enumerate USB devices on the system.
  2. The Register-WmiEvent cmdlet lets you respond to WMI events.
  3. WMI has a default __InstanceCreationEvent class that lets you respond to ANY new instance being created (Services, Processes, etc.)

The answer to #1 has come up a few times on the Team Blog:

http://blogs.msdn.com/powershell/archive/2007/02/24/displaying-usb-devices-using-wmi.aspx
http://blogs.msdn.com/powershell/archive/2009/01/10/get-usb-using-wmi-association-classes-in-powershell.aspx

I had no idea what the Axiom keyboard was being recognized as, so I did the easy thing. Ran the query once, turned off the keyboard, and then ran it again. Compare-Object to the rescue:

001
002
003
$before = gwmi Win32_USBControllerDevice |% { [wmi] $_.Dependent }
$after = gwmi Win32_USBControllerDevice |% { [wmi] $_.Dependent }
Compare-Object $before $after -PassThru

That gives an object like:

Availability                :
Caption                     : USB Axiom 49
ClassGuid                   : {4d36e96c-e325-11ce-bfc1-08002be10318}
CompatibleID                : {USB\Class_01&SubClass_01&Prot_00, USB\Class_01&SubClass_01, USB\Class_01}
ConfigManagerErrorCode      : 0
ConfigManagerUserConfig     : False
CreationClassName           : Win32_PnPEntity
Description                 : USB Audio Device
DeviceID                    : USB\VID_0763&PID_0199&MI_00\6&2F841C5F&0&0000
ErrorCleared                :
ErrorDescription            :
HardwareID                  : {USB\VID_0763&PID_0199&REV_0105&MI_00, USB\VID_0763&PID_0199&MI_00}
InstallDate                 :
LastErrorCode               :
Manufacturer                : (Generic USB Audio)
Name                        : USB Axiom 49
PNPDeviceID                 : USB\VID_0763&PID_0199&MI_00\6&2F841C5F&0&0000

There’s the ticket. It’s actually a Win32_PnpEntity. While the previous WMI query helped us discover USB entities, now that we know how the keyboard is identified to the system, we can drop the complicated WMI dependency traversal, and turn this specific goal into a simpler query:

Get-WmiObject Win32_PnPEntity –Filter "Name='USB Axiom 49'"

Now that we know which WMI class represents the keyboard, and the query that selects it specifically, we can use WMI’s __InstanceCreationEvent to monitor for that instance as it comes and goes. The Register-WmiEvent cmdlet makes this a snap:

001
002
003
004
005
006
007
008
009
010
function Register-KeyboardEvent
{
    $query = "SELECT * FROM __InstanceCreationEvent " +
         "WITHIN 5 " +
         "WHERE TargetInstance ISA 'Win32_PnPEntity' " +
         "AND TargetInstance.Name = 'USB Axiom 49' "

    $null = Register-WmiEvent -Query $query -SourceIdentifier KeyboardMonitor -Action { E:\lee\reason\Default.rns }
}

After calling this function, PowerShell automatically launches Reason with your favourite presets already loaded whenever you turn on the USB keyboard.

Comments [2] | | # 
 Friday, February 26, 2010
Friday, February 26, 2010 9:25:54 AM (Pacific Standard Time, UTC-08:00) ( )

I’ve got a neat application that I use to map keystrokes to background PowerShell functions / script blocks. I recently started using Pandora more often, and the killer problem is when somebody drops by the office to ask a quick question. You dig around all of your open Explorer windows until you find the one for Pandora, then find the Pause button.

Here’s a function that does all of that for you, and maps it to Control+Alt+P:

001
002
003
004
005
006
007
008
009
010
011
012
013
function PausePandora
{
    $ie = New-Object -Com Shell.Application
    $pandora = $ie.Windows() | ? { $_.LocationName -like "*Pandora Radio*" }
    if($pandora)
    {
        $pandora.Navigate2("http://www.pandora.com/#/paused")
    }
    $ie = $null
}

## Pause Pandora
$keyMapping['Control,Alt,P'] = @{ Action =  { PausePandora } }
Comments [2] | | # 
 Wednesday, February 24, 2010
Wednesday, February 24, 2010 8:09:42 AM (Pacific Standard Time, UTC-08:00) ( )

We're getting close to "content complete" of the Windows PowerShell Cookbook, 2nd edition. The next step is technical review, where we look for both high-level and low-level feedback on the content and structure.

Also, this is a book focused on administrators. While PowerShell uber-hackers are always appreciated, inexperience with PowerShell is extremely valuable, as well.

If you're interested in being a Technical Reviewer of the book, participate in its Open Beta!

(Update 03/19/2010 – the Cookbook is now in an Open Beta!)

Comments [0] | | #