Monday, June 14, 2010

Updated PDFCreator post

I've updated my post on running PDFCreator as a service to the latest version, 1.0.1.  It's also now a static page with a link at the top.  Enjoy!

Wednesday, June 9, 2010

Things I learned from the Scripting Games 2010 #5

In this final entry in the ‘Things I learned from the Scripting Games’ series, we’ll go over two different yet related bits of information.  The first is displaying balloon tips in the notification area (System Tray).  The second is displaying log-like feedback in a form generated in PowerShell.  Because they both relate to presenting information or feedback to the user, and because it’s been awhile since I posted anything…and because I promised someone I would try real hard to update my PDFCreator post this week, both subjects fit in one post today.

Balloon Tips

Often times when we need to present the user with information in a graphical way, the messagebox is sufficient.  However, because the messagebox is synchronous by default (that is, the script pauses execution until the messagebox is closed) and there isn’t an easy way to create an asynchronous messagebox, they don’t fit every scenario.  Sometimes we just want to display some information to the user and move on.  Enter balloon tips.

For me, the most challenging part of making a balloon tip function wherever the script is running is the fact that it requires an icon.  How, exactly, is one to deliver an icon when (especially during the scripting games) all we have at our disposal is our PowerShell script file?  Let’s get our object setup and then we’ll answer that question.  First things first, load the forms assemblies:

