A File Copy and Compress – PowerShell Script

Following on from my previous post. I’m creating a PowerShell script that takes an input of a file location via a dialog. Extracts some files through a copy action, and compresses them into an archive. As I will show in the 3rd post, the purpose is to extract out 2 files to help with deploying a SketchFlow project. Such a complex script is overkill for 2 files, but is easily extensible to handle a larger set of files.

Step 1 – User input of folder:
Create a function to launch a folder select dialog, this is achieved through a com object call.

function Select-Folder($message='Select a folder', $path = 0)
{
    $object = New-Object -comObject Shell.Application
            
    $folder = $object.BrowseForFolder(0, $message, 0, $path)
    if ($folder -ne $null)
    {
        $folder.self.Path
    }
}

Note: Could call this twice to prompt user for output location also.
OR
To speed up script execution supply input parameter(s) to the script [input (and output) directory].

To achieve this simply use $args[0] (and $args[1]) instead of the Select-Folder cmdlet call.

The Select-Folder cmdlet launches:

Browse For Folder Dialog

Browse For Folder Dialog

Step 2 – Extract (via copy) the deployable files:
There are 3 tricks here; first extracting only files via not PSisContainer, grouping them via their extensions (in order to easily process them in a copy loop) and matching only a list of valid extensions (in this case xap and html).

$types = ".xap", ".html"

$files=  Get-ChildItem $folder | Where-Object {-not$_.PSisContainer} | Group-Object Extension 

$files = $files | Where-Object {$types-contains$_.Name}

New-Item -itemType Directory -path $deploymentFolder -ea SilentlyContinue

$files | ForEach-Object { $_.Group | Copy-Item -destination  $deploymentFolder }

Tip: use Some-Cmdlet | Format-Table or its variants to output details to screen to help with debugging.

Step 3 – Compress
This last step in the script is a compression action. I got the compression functions from David Aiken’s MSDN blog post. The Add-Zip function was unable to take a file path containing a directory-up (\..\), so the final script has Move-Item cmdlet line to compensate.

function Add-Zip
{
    param([string]$zipfilename)

    if(-not (test-path($zipfilename)))
    {
        set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
        (dir $zipfilename).IsReadOnly = $false    
    }
    
    $shellApplication = new-object -com shell.application
    $zipPackage = $shellApplication.NameSpace($zipfilename)
    
    foreach($file in $input) 
    { 
            $zipPackage.CopyHere($file.FullName)
            Start-sleep -milliseconds 500
    }
}

The complete script file can be found here on Gist.GitHub.

The next step in this script could be to email the newly created zip file, but that’s something for a future post.

Advertisements

A long time ago, in a framework far far away

I have been really busy lately, haven’t had much of a chance to work on some of the WCF post ideas I have sitting in my drafts folder. So I thought a quick simple run through of a little problem I had to resolve the other day might be an o.k. “filler post”.

Note: This is not a problem if you’re using WPF in .NET 3.0 onwards, combo boxes by default expand to the largest element. This problem was encountered on a .NET 2.0 forms control ComboBox.

DotNET Far Far Away

The problem was with long text inside the drop-down list for any given ComboBox in the application. The text would simply be truncated as the drop down panel would only be as wide as the ComboBox.Width. A quick web search uncovered an MSDN article “Building a better ComboBox” from Jan 2005. This article and its download-able code sample solves 95% of the problem. The code supplied is very simple, it has a function which determines the length of the longest text item, then adjusts the drop down panel accordingly. There were some complications/limitations so I spent some more time investigating and playing.

A note on the MSDN article sample code, it’s very straight forward, this specialised combo box extends on the standard combo box. The bulk of the code is to handle the situation where the combo box drop down goes off the edge of the screen.

public class BetterComboBox : ComboBox

This is what I needed to achieve with a 2.0 forms control, demonstrated by this image of the default behavior of a WPF ComboBox control:

WPF Combobox

WPF Combobox

Once the existing combo boxes were modified to be of the new BetterComboBox type, the first problem was with a breakpoint I had set to verify the execution of drop down resizing was never being reached.

This was simply an issue with the order in which events were occurring after a data binding. Upon the assignment of DataSource, the event would fire, but the ComboBox.Items collection was not yet populated. This is actually by design in the framework, that the Items collection is not populated/processed until the object is displayed. It was therefore as simple as refreshing with a call to RefreshList().

This is why the design of the BetterComboBox made use of the HandleCreated event. But my particular implementation required the adjustment occur upon the DataBinding event.

The search for this issue lead me to a few common problems that people are having, and the answer to this StackOverflow question was helpful.

Once the items were showing the next problem I encountered led me down the wrong investigation path my first instinct was to challenge the ability of the improved ComboBox to calculate the appropriate pixel length of my strings. I quickly found a Code Project post (by Pierre Arnaud) about the limitations of:

System.Drawing.Graphics.MeasureString()

There in fact is a limitation, but it wasn’t the cause of my problem, none-the-less an improved measurement function is a welcome addition, so I included it in the customisation.

Once the improved MeasureString() was implemented the actual problem became quickly apparent, that the ToString() method being called on each item was too long. This was due the varying types of elements being bound to the ComboBox data sources. Each class did not have an overload for the ToString() method, so it was the fully qualified class name: Namespace.Something.SomethingElse.ClassName, instead of the actual property that would be bound as the DisplayMemeber.

public override String ToString()
{ return _property; }

So with the ComboBox item’s collection populated, the to-string overload on a few classes, an improved string calculation method, a new appropriate width would successfully be calculated and applied, and the equivalent of an auto-resizing WPF ComboBox was achieved. Fun times in 2.0!