Pscx 1.2 progress – archive read and extract support coming

In the latest PowerShellCX changeset, archive read and extract support is finally available. Sorry there are no binary builds yet. Use the Extract-Archive cmdlet as a stand-alone to extract all files. Use Read-Archive to generate ArchiveEntry objects which can be piped through Where-Object for filtering and eventual consumption by Extract-Archive (which can accept ArchiveEntry as well as FileInfo pipeline input). I’ve got progress reporting working too for extract. I’ve added support for reading/extracting SevenZip, Arj, BZip2, Cab, Chm, Cpio, Deb, GZip, Iso, Lzh, Lzma, Nsis, Rar, Rpm, Tar, Wim, Z & Zip.  Yes, you did see ISO support in that list ;-) Write support is only zip, bzip2, tar and gzip still.

There are more features coming, including encrypted archive support.

Get a quick database connection with a nice GUI

I just hacked this one up a few minutes ago to let me get an Oracle, Access, Excel/CSV or SQL Server database connection quickly without fiddling with provider strings and other things that just fall out of my head as fast as they get put in. It uses an old trick of creating a zero-length file with an UDL extension and then executing it. The function returns an operable OleDbConnection object, albeit a closed one unless you specifiy –Open as a switch.

  1. function get-oledbconnection ([switch]$Open) {  
  2.     $null | set-content ($udl = "$([io.path]::GetTempPath())\temp.udl");  
  3.     $psi = new-object Diagnostics.ProcessStartInfo  
  4.     $psi.CreateNoWindow = $true 
  5.     $psi.UseShellExecute = $true 
  6.     $psi.FileName = $udl 
  7.     $pi = [System.Diagnostics.Process]::Start($psi)  
  8.     $pi.WaitForExit()  
  9.     write-host (gc $udl)  
  10.     if (gc $udl) {  
  11.         $conn = new-object data.oledb.oledbconnection (gc $udl)[2]  
  12.         if ($Open) { $conn.Open() }  
  13.     }  
  14.     $conn 
  15. }  

You’ll notice I said OleDbConnection. This means you should pick an OLEDB provider on the provider page for best (read: working) results. Cancelling returns nothing.

image

Ah, how I love the little blue box of typographical terrific-ness.

PSParser Tricks #1 – Resolve all Aliases to definitions in a script

Why do such a thing? Well, if you’ve created your own aliases for commands and you try to give someone your ps1 script file, it will not run because they have not got the same aliases defined as you. Also, scripts that use fully-resolved names like “Get-ChildItem” are more readable for a newcomer to PowerShell than one that is using the unix-like “ls" alias for example. "Get-ChildItem" leads quite directly to the MSDN documentation, but "ls" might lead you anywhere. Before you publish a script online somewhere for the world to use, it’s important that you try to remove any aliases and replace them with the native command names.

Doing this kind of thing has been talked about before but it was always a very difficult thing to do with PowerShell v1.0, what with the lack of BNF documentation describing the grammar etc. Thankfully, it's a lot easier to do with PowerShell v2.0 (currently at release CTP2) because the team has exposed the Tokenizer for use in scripts. There's been suprisingly little use of it so far, so I figured I'd start the ball rolling with a series of articles based around it. So, let's look at an example script that uses aliases and put it through the meat grinder:

image

As you can see, it spits out the expanded script to the output stream. The informational messages are written to the host, so they won't interfere if you redirect the output to a file like: .\resolve-aliases.ps1 in.ps1 > out.ps1