[void][reflection.assembly]::Load("System.Windows.Forms, `
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

Now we can create a new NotifyIcon object, and setup the text for the balloon tip:

$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
$notifyIcon.BalloonTipTitle = "Balloon Title"
$notifyIcon.BalloonTipText =  "Balloon Text"

So, about that icon…turns out we can extract an icon from a file to use for the NotifyIcon object.  What file with icons should every relevant Windows system have?  Explorer.exe!

$notifyIcon.Icon = [System.drawing.icon]::ExtractAssociatedIcon(`
"$env:windir\explorer.exe")

OK, so now we have everything setup, let’s display that balloon tip.  First we have to make the NotifyIcon visible, then we can display the balloon tip for the number of milliseconds we specify:

$notifyIcon.Visible = $true
$notifyIcon.ShowBalloonTip(3000)

There we go.  Presenting information to the user in a way that doesn’t pause script execution, done.

Display Scrolling Text in a Form Window

Coming up with a “how many times would I actually use this” example for this particular technique has proven difficult.  Maybe it’s because I don’t have much experience creating GUIs designed for end users.  But that said, a wall (or box) of scrolling text seems so deceptively simple that it took me a while to figure out how to accomplish the task.

In advanced event 10 of the scripting games, one element was to present the user with progress of files being moved.  Instantly the picture of the nullsoft installer with it’s wall of text during the install sprung into my mind.  Achieving a similar effect isn’t too involved.  Here’s how we do it.

First we’re going to setup 3 objects: a form, a textbox, and a button.

$form = new-object windows.forms.form
$form.Size = New-Object System.Drawing.Size(270,125)

$txtBox = new-object windows.forms.textbox
$txtbox.Multiline = $true
$txtBox.WordWrap = $false
$txtBox.size = New-Object System.Drawing.Size(250,60)
$txtBox.ScrollBars = [System.Windows.Forms.ScrollBars]::Vertical

$btn = new-object windows.forms.button
$btn.Location = new-object system.drawing.point(0,60)
$btn.Text = "Go!"

$form.Controls.Add($txtBox)
$form.Controls.Add($btn)

So we’ve assigned some size and location information to the controls and added the controls to the form, which is standard business so far.  Two properties of the textbox control are important for this particualr task: Multiline & ScrollBars.  We make sure to set the multiline property to true, and assign vertical scrollbars to the textbox.  I also like to disable wordwrap, but that’s just me.

Next let’s setup the scriptblock that will run when we click the button:

$button_OnClick = {
    1..10 | % {
        $txtBox.Lines += $_
        $txtBox.Select($txtBox.Text.Length, 0)
        $txtBox.ScrollToCaret()
        $form.Update()
        start-sleep 1
    }
}
$btn.add_Click($button_OnClick)

So this is pretty simple.  We create a variable ($button_OnClick) and assign it a scriptblock in which we have a small loop.  We take the array created by the 1..10 range, and for each number we:

  1. Add the number to the lines array property of the textbox
  2. Move the caret (cursor) to the last character in the textbox
  3. Scroll the textbox view to the caret
  4. Update the form
  5. Pause for a second

We add that scriptblock to the click event of the button by using the hidden method add_Click($button_onClick).  As a side note in case you’re wondering, you use the –Force parameter of Get-Method to display hidden members.  Lastly let’s show the form:

$form.showdialog()

Give that button a push!  Sure it’s a simplistic example, but it can be used to pretty good effect.  The $form.update() method caught me by surprise, probably because I’m pretty green with this GUI business.  To see what happens without calling the update method, comment out or delete the $form.Update() line and run $form.showdialog() again.  It’s like the whole form freezes while the loop runs!  Bad jujus.

To see a more complete example you can check out my submission for Event 10 of the Scripting Games here.

Wednesday, June 2, 2010

Sorry for the reposts and instability

I've been trying to troubleshoot an issue with the feed posting, and ended up deleting and reposting all the entries from the last month or so.  Sorry about that, hopefully I can get the feed & keyword business straightened out soon.

Happy Wednesday!

Things I learned from the Scripting Games 2010 #4: Background Jobs

Okay, so background jobs themselves aren’t really the subject of this post.  But a handy function to make efficient use of them, and a quick gotcha when using background jobs via Start-Job is.  Often times we need to execute the same code over and over, with one or two variations in each iteration.  That's what the function we’ll look at today is built to do.  The two mandatory parameters for this function, -scriptblock & -collection, really allow for some flexible backgrounding of similar tasks.  Take a moment and think, how many times have I needed to run the same or similar code against 10+ computers, users, files, directories, etc.  Probably doesn’t take too long before you have at least a few examples.

Before we get into the function let’s address the gotcha.  I learned about a new parameter to Start-Job in Event #5 this year, ArgumentList.  If you look at the documentation on this parameter it is a little bit misleading.  I’ve filed a documentation bug on connect should you wish to vote for it.  The short of it is, even though the parameter help doesn’t mention that the ArgumentList parameter works with the ScriptBlock parameter, it does.  Which is pretty important.  To illustrate this, try the following:
  1. Create a variable
    $arr = 1..5
  2. Start a background job that writes that variable to output
    $job = Start-Job –Scriptblock { Write-Output $arr }
  3. Receive the job
    Receive-Job $job
  4. Notice that there is no output, sadface.
So, what happened?  Why don’t we have any output?  The answer lies in two bits of information.  The first we get from Get-Help about_jobs:
Important: Background jobs that are started by using Start-Job or the AsJob parameter of Invoke-Command rely on the Windows PowerShell remoting infrastructure. To use these features, Windows PowerShell must be configured for remoting, even if the background job is run only on the local computer. For more information, see about_Remote_Requirements.
The second from Get-Help about_scopes:
Sessions: A session is an environment in which Windows PowerShell runs. When you create a session on a remote computer, Windows PowerShell establishes a persistent connection to the remote computer. The persistent connection lets you use the session for multiple related commands.
Because a session is a contained environment, it has its own scope, but a session is not a child scope of the session in which is was created. The session starts with its own global scope. This scope is independent of the global scope of the session. You can create child scopes in the session. For example, you can run a script to create a child scope in a session.
Aha!  The variable scope is separate and distinct inside of the scriptblock that we pass to Start-Job.  So we use the ArgumentList parameter to get the information that we need to the scriptblock.  Let’s see that in action:
  1. Create a variable:
    $arr = 1..5
  2. Start a background job, passing the variable as an argument:
    $job = Start-Job –Scriptblock { Write-Output $args } –ArgumentList $arr
  3. Receive the job
    Receive-Job $job
  4. Hey there’s our information!
Sweet.  Hopefully that information will make working with background jobs a little easier for you.  Below is a function I wrote as part of my solution for Event 5.  It takes a scriptblock and a collection of items & iterates through the collection passing each item into the scriptblock.  By default it displays a progress bar to keep you abreast of the progress of the jobs.  For detailed information on how you can use it and examples, import the function into your powershell session and run ‘Get-Help Start-BackgroundJobs’
Get the function here.

Things I learned from the Scripting Games 2010 #3: Test-Connection

Who knew there was a cmdlet designed just for testing remote connections?  Not me, at least until the scripting games this year.  For the past....forever, I've been doing something like the following to test remote connections

$pingResults = Ping-Host -HostName $ComputerName -Count 2 -Quiet -ErrorAction SilentlyContinue
if ($pingResults.received -gt 0)
{
    # Do something
}

And then I discovered test-connection! Now for simple quick tests we can just do:

if (Test-Connection $ComputerName -count 2 -quiet)
{
    #Do something
}

For larger groups of computers I'll still use PSCX's Ping-Host cmdlet due it's ability to ping all specified hosts asynchronously and it's speed.  You've updated to PSCX 2.0, right?  But Test-Connection is the right tool at the right price for quick connectivity checks.  Learn more about Test-Connection with Get-Help Test-Connection or on Technet.

Things I learned from the Scripting Games 2010 #2: #Requires

So this post comes from the "how embarrassing" folder.  I've been fiddling with and using PowerShell since it was the Monad beta, and until the scripting games I didn't know about #Requires.  '#Requires' is awesome.  You just stick it at or around the start of your script, and the magic happens.  There's a pretty good summary available in PowerShell if you type:

get-help about_Requires

But this all comes with a bit of a caveat, there isn't an option for #Requires for those nifty new things called modules!  So we can '#Requires -version 2' to require PowerShell version 2, we can '#Requires -PSSnapIn Quest.ActiveRoles.ADManagement' to require the Quest AD snap-in....but we can't '#Requires -Module grouppolicy' to require a module.  Bummer.  So how can we require a module the "hard" way?  Here's one way:

function Test-ModuleAvailability([string]$modulename,[switch]$Import)            
{            
    # Check to see if our module is available            
    [System.Management.Automation.PSModuleInfo]$results = `
          Get-Module -ListAvailable | ? { $_.name -ieq $modulename }            
    if ($results)            
    {            
        if ($Import -and $results.sessionState -eq $null)            
        {            
            #Import the module            
            Import-Module $results            
        }            
        #Return true, the module is available            
        $true            
    }            
    else            
    {            
        #Return false, the module is not available            
        $false            
    }                
}

With this function we can easily test for a module's availability, load it if it we need to, and throw an error if $false is returned:

if (-not $(Test-ModuleAvailability FileSystem -Import))            
{            
    Throw "Could not find or load required Module FileSystem"            
}

Not too hard, but maybe those PowerShell guys will hook us up with a beefed up #Requires statement later.

Until tomorrow!

Scripting Games Results

Scripting Games Results

Wow, what a couple of weeks.  Haven't posted much between the scripting games & Exchange 2010 training this week I've been really busy.  Awaiting the official final results from the scripting games, but it looks like I've managed to snag 3rd in the 'Advanced Division'.  Well...at least according to "Clint's Rules" which are different from the official rules - you can download the spreadsheet I created here.  What can I say...I'm a little competitive by nature.  But more importantly, man did I learn a lot from doing the exercises and from reading what other people submitted.  This week I'll try to post one thing I learned each day (maybe even starting today if I can resist the play SC2 beta urge) this week since class is over at 4 and I'm all by myself.  In fact....here we go, the thing I remember first is:

A way better method of looping/waiting for an event and then acting on it:

Look at lines 34-44 in Robert Robelo's Event 6 submission.  Compare that to all the nasty global variables and junk I made to work inside the event handler in my submission for Event 6 (had to make a link to download, the link for my Event 6 submission blows up for some reason).

Which kinda ties into thing #1.5 that I learned - the scriptblocks you supply to the -Action parameter in Register-*Event create a separate variable scope that does not inherit the variables from the script scope etc.  At least that's what it seems like....I could be wrong on that, I haven't looked for any documentation tbh.

So, in my opinion, the way that Robert does it is WAY better than the way I did it, which basically boils down to:
  1. Create ONE global variable that will hold the event object when it is raised
  2. In the -action parameter of Register-*Event just assign the $Event automatic variable (or a property of it if that's all you're interested in) to the global variable that you created in your script
  3. Sleep loop until that global variable isn't $null anymore.
In fact, I totally stole this method from him and used it in a later event that involved eventing.  So....thanks Robert :)

Speaking of Robert, why aren't you reading his blog instead of mine?  He rolled #1 in the Advanced division according to Clint's Rules....better go check out what he has to say.

Until tomorrow,  bubye!!  v(^_^)v