Archives for the Month of May, 2006

How do I learn the Dvorak keyboard layout?

Well, it looks like Scott had to go and drum us Dvorak nuts out of the ground.  It brought up a good question, though -- how do you go about learning the Dvorak keyboard layout?

The Dvorak keyboard layout, for those that do not know, was designed from the start to be maximally efficient.  It places the most common letters on the home row, and arranges the letters to maximize both the balance and alternation between hands while typing.  There are some that disagree with its effectiveness, but those looking to learn Dvorak typically aren't part of that crowd.

Advanced touch-typists (i.e.: > 60 wpm) don't typically notice much of a speed improvement in moving to the Dvorak keyboard layout, but the improved ergonomics are very noticeable.  If you don’t already touch-type (or do so slowly,) then you’ll gain significant benefits from learning the keyboard layout.

I’ve been using the Dvorak layout for about 7 or 8 years, so I thought I’d offer my advice.

One of the mistakes that many people make when learning Dvorak is to rearrange the keys on their QWERTY keyboard.  Not surprisingly, hunt-and-peck Dvorak isn’t much better than hunt-and-peck QWERTY.  The best approach, in my opinion, is to ensure that your hands learn the keyboard layout independently from your eyes.  If you glance down at the keyboard once in awhile to find a key, you will impede your touch typing skill.

Another mistake that many people make is to switch their machines at work first.  This creates an unhealthy sense of urgency (as your typing productivity temporarily plunges,) and brings with it a needless amount of stress.  Instead, I suggest using Dvorak at home (for things like your email, blog posts, etc) until your typing speed gets up to about 30 WPM.

With that in mind, this is how I taught myself the Dvorak keyboard layout:

Day 1

  1. Benchmark your current QWERTY typing speed at a site such as http://www.typingtest.com.  Do a good number of these tests so you can be confident in the measurement.  Dvorak is so much more ergonomic that you will likely feel like you are typing slower – even once you regain your original typing speed.
  2. Print off the above diagram of the keyboard, and fold it / cut it / whatever so that you can prop it against the bottom of your monitor.  Prop it against the bottom of your monitor.
  3. At home, change your keyboard layout to Dvorak, using the tools most probably built into your operating system.  By default, Windows also configures a shortcut (Control-Shift) to switch between layouts.  I disable this as it tends to interfere with using the keyboard to select text. 

  4. Log off, and log back in.  A keyboard layout change doesn’t affect currently running programs, so this gets you back to a fresh state.
  5. Open Notepad, and familiarize yourself with the keyboard layout.  Starting with your hands on the home row, go through the entire alphabet several times.  Don’t look at your fingers, but instead look at the keyboard chart.  When you make a mistake, look at the chart to see what key you pressed, and which one you should have pressed.
  6. While still in Notepad, try some other sentences, like “The quick brown fox jumped over the lazy dog.”
  7. Respond to / write some email, blog, and take part in other typing tasks. 
  8. Don’t push steps 5 / 6 / 7 past ~ 2 hours, or your return on investment will be pretty low.
  9. Sleep, and let your mind study Dvorak while you rest.

Day 2

  1. Repeat steps 5, 6, and 7, again stopping after about 2 hours.   At this point, you’re getting close to touch-typing Dvorak, but just very slowly.
  2. Check your current typing speed at the same site you used on Day 1.

Day 3 and on

  1. Keep using your home computer the way you would normally.
  2. Keep checking your typing speed.
  3. When you get to about 30 wpm, change the keyboard layout of your work machine as well.  At this point, you will quickly regain your previous typing speed.

Month 1 or 2 (and occasionally afterwards)

  1. Print out a QWERTY keyboard layout, and change all of your computers back to QWERTY. Touch type in this layout again for awhile to ensure that your skill doesn’t erode.