Here's the Resolve-Aliases.ps1 script itself:

  1. #requires -version 2  
  2. param($filename = $(throw "need filename!"))  
  3.  
  4. $lines = $null 
  5. $path = Resolve-Path $filename -ErrorAction 0  
  6.  
  7. if ($path) {  
  8.     $lines = Get-Content $path.path  
  9. } else {  
  10.     Write-Warning "Could not find $filename" 
  11.     exit  
  12. }  
  13.  
  14. # Initialize  
  15. $parser = [system.management.automation.psparser]  
  16. $errors = new-object system.management.automation.psparseerror[] 0  
  17.  
  18. do {  
  19.     $tokens = $parser::tokenize($lines, [ref]$errors)     
  20.     $retokenize = $false 
  21.       
  22.     if ($errors.count -gt 0) {  
  23.         Write-Warning "$($errors.count) error(s) found in script." 
  24.         $errors 
  25.         exit  
  26.     }  
  27.  
  28.     # look through tokens for commands  
  29.     $tokens | % {  
  30.         if ($_.Type -eq "Command") {  
  31.             $name = $_.Content  
  32.               
  33.             # is it an alias?  
  34.             # we use -literal here so '?' isn't treated as wildcard  
  35.             if ((!($name -eq ".")) -and (Test-Path -LiteralPath alias:$name)) {  
  36.                   
  37.                 # gcm may return more than one match, so specify "alias"  
  38.                 # filtering against name kludges the '?' alias/wildcard   
  39.                 $command = gcm -CommandType alias $name | ? { $_.name -eq $name }  
  40.                               
  41.                 # resolve alias which may lead to another alias  
  42.                 # so loop until we reach a non-alias  
  43.                 do {  
  44.                     $command = Get-Command $command.definition  
  45.                 } while ($command.CommandType -eq "Alias")  
  46.                   
  47.                 Write-Host -NoNewline "Resolved " 
  48.                 Write-Host -NoNewline -ForegroundColor yellow $name 
  49.                 write-host -nonewline " to "   
  50.                 write-host -ForegroundColor green $command.name           
  51.                   
  52.                 # Use a stringbuilder to replace the alias in the line  
  53.                 # pointed to in the Token object. StringBuilder has a much  
  54.                 # more precise Replace method than String. This allows us to  
  55.                 # replace the token with 100% confidence.  
  56.                 $sb = New-Object text.stringbuilder $lines[$_.startline - 1]  
  57.                 $sb = $sb.replace($name, $command.Name, $_.startcolumn - 1, $_.length)  
  58.                 $lines[$_.startline - 1] = $sb.tostring()  
  59.                   
  60.                 # now that we've replaced a token, the script needs to be reparsed  
  61.                 # as offsets have changed on this line.   
  62.                 $retokenize = $true 
  63.                   
  64.                 # break out of pipeline, (not 'do' loop)  
  65.                 continue;  
  66.             }  
  67.         }  
  68.     }  
  69. } while ($retokenize)  
  70.  
  71. Write-Host "" # blank line  
  72.  
  73. # output our modified script  
  74. $lines 

Of course, this requires PowerShell v2.0 CTP2. Next in the series, I'll give you a script to check your ps1 scripts for backwards compatibility against PowerShell 1.0. That should be handy for those naughty admins out there who despite all the warnings have installed v2 in production. ;-)

Workaround to force PowerShell redirection operator to use ASCII encoding

Someone on a private mailing list I'm on lamented the problem with powershell's '>' redirection operator defaulting inflexibly to use unicode for encoding the output file. This is not very compatible for NT's ancient console subsystem which works best with ASCII data. Fortunately, there's an easy workaround to fix this:

Due to the magic of command discovery and the fact that > really does use out-file, you can "fix" this by placing the following in your $profile:

