Note, that there is a new version of the script.

From time to time I need to select some files for operations like copying, reading content etc. I know that the files have some extension and their file name contains some word, but I'm not sure if my filters get all the files I need, because the rules what file should be selected might be quite complex.
And besides that I sometimes have some set of files that I need to select visually (~ look at each file and decide) and then process. Then it is very hard time for me.

Command like the following takes my time to create and can be quite error prone, because you know that there might be some old files that don't contain holiday nor summer in the name:

Get-ChildItem c:\temp -filter *.jpg -rec | 
   ? { $_.Name -match 'holiday|summer' -and $_.LastWriteTime -lt '2008-01-01' } 

That's why it would be much better to have a possibility to select files from a list by hand.

Solution

After you run Select-File a form appears with one button. After you select the files (there are checked), just press the button and checked items will be sent to the pipeline. Dead simple.

It can be used similarly as Get-ChildItem, but it is much more simple and it works only on file system.

Navigation

  • You can use keys to move up and down.
  • When holding Shift, more items are selected (but not checked).
  • To check the items press space.

Examples

Basic usage

[1]: Select-File M:\install\ | 
	? { $_.Length -lt 1kb } | 
	Copy-Item -Destination c:\temp\install

You can filter the items and search recursively:

[2]: Select-File M:\install\ -rec -filter *.exe | 
	copy-item -Destination C:\temp\install

You can sort the items:

[3]: Select-File M:\install\ -rec -sort LastWriteTime | 
	copy-item -Destination C:\temp\install

I think the parameters are quite self-explanatory so I won't explain them here.

Code

function Select-File {
    param(
        [Parameter(Position=0,Mandatory=$true)][string]$dir,
        [Parameter(Position=1,Mandatory=$false)][string]$filter,
        [Parameter()][switch]$recurse,
        [Parameter()][switch]$hideDir,
        [Parameter(Mandatory=$false)][string]$sortProperty='Name'
    )
    begin {
        Add-Type -Assembly System.Windows.Forms
        $global:SelectedItems = @()
        
        $items = @{}
        $form = new-object Windows.Forms.Form  
        $form.Text = "Pick the files"
        $form.Size = new-object Drawing.Size @(500,600)  

        $panel = new-object System.Windows.Forms.ListView
        $panel.Dock = 'Fill'
        $panel.CheckBoxes = $true
        $panel.View = 'Details'
        $panel.FullRowSelect = $true
        [void]$panel.Columns.Add('', -1, 'Left')
        [void]$panel.Columns.Add('Dir', $(if($hideDir){0}else{-1}), 'Left')
        [void]$panel.Columns.Add('Name', -1, 'Left')
        [void]$panel.Columns.Add('Size', -1, 'Right')
        [void]$panel.Columns.Add('Modified', -1, 'Left')
        [void]$panel.Columns.Add('FullPath', 0, 'Left')
        $form.Controls.Add($panel)
    
        $p = @{LiteralPath=$dir}
        if ($filter) { $p['filter'] = $filter }
        if ($recurse) { $p['recurse'] = $true }
        
        $dir = (Resolve-Path $dir).Path
        $dirE = [regex]::escape($dir)
        Get-ChildItem @p | 
            ? {!$_.PsIsContainer} | 
            Sort $sortProperty |
            % { 
                $items[$_.FullName] = $_
                $cb = New-Object Windows.Forms.ListViewItem
                $cb.Checked = $false
                [void]$cb.SubItems.Add(($_.FullName -replace "($dirE).*",'$1'))    #directory
                [void]$cb.SubItems.Add(($_.FullName -replace "$dirE\\?(.*)",'$1')) #file
                [void]$cb.SubItems.Add($_.Length)                                  #length
                [void]$cb.SubItems.Add($_.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss')) #modified
                [void]$cb.SubItems.Add($_.FullName)                                #helper full path
                [void]$panel.Items.Add($cb)
            }
    
        $ok = new-object System.Windows.Forms.Button 
        $ok.add_Click({  
            $global:SelectedItems = @($panel.Items | ? { $_.Checked })
            $form.close()
        })  
        $ok.BackColor = [System.Drawing.Color]::Green
        $ok.Text      = "Ok"
        $ok.Dock      = [Windows.Forms.DockStyle]::Bottom
        $form.Controls.Add($ok)
    
        $Form.Add_Shown({$form.Activate()})   
        [void]$form.showdialog()  
    }
    process {
        $SelectedItems | % { 
            $key = $_.SubItems[5].Text
            Write-Debug "Key: $key, Value: $($items[$key])"
            Write-Output $items[$key]
        }
    }
}

This is just first attempt how to solve my frustration. So, it is far from perfect solution. Just take it as inspiration for your further improvements ;)

Download

Meta: 2010-04-01, Pepa