Caveats:

  • Some programs (such as Emacs and VI) use hotkeys so heavily that you remember them through muscle memory, and not by their individual characters.  This muscle memory becomes a slight hindrance when you learn the program in a new keyboard layout.  In addition, programs that use positional hotkeys (like cursor movement in VI) become slightly annoying to use as the keys are now scattered around the keyboard.
  • Typing with one hand (while you hold the phone with the other / etc) becomes nearly impossible.  You know only how to touch type – so the left hand has no idea what characters sit on the right hand side of the keyboard!
  • Password changes are sometimes perilous.  Be very sure which keyboard layout is active when you change your password.  It is quite difficult to figure out your effective password when you typed it in Dvorak– only to find out that the boot sequence actually recorded it in QWERTY.
  • Coworkers that attempt to use your keyboard will think you are a helpless geek, if they don’t already.

For more good resources on the Dvorak keyboard layout, check out Marcus Brooks' site, or the rest of the internet.  Good luck!

 

Shortcut to Select the Next Edit Box in Internet Explorer

One of my pet peeves with Internet Explorer comes as I read a site or navigate the internet with the keyboard – but then have to use the mouse to click on a single, isolated edit box.  One solution is to use the ‘Tab’ key until I reach the edit box, but a page full of links can often require 50 or more presses of the ‘Tab’ key to get the edit box.

Here’s a perfect example.  You go to your search engine of choice (Win+R, www.ouhn.net or www.sfjl.net) and type in a query.  The query doesn’t give you the approximate results you hope for, so you press ‘Tab’ 15 times to get back to the edit box.

Here’s a solution that accomplishes that much more efficiently.  It has a lot of moving parts, but definitely works:

The Javascript Bookmarklet.  Bookmarklets are small snippets of Javascript that execute from your browser’s address bar.  For example, try the following in your address bar:

  • javascript:alert("hello world")
  • javascript:void(document.bgColor="#CCFFCC")

They are great, because they let you inject script into the page itself – giving you access to the content, DOM, and much more.  As far as I know, the concept originates from http://www.bookmarklets.com in 1998.  Think of them as the limited-length precursor to Firefox’s GreaseMonkey scripts.

Bookmarklets are usually published on a page as a simple Javascript link.  Rather than click it, you drag the link up to the “Links” section of your address bar.  After you accept the warning that the link may be unsafe, you can then click on the link in your “Links” bar to invoke whatever functionality the Bookmarklet surfaces.  That forms the core of the solution.

To solve the problem, we use two bookmarklets: one to set the focus to the previous editable DOM element, and one to set the focus to the next editable DOM element.  Drag the URLs below to your ‘Links’ bar in Internet Explorer to let you use these buttons to quickly focus the next text box. 

[Prev]
Content: javascript:var d = document; var e = d.all; var fc  = false; var c = e.length - 1; while(! (fc && e[c].isContentEditable)) { if(e[c].uniqueID == d.activeElement.uniqueID) if(fc) { break; } else { fc = true; } c = (c + e.length - 1) % e.length } e[c].focus()

[Next]
Content: javascript:var d = document; var e = d.all; var fc  = false; var c = 0; while(! (fc && e[c].isContentEditable)) { if(e[c].uniqueID == d.activeElement.uniqueID) if(fc) { break; } else { fc = true; } c = (c + 1) % e.length } e[c].focus()

 

 

In short, these links cycle through every element of the DOM, stopping at the first one before or after your “current” element.

The Hotkey.  We haven’t actually solved the problem yet, as you still have to click on the link button to have it focus the next edit box.  To solve that problem, use a Hotkey / Macro program that automates your keystrokes.

Since these are the only two links in my “Links” bar, I can access the “Previous” button with the following sequence of keystrokes:

  1. F10 to open the menu bar
  2. ‘A’ to open the favourites menu
  3. ‘L’ [ENTER] to open the links submenu
  4. ‘P’ to select the ‘Prev’ saved link.
  5. ALT to get the cursor out of the menu bar

To access the ‘Next’ saved link, I use ‘N’ instead in step d.

To actually automate these keystrokes, I use a program called AutoHotkey.  When I’m in Internet Explorer, I have AutoHotkey map Control+Alt+N to the the keystroke sequence for the ‘Next’ link, and Control+Alt+P to the ‘Prev’ link.

Here is its configuration script:

;;
;; Next text box in IE
;;
#IfWinActive, ahk_class IEFrame
^!n::Send {F10}al{ENTER}n{ALT}
#IfWinActive

