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