Monday, June 14, 2010
Updated PDFCreator post
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:
- Add the number to the lines array property of the textbox
- Move the caret (cursor) to the last character in the textbox
- Scroll the textbox view to the caret
- Update the form
- 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
Happy Wednesday!
Things I learned from the Scripting Games 2010 #4: Background Jobs
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:
- Create a variable
$arr = 1..5 - Start a background job that writes that variable to output
$job = Start-Job –Scriptblock { Write-Output $arr } - Receive the job
Receive-Job $job - Notice that there is no output, sadface.
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.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:
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.
- Create a variable:
$arr = 1..5 - Start a background job, passing the variable as an argument:
$job = Start-Job –Scriptblock { Write-Output $args } –ArgumentList $arr - Receive the job
Receive-Job $job - Hey there’s our information!
Get the function here.
Things I learned from the Scripting Games 2010 #3: Test-Connection
if ($pingResults.received -gt 0)
{
# Do something
}
And then I discovered test-connection! Now for simple quick tests we can just do:
{
#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
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
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:
- Create ONE global variable that will hold the event object when it is raised
- 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
- Sleep loop until that global variable isn't $null anymore.
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