;;
;; Previous text box in IE
;;
#IfWinActive, ahk_class IEFrame
^!p::Send {F10}al{ENTER}p{ALT}
#IfWinActive

Works like a charm.

Nothing solves everything – PowerShell and other technologies

Raymond recently wrote a post titled, “A new scripting language doesn’t solve everything.”  In that post (and in a previous one,) he points out that backwards compatibility is extremely important for cmd.exe (with itself,) and for PowerShell (with existing cmd.exe scripts.)  More importantly, the point is that PowerShell doesn’t solve everything:

Shipping a new command shell doesn't solve everything either. For one thing, you have to decide if you are going to support classic batch files or not. Maybe you decide that you won't and prefer to force people to rewrite all their batch files into your new language. Good luck on that.

On the other hand, if you decide that you will support batch files after all, then presumably your new command shell will not execute old batch files natively, but rather will defer to CMD.EXE. And there's your problem: You see, batch files have the ability to modify environment variables and have the changes persist beyond the end of the batch file.

Similar threads have come up in the newsgroups in relation to VBScript.

Raymond is absolutely correct, and you won’t find a single person on the PowerShell team that says otherwise.  Or has ever said otherwise, for that matter.  PowerShell is not a swap-in replacement for cmd.exe, it is not a swap-in replacement for the Windows Scripting Host, does not need to re-implement every command-line tool that ships in Windows, and does not solve world hunger.  Tensions that suggest differently are misguided.

Many people – and many systems – depend on the subtle details of the tool with which they’ve chosen to solve their problem.  If you want to write an application that understands the source files for all related technologies (ie: cmd.exe scripts, or .vbs files,) then you’d better make sure that you re-implement every nuance and bug as well.  That includes the bugs that even the owners of those related technologies don’t know about, because I guarantee that a script somewhere depends on it.

The solution isn’t to burn your time by rewriting your systems, either.  If you have code that works – keep it!  I currently have files from 41 different technologies in my “tools” directory:

[D:\lee\tools]
PS:170 > dir | group Extension | sort -desc Count