function out-file($FilePath, $Encoding, [switch]$Append) {
$input | microsoft.powershell.utility\out-file $filepath -encoding ascii `
     -append:$append
}


From now on, > will be forced to use ASCII encoding. This works because functions have higher precedence than built-in commands in powershell's command discovery search.

UPDATE: Rather annoyingly, I'm informed that this particular workaround doesn't work on v1.0 of PowerShell. I tested the above on v2.0CTP2 only. Doh.

VMWare and Windows Server 2008 – Choppy Display Performance

Just another quick-fix post for any readers’ benefit. I have been using MOSS on Windows 2008 Server on VMWare for a while now and the display has always been sluggish and choppy even though VMWare tools is up-to-date and installed. I decided to take a quick peek at the display properties to see if perhaps hardware acceleration is off or something like that and I noticed that the display driver was “Standard VGA Display.” I thought to myself, “Shouldn’t that be a VMWare display driver?” so I clicked properties and drilled down to the “Update Driver…” dialog. Clickety-click and hey presto, it finds a newer driver, namely “VMWare SVGA II” and installs it. Display is now much better. On other guest OS’s like Win 2003 etc, VMWare tools installation updated the driver for you, but this time it didn’t. Not sure why.

One remaining problem I have is that the mouse is dodgy and sometimes the host mouse pointer gets de-synced with the guest’s. Anyone got that problem?  Fixed by starting device manager and going through nearly the same drill as the display driver. I manually chose “VMWare Pointing Device” and rebooted, replacing the default ps/2 mouse driver.

image

Update:

After I rebooted, it was still a little sluggish. Then I remembered that by default, hardware acceleration is only at one notch up. So, push it up to full!

Dev-SysAdmin Meme

MoW poked at me, so I guess I can’t let the crazy dutchman down:

How old were you when you started using computers?

10 or 11

What was your first machine?

An Amstrad CPC 464 with Green screen.

What was the first real script you wrote?

I vaguely remember being delighted at having a rocket (Chr$(239)) ascend the Amstrad’s screen when I figured out that STEP –1 was the key in getting a for/next loop to count backwards.

What languages have you used?

    • Powershell, VBScript, JavaScript, Tcl, Perl, Batch/4NT
    • Z80A, 8088/8086 (NECv20/v30) assembler
    • Basic, Turbo Pascal, Turbo C, C++, Java, C#, VB.NET

I’m a bit of a jack-of-all-trades when it comes to development. The list above is just what I can remember ;-)

What was your first professional Sysadmin Developer gig?

After helping out a friend’s dad put together a training course for Visual Basic 3.0, I then managed to blag a job coding Ireland’s first ever major e-commerce site using MS Merchant Server 1.0 (which predated ASP 1.0) for a leading ISP.

If you knew then what you know now, would you have started in IT?

I might have started a little bit earlier even!

If there is one thing you learned along the way that you would tell new Sysadmins (or Devs), what would it be?

Just one thing? Learn Regular Expressions as it will pay you back ten-fold for whatever time you put into it.

What is the most fun you have had scripting?

There’s only one answer here: writing extensions for PowerShell.

This particular branch of the meme ends here as I don’t think I can tag anyone who hasn’t been tagged already.

Using SharePoint Designer with .NET 3.5 SP1 Beta

Just a quick one for the frustrated searchers out there. If you've recently installed the SP1 beta for Visual Studio 2008 (or just the 3.5 sp1 beta alone), and you find that you have serious difficulty using SharePoint Designer 2007 against a remote instance of SharePoint running in a VM (or a physical machine) that doesn't have the SP1 Beta bits, you know what to do. My SPD would refuse to load the master page from my virtualized SharePoint instance until I had installed .NET 3.5 SP1 Beta onto the virtual machine also. Just so you know!

Windows Mobile PowerShell Provider

In the spirit of "tidying things up," and pushing out nearly-there projects, I turned my attention to http://www.codeplex.com/PSMobile. I have my fingers in way too many pies I think and I'm just not finding enough time to get things finished off - but enough is enough. I am trying to clear my plate, and this is fruit number #2 of that attempt at self-correction. I have some other important out of band work that I need to complete, but I find I cannot concentrate on that until these personal projects that have been niggling me for ages are dealt with. Anyway, lets dispel a couple of questions with a screenshot:

Requirements

  • ActiveSync 4.2 or higher (or Windows Mobile Device Centre 6.0+ on Vista) Download
  • A Windows Mobile device (PocketPC/SmartPhone 2002, 2003, 2003SE, Windows Mobile 5, 6 or 6.1)
  • Windows PowerShell 1.0 or 2.0 (CTP) Download

Features

wm61-device

  • Copy, Move, Delete items between folders on your device (including Storage Card) with standard PowerShell Cmdlets
  • Move/Copy files to/from your device and your desktop with ConvertTo-WMFile and ConvertFrom-WMFile
  • Get device information and manipulate and explore the registry with a rich device object returned from Get-WMDevice
  • Invoke-Item against remote items to or execute or trigger their associated applications
  • Invoke-Item with -Local switch to attempt to execute a remote file in the context of your local desktop (e.g. office docs or images/videos)
  • New "Mode" attributes specific to Windows Mobile file attributes: (I)nRom, Rom(M)odule
  • File/Folder objects' attributes can be modified with .Attributes properties just like FileInfos etc.
  • Tab completion with MoW's PowerTab Download

File Manipulation

A picture's worth a thousand words.

images-screenshot

Cmdlets and Definitions

Here's a table of the syntax for the included Cmdlets.

Cmdlet Definition WhatIf / Confirm
ConvertFrom-WMFile * [-Path] [-Destination] [-Force] [-Verbose] Yes
  [-LiteralPath] [-Destination] [-Force] [-Verbose] Yes
ConvertTo-WMFile * [-Path] [-Destination] [-Force] [-Verbose] Yes
  [-LiteralPath] [-Destination] [-Force] [-Verbose] Yes
Get-WMDeviceInfo [-Verbose]  
Get-WMMemoryInfo [-Verbose]  
Get-WMStoreInfo [-Verbose]  
Start-WMActiveSync [-Verbose]  
Stop-WMActiveSync [-Verbose]  
Start-WMProcess [-LiteralPath] [[-Arguments] ] [-Verbose]  
Get-WMDevice [-Verbose]  

* These Cmdlets that accept a path will bind to pipeline input via PSPath property name.

If you've got any problems, suggestions or ideas, please post into the discussions board on the web site. Have fun!

PSEventing 1.1 For PowerShell 1.0 Final Release

I took a few hours yesterday to "tidy up my room" so to speak, so I built a nice MSI installer, updated the help, CodePlex Wiki and examples and closed all bugs. This is probably the final release now that PowerShell 2.0 CTP2 has introduced support for eventing, so thanks for all the support.

New Features

  • Multiple named queue support and default queue with -QueueName parameter
  • Better COM support, window message pumping etc.
  • NoFlush / Peek parameter support for queue reading
  • Get-EventQueue command added for viewing queues and their message counts.

Cmdlet Name Changes

  • Get-Event -> Read-Event
  • Connect-EventListener -> Connect-Event
  • Disconnect-EventListener -> Disconnect-Event

Additionally, several niggling bugs closed (including the one where read-event -wait would return immediately with no events).

http://www.codeplex.com/pseventing

For an advanced example: Foreground / Background Swappable Downloads In PowerShell.

Background Timer PowerShell WPF Widget

UPDATE May 26th: You must run PowerShell v2.0 CTP in STA mode for this to work. Start the shell, then run "powershell -sta" from the command line to start a new version of the shell in "single thread apartment" mode (STA). This is required for WPF to work correctly.

That is a bit of a mouthful of a title for this post but it's the best I could come up with. This post takes some of James' scripty bits and Jaykul's scripty bits and shows you how to create a countdown timer written in PowerShell script that runs in the background without blocking input. Just like Jaykul's original clock, you can drag it around and right-clicking it will close it. His version was the current time and it also showed some system resources. I changed it into a countdown and removed the other nested graphs. When it hits 00:00:00 it turns red. Here's what it looks like:

countdown

Here's the source of invoke-background.ps1:

  1. param([string]$scriptName)  
  2.  
  3. # original script James Brundage (blogs.msdn.com/powershell)  
  4.  
  5. $rs = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()  
  6. $rs.ApartmentState, $rs.ThreadOptions = “STA”, “ReuseThread”  
  7. $rs.Open()  
  8.  
  9. # Reference the WPF assemblies  
  10. $psCmd = {Add-Type}.GetPowerShell()  
  11. $psCmd.SetRunspace($rs)  
  12. $psCmd.AddParameter("AssemblyName", "PresentationCore").Invoke()  
  13. $psCmd.Command.Clear()  
  14.  
  15. $psCmd = $psCmd.AddCommand("Add-Type")  
  16. $psCmd.AddParameter("AssemblyName", "PresentationFramework").Invoke()  
  17. $psCmd.Command.Clear()  
  18.  
  19. $psCmd = $psCmd.AddCommand("Add-Type")  
  20. $psCmd.AddParameter("AssemblyName", "WindowsBase").Invoke()  
  21.  
  22. $sb = $executionContext.InvokeCommand.NewScriptBlock(  
  23.     (Join-Path $pwd $scriptname)  
  24. )  
  25.  
  26. $psCmd = $sb.GetPowerShell()  
  27. $psCmd.SetRunspace($rs)  
  28. $null = $psCmd.BeginInvoke()  

Next, here's the modified clock script:

  1. param (  
  2.     [timespan]$period = (New-Object system.TimeSpan(0,5,0)),  
  3.     $clockxaml="<path to xaml file>\clock.xaml" 
  4. )  
  5.  
  6. ### Import the WPF assemblies  
  7. Add-Type -Assembly PresentationFramework  
  8. Add-Type -Assembly PresentationCore  
  9.  
  10. $clock = [Windows.Markup.XamlReader]::Load(   
  11.          (New-Object System.Xml.XmlNodeReader (  
  12.             [Xml](Get-Content $clockxaml) ) ) )  
  13.  
  14. $then = [datetime]::Now  
  15.  
  16. $red = [System.Windows.Media.Color]::FromRgb(255,0,0)  
  17. $redbrush = new-object system.windows.media.solidcolorbrush $red 
  18. $label = $clock.FindName("ClockLabel")  
  19. $done = $false 
  20.  
  21. # Create a script block which will update the UI  
  22. $updateBlock = {     
  23.    if (!$done) {  
  24.         # update the clock  
  25.         $elapsed = ([datetime]::Now - $then)  
  26.         $remaining = $null;  
  27.           
  28.         if ($elapsed -lt $period) {  
  29.             $remaining = ($period - $elapsed).ToString().substring(0,8)  
  30.         } else {  
  31.             $label.Foreground = $redbrush         
  32.             $remaining = "00:00:00" 
  33.             $done = $true 
  34.         }         
  35.         $clock.Resources["Time"] = $remaining 
  36.    }  
  37. }  
  38.  
  39. ## Hook up some event handlers   
  40. $clock.Add_SourceInitialized( {  
  41.    ## Before the window's even displayed ...  
  42.    ## We'll create a timer  
  43.    $timer = new-object System.Windows.Threading.DispatcherTimer  
  44.    ## Which will fire 2 times every second  
  45.    $timer.Interval = [TimeSpan]"0:0:0.50" 
  46.    ## And will invoke the $updateBlock  
  47.    $timer.Add_Tick( $updateBlock )  
  48.    ## Now start the timer running  
  49.    $timer.Start()  
  50.    if(! $timer.IsEnabled ) {  
  51.       $clock.Close()  
  52.    }  
  53. } )  
  54.  
  55. $clock.Add_MouseLeftButtonDown( {   
  56.    $_.Handled = $true 
  57.    $clock.DragMove() # WPF Magic!  
  58. } )  
  59.  
  60. $clock.Add_MouseRightButtonDown( {   
  61.    $_.Handled = $true 
  62.    $timer.Stop()  # we'd like to stop that timer now, thanks.  
  63.    $clock.Close() # and close the windows  
  64. } )  
  65.  
  66. ## Lets go ahead and invoke that update block   
  67. &$updateBlock 
  68. ## And then show the window  
  69. $clock.ShowDialog()  

...and finally the modified clock.xaml file:

  1. <Window xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' 
  2.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  3.         xmlns:system="clr-namespace:System;assembly=mscorlib" 
  4.         WindowStyle='None' AllowsTransparency='True' 
  5.         Topmost='True' Background="Transparent"  ShowInTaskbar='False' 
  6.         SizeToContent='WidthAndHeight' WindowStartupLocation='CenterOwner' > 
  7.    <Window.Resources> 
  8.       <system:String x:Key="Time">12:34.56</system:String> 
  9.    </Window.Resources> 
  10.  
  11.    <Grid Height="2.2in"> 
  12.       <Grid.ColumnDefinitions> 
  13.          <ColumnDefinition/> 
  14.       </Grid.ColumnDefinitions> 
  15.       <Label Name="ClockLabel" Grid.Column="2" Opacity="0.7" Content="{DynamicResource Time}" FontFamily="Impact, Arial" FontWeight="800" FontSize="2in" > 
  16.          <Label.Foreground> 
  17.             <LinearGradientBrush> 
  18.                <GradientStop Color="#CC064A82" Offset="1"/> 
  19.                <GradientStop Color="#FF6797BF" Offset="0.8"/> 
  20.                <GradientStop Color="#FF6797BF" Offset="0.4"/> 
  21.                <GradientStop Color="#FFD4DBE1" Offset="0"/> 
  22.             </LinearGradientBrush> 
  23.          </Label.Foreground> 
  24.       </Label> 
  25.    </Grid> 
  26. </Window> 

Important: you'll need to save all files into the same directory and fix up the path to the clock.xaml file in the start-countdown.ps1 script.

Have fun!

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List