Count Name                      Group
----- ----                      -----
  275 .exe                      {adlb.exe, ansi2knr.exe, atmar
   59 .ps1                      {#share-file.ps1, backup-chang
   39 .bat                      {articlecounter.bat, c.bat, ch
   25 .dll                      {acctinfo.dll, AutoItX3.dll, B
   20 .cmd                      {d.cmd, devdiv.cmd, dumpfsmos.
   16 .vbs                      {checkrepl.vbs, clean.vbs, clo
    8 .js                       {calc.js, cio.js, d2.js, evt2c
    7                           {%backup%~, 0, _viminfo, autom
    6 .config                   {cordbg.exe.config, perfcompar
    5 .hlp                      {depends.hlp, srvmgr.hlp, usrm
    5 .ini                      {KeePass.ini, memtriage.ini, r
    5 .txt                      {cmgetcer.txt, getcm.txt, inst
    4 .doc                      {kernrate.doc, mqcast.doc, prn
    4 .inf                      {clusfileport.inf, clusfilepor
    3 .reg                      {intfiltr.reg, samplereasons.r
    3 .chm                      {clusterrecovery.chm, eventcom
<snip>

Rewriting for the sake of rewriting provides a poor return on investment – interop is the key word here.  If you want to extend your existing systems, you can do that in the tool of your choice.  Most systems can be extended to include other technologies – a typical Windows build, for example, takes advantage of dozens of different tools.  If you want to write new scripts or systems, you can also do that in the tool of your choice. 

In both cases, PowerShell happens to be a tool that solves certain problems extremely efficiently.

To address one specific detail in Raymond’s post,

“You see, batch files have the ability to modify environment variables and have the changes persist beyond the end of the batch file.”

Try this script.  From a PowerShell.exe window, this wrapper script allows you to run batch files that persistently modify their environment variables.

############################################################################## 
## 
## Invoke-CmdScript.ps1 
## 
## Invoke the specified batch file (and parameters), but also propigate any 
## environment variable changes back to the PowerShell environment that 
## called it. 
## 
## ie: 
## 
## PS > type foo-that-sets-the-FOO-env-variable.cmd 
## @set FOO=%* 
## echo FOO set to %FOO%. 
##  
## PS > $env:FOO 
##  
## PS > Invoke-CmdScript "foo-that-sets-the-FOO-env-variable.cmd" Test
##  
## C:\Temp>echo FOO set to Test. 
## FOO set to Test. 
##  
## PS > $env:FOO 
## Test 
## 
############################################################################## 

param([string] $script, [string] $parameters) 

$tempFile = [IO.Path]::GetTempFileName() 

## Store the output of cmd.exe.  We also ask cmd.exe to output  
## the environment table after the batch file completes 

cmd /c " `"$script`" $parameters && set > `"$tempFile`" "

## Go through the environment variables in the temp file. 
## For each of them, set the variable in our local environment. 
Get-Content $tempFile | Foreach-Object {  
    if($_ -match "^(.*?)=(.*)$"
    {
        Set-Content "env:\$($matches[1])" $matches[2
    }

Remove-Item $tempFile

[Edit: Updated to make more usable for commands with spaces in their names.]

Accessing Environment Variables in PowerShell

In a recent comment, David asked how to access environment variables in PowerShell.  This is something that we normally just document explicitly: $env:Variable

However, that explicit documentation is actually a shortcut for a feature you may not know about.  It’s a little like dividing fractions – you can do it without ever knowing why it works, just that it works.  Of course, it’s nice to be able to explain why.

As you may know, the ‘$’ character is the way that we access variables in PowerShell.  The probing student might then ask, “Why can we access environment variables the same way we access normal variables.  Isn’t ‘Env:’ the environment provider?  And specific environment variables are children of it?”

Those are good questions.  This is because the Environment provider shares something in common with several other providers – namely support for the *-Content set of core Cmdlets:

[C:\temp]
PS:11 > "hello world" > test

[C:\temp]
PS:12 > get-content test
hello world

[C:\temp]
PS:13 > get-content variable:ErrorActionPreference
Continue

[C:\temp]
PS:14 > get-content function:more
param([string[]]$paths);  if(($paths -ne $null) -and ($paths.length -ne 0))  { ...
       Get-Content $local:file | Out-Host -p    }  }  else { $input | Out-Host ...

[C:\temp]
PS:15 > get-content env:systemroot
C:\WINDOWS

For providers that support the content cmdets, we let you interact with this content through a special variable syntax:

[C:\temp]
PS:16 > $function:more
param([string[]]$paths);  if(($paths -ne $null) -and ($paths.length -ne 0))  { …
       Get-Content $local:file | Out-Host -p    }  }  else { $input | Out-Host …

[C:\temp]
PS:17 > $variable:ErrorActionPreference
Continue

[C:\temp]
PS:18 > $c:test
hello world

[C:\temp]
PS:19 > $env:systemroot
C:\WINDOWS

This variable syntax for content management allows you to both get, and set content:

[C:\temp]
PS:20> $function:more = { $input | less.exe }

[C:\temp]
PS:21> $function:more
$input | less.exe

Now, when it comes to accessing complex provider paths using this method, you’ll quickly run into naming issues:

[C:\temp]
PS:22> $c:\temp\test.txt
Unexpected token '\temp\test.txt' in expression or statement.
At line:1 char:17
+ $c:\temp\test.txt <<<<

The solution to that lies in our escaping support for complex variable names:

[C:\temp]
PS:31 > ${1234123!@#$!@#$12$!@#$@!} = "Crazy Variable!"

[C:\temp]
PS:32 > ${1234123!@#$!@#$12$!@#$@!}
Crazy Variable!

[C:\temp]
PS:33 > dir variable:\1*

Name                           Value
----                           -----
1234123!@#$!@#$12$!@#$@!       Crazy Variable!

… and the content equivalent:

[C:\temp]
PS:34 > ${c:\temp\test.txt}
hello world

So, the way to access environment variables isn’t so unique after all. 

Running PowerShell Scripts from Cmd.exe

One of the things that people often struggle with when they try to use PowerShell scripts as targets of Scheduled Tasks, or launch PowerShell scripts from cmd.exe is the following error message:

C:\Program Files\Windows PowerShell\v1.0>powershell.exe "c:\temp\has space\test.ps1"
'c:\temp\has' is not recognized as a cmdlet, function, operable program, or script file.
At line:1 char:12
+ c:\temp\has  <<<< space\test.ps1

(By the way, you can speed this up by using the -noprofile parameter to powershell.exe)

This is because Powershell doesn’t natively support a parameter for a script to run.  The default command-line argument is “-command,” which defines the command as though you had typed it at the prompt.

For example:

C:\temp\monad>powershell "2+2"
4

This happens to work on script names with no spaces or quotes, as our interpreter interprets that as a command execution.  For scripts with spaces and quotes, you are doing the equivalent of:

[C:\temp]
PS:13 > c:\temp\has space\test.ps1
'c:\temp\has' is not recognized as a cmdlet, function, operable program, or script file.
At line:1 char:12
+ c:\temp\has  <<<< space\test.ps1
 

[C:\temp]
PS:14 > "c:\temp\has space\test.ps1"
c:\temp\has space\test.ps1

Suggestion: Did you mean to run the command in quotes?  If so, try using & "<command>"

So, the solution is:

[C:\temp]
PS:15 > & 'C:\temp\has space\test.ps1'
Hello World

Or,

C:\temp\monad>powershell "& 'c:\temp\has space\test.ps1'"
Hello World

This is something that we know to be a usability issue.  We’re tracking it internally, but a bug (and votes) on Ms Connect would help us prioritize this properly.

Blogging From PowerShell – Editing Posts With the MetaWeblog API

In the last post, I mentioned that I wanted to update all of my old Monad posts to include a reference to Windows PowerShell.  That way, users who use the "PowerShell" keyword to search for a topic I've covered (under the Monad codename) can still find it.

I provided a script to get all of the posts from a MetaWeblog-enabled host, so our next goal is to actually make the changes.

To do this, we'll follow the get, modify, and set pattern.  You get the object-based representation of a post (covered in the previous entry,) modify its properties in Monad, and then upload that post back to the web host.  For example:

$blog = .\get-posts.ps1 $endPoint $null "username" "password" 1
$blog.description += "<p>Extra content here</p>"
.\set-blogpost.ps1 $endpoint $null "username" "password" $blog $true

To get the posts originally, we used the MetaWeblog API called "getRecentPosts."  Now, to upload the post back to the web host, we use a different API, called "editPost."  As we did in the previous example, we use .Net to actually talk with the web server.

The XML required for the editPost method only requires 5 things: the Post ID (matching the Post ID we got originally,) a username, password, more XML that describes the post, and a flag that determines if we should publish the post or not.

We could use the Set-BlogPost script to craft the request XML, and also dig around in the $blog object to craft its portion of the XML as well.  But we have an opportunity to introduce a better design here.  In Refactoring parlance, the code has a bad "smell" - namely feature envy.  If your code is overly involved with the methods and properties of another object, then probably that other object should do the work instead.  This is the core of Jeffrey's point: "Whenever you are adding some functions, you should make a conscious decision about whether those functions are best exposed as a "function" or as a "type extension"."

Since we own the blog object (we create it in get-posts.ps1,) we'll have the post XML-ify itself in its ToString() method.

So first, our addition to get-posts.ps1:

         ## Pull the ID of the post
         "postid" { $propertyValue = $property.value.stringbreak }
      }

      ## Add the synthetic property     
      $blogEntry | add-member NoteProperty $propertyName $propertyValue
   } 

-------------8<--------------------------------
   ## Add the ToString method
   ## This method formats the post so that it may be used in an edit
   $blogEntry | add-member -force ScriptMethod ToString { 

      ## A function that encoded our content into an XML-friendly
      ## format
      function encode([string] $xml)
      {
         $tempContent = new-object System.IO.StringWriter
         $textwriter = new-object System.Xml.XmlTextWriter $tempContent
         $textWriter.WriteString($xml)
         $tempContent.ToString()
      }

      @"
      <struct>
        <member>
          <name>dateCreated</name>
          <value>
            <dateTime.iso8601>$($this.dateCreated.ToString("yyyyMMddTHH:mm:ss"))</dateTime.iso8601>
          </value>
        </member>
        <member>
          <name>description</name>
          <value>
            <string>$(encode $this.description)</string>
          </value>
        </member>
        <member>
          <name>title</name>
          <value>
            <string>$(encode $this.title)</string>
          </value>
        </member>
        <member>
          <name>categories</name>
          <value>
            <array>
               <data>
              $(
                  foreach($category in $this.categories)
                  {
                     if($category -and $category.Trim()) { "<value>$category</value>" }
                  }
               )
               </data>
            </array>
          </value>
        </member>
        <member>
          <name>link</name>
          <value>
            <string>$(encode $this.link)</string>
          </value>
        </member>
        <member>
          <name>permalink</name>
          <value>
            <string>$(encode $this.permalink)</string>
          </value>
        </member>
        <member>
          <name>postid</name>
          <value>
            <string>$(encode $this.postid)</string>
          </value>
        </member>
      </struct>
"@
   }

-------------8<--------------------------------
   ## Finally output the object that represents the post
   $blogEntry
}

Then, our script to upload the post:

param([string] $endPoint, [string] $blogid, [string] $username, [string] $password, $blogPost, [bool] $publish = $true)

$postTemplate = @"
<methodCall>
  <methodName>metaWeblog.editPost</methodName>
    <params>
      <param>
        <value>$($blogPost.postId)</value>
      </param>
      <param>
        <value>$username</value>
      </param>
      <param>
        <value>$password</value>
      </param>
      <param>
        <value>
           $blogPost.ToString()
        </value>
      </param>
      <param>
         <value>
            <boolean>$(if($publish) { 1 } else { 0 })</boolean>
         </value>
      </param>
    </params>
</methodCall>
"@

(new-object System.Net.WebClient).UploadString($endPoint, $postTemplate)

And finally, the script to drive it all:

$endpoint = "Endpont Here"
$username = "Username Here"
$password = "Password Here"

$posts = .\get-posts.ps1 $endPoint $null $username $password 9999
foreach($post in $posts)
{
   if(($post.description -match "monad|msh") -and ($post.description -notmatch "powershell"))
   {
      $post.description += "<p>[<i>Edit: Monad has now been renamed to Windows PowerShell.  " + 
         "This script or discussion may require slight adjustments before it applies directly " + 
         "to newer builds.</i>]</p>"
      $post.postid
      .\set-blogpost.ps1 $endpoint $null $username $password $post $true
   }
}

What's unreal is that all of this fancy code is only 89 lines of code when you exclude the XML templates and comments!

[D:\Lee\blogs\Monad]
PS:252 > ((gc get-posts.ps1,set-blogpost.ps1) -notmatch "#|<" | measure-object).Count
89

Blogging From PowerShell – Retrieving Posts With the MetaWeblog API

One of the most difficult aspects of our name change is that we have thousands of pages (spanning hundreds of posts) that use the keyword, “Monad” instead of “PowerShell.”  For users just starting with PowerShell, that means that the content basically does not exist.  If you search for a solution to “PowerShell Hosting,” you find nothing related to Windows PowerShell.

[Tip: If an internet search for PowerShell help comes up fruitless, try it again using Monad as a keyword.  A vast wealth of PowerShell (when it was called Monad) blogging dates back as far back as September, 2004!]

To solve this, I’m going to edit every single one of my PowerShell-related posts to include the following snippet: “[Edit: Monad has now been renamed to Windows PowerShell.  This script or discussion may require slight adjustments before it applies directly to newer builds.]”

I’m not about to do this by hand, of course.  I used the MetaWeblog API to port the content from the old Monad Team Blog to the new PowerShell blog, and was quite pleased with the results.  I may show that one-off script soon, but wanted to give some more detailed explanation of the underlying principles first.

Ideally, I want an experience like this:

$posts = get-blogPosts <parameters>
foreach($post in $posts)
{
   $post.Description += <PowerShell edit string>
   set-post $post
}

So, the first requirement of that scenario leads us to writing a script that produces “BlogPost Objects” – one for each post on the blog.  Here it is:

 

##############################################################################
##
## Get-BlogPosts.ps1
##
## Get the posts to a blog that you own, using the MetaWeblog API
## Returns a strongly-structured set of object that represent the
## posts
##
## Example Usage:
##   $endPoint = "http://www.yourblog.com/blogger.aspx"
##   .\get-posts.ps1 $endPoint $null "username" "password" 3 | ft
##
##############################################################################

param([string] $postUrl, [string] $blogid, [string] $username, 
      [string] $password, [int] $numberOfPosts)

## Post template as required by the metaWeblog.getRecentPosts
## call format
$postTemplate = @"
<methodCall>
  <methodName>metaWeblog.getRecentPosts</methodName>
    <params>
      <param>
        <value>$blogid</value>
      </param>
      <param>
        <value>$username</value>
      </param>
      <param>
        <value>$password</value>
      </param>
      <param>
        <value><i4>$numberOfPosts</i4></value>
      </param>
    </params>
</methodCall>
"@

## Perform the actual post to the server, and transform the response to
## XML
$responseContent = 
   (new-object System.Net.WebClient).UploadString($postUrl, $postTemplate)
$results = [xml] $responseContent

## Go through each of the items in the response to pick out the properties
foreach($item in $results.methodResponse.params.param.value.array.data.value)
{
   ## Prepare our synthetic object
   $blogEntry = new-object System.Management.Automation.PSObject

   ## Go through each of the properties in the current post
   ## For each, compare its property name to one that we know.  From there,
   ## convert the property value into as strongly-typed of a representation
   ## we can muster
   foreach($property in $item.struct.member)
   {
      $propertyName = $property.name
      $propertyValue = $property.value

      switch($propertyName)
      {
         ## The date the post was created.  Uses ISO8601, which is not
         ## natively supported in .Net.  Returned as a [DateTime]
         "dateCreated" 
         { 
            $propertyValue = 
               [DateTime]::ParseExact($property.value."dateTime.iso8601", `
                  "yyyyMMddTHH:mm:ss", `
                  [System.Globalization.CultureInfo]::InvariantCulture)
            break
         }
         
         ## Pull the simple description (content of the post)
         "description" { $propertyValue = $property.value.stringbreak }
         
         ## Pull the title of the post
         "title" { $propertyValue = $property.value.stringbreak }

         ## Pull the categories of the post.  Returned as an array
         ## of strings
         "categories" 
         {
            $propertyValue = @()
            
            foreach($category in $property.value.array.data.value)
            {
               $propertyValue += @($category.string)
            }
 
            break 
         }

         ## Pull the link to the post, returned as an [URI]
         "link" { $propertyValue = [URI] $property.value.stringbreak }

         ## And the permalink to the post, returned as an [URI]
         "permalink" { $propertyValue = [URI] $property.value.stringbreak }

         ## Pull the ID of the post
         "postid" { $propertyValue = $property.value.stringbreak }
      }

      ## Add the synthetic property     
      $blogEntry | add-member NoteProperty $propertyName $propertyValue
   }

   ## Finally output the object that represents the post
   $blogEntry
}

 

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Jim Launches His PowerShell Blog

It's an interesting world we live in.  As I was checking my Technorati search for "PowerShell," I noticed the first PowerShell blog imposter!  The person starts off, "I'm a PM on the PowerShell team and i'm largely responsible for the PowerShell language ..."

We don't have any Program Managers that blog.

So I click into the blog, with a wry smile on my face -- thinking how much you all will like the story.  Then I realize exactly how much faster the speed of light is than word-of-mouth.

Jim Truher, one of our program managers on the PowerShell team launched his blog today.  Amongst other things, Jim teams up with Jeffrey Snover for many of our well-received conference presentations.

Enjoy!

Abhishek Launches His PowerShell Blog

Abhishek Agrawal, another member of the PowerShell team launched his blog today.  Amongst other things, Abhishek blogged the very cool types.mshxml extension to add MSDN help links to the output of Get-Member.

Enjoy!