[bgreco.net]: PS C:\> Copy-ItemGuiThis function can be used to show a Windows Explorer progress dialog to display progress while copying large files and folders. Requires PowerShell 2.0 or higher.
Usage: mostly the same as Copy-Item. Supports copying files and directories, giving them a new name if desired.
Supports the standard cmdlet parameters -WhatIf, -Confirm, and -Verbose.
Limitations: It is up to Windows Explorer to determine whether a file copy will take long enough to display a progress dialog. For small files, no dialog will be shown. If multiple large files are passed as the source, a separate progress dialog will be shown for each file.
Disclaimer: As with Copy-Item, careless use can result in loss of data. This function comes with absolutely no warranty.
function Copy-ItemGui {
[CmdletBinding(SupportsShouldProcess=$true)] Param (
[Parameter(Position=0, Mandatory=$true)] [ValidateNotNullOrEmpty()] [String[]] $Path,
[Parameter(Position=1, Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $Destination
)
$items = @(Get-Item $Path)
if($items -eq $null) {
throw 'Source file or directory not found'
}
$dst_is_dir = $false
# Copying to an existing directory or file
if(Test-Path $Destination) {
$dst = Get-Item $Destination
if($dst.PSIsContainer) {
$dst_dir = $dst.FullName
$dst_is_dir = $true
} else {
# If copying and overwriting an existing file, only one source file is allowed
if($items.Count -gt 1) {
throw 'Multiple source files are not allowed when a file name is given as the destination.'
}
$dst_dir = $dst.DirectoryName
$dst_name = $dst.Name
}
# Copying to a new file name in an existing directory
} else {
if($Destination.EndsWith('/') -or $Destination.EndsWith('\')) {
throw "Destination directory '$Destination' does not exist."
}
# If copying to an existing directory and giving a destination file name, only one source file is allowed.
if($items.Count -gt 1) {
throw 'Multiple source files are not allowed when a file name is given as the destination.'
}
# Get directory name. If there is none, file is being copied to the current directory.
$dst_dir = [System.IO.Path]::GetDirectoryName($Destination)
if($dst_dir -eq '') {
$dst_dir = '.'
}
$dst_name = [System.IO.Path]::GetFileName($Destination)
if(-not ((Test-Path $dst_dir) -and (Get-Item $dst_dir).PSIsContainer)) {
throw "Destination directory '$dst_dir' does not exist."
}
}
$dst_dir = (Get-Item $dst_dir).FullName
$shell = New-Object -ComObject "Shell.Application"
$shell_dst = $shell.NameSpace($dst_dir)
foreach($item in $items) {
if($item.PSIsContainer) {
$type = 'Directory'
$parent_dir = $item.Parent.FullName
} else {
$type = 'File'
$parent_dir = $item.DirectoryName
}
if($dst_is_dir) {
$dst_name = $item.Name
}
# Copying an item to its own directory without giving a new name is not allowed
if($parent_dir -eq $dst_dir -and $dst_name -eq $Item.Name) {
Write-Error "Cannot copy '$($item.FullName)': Source and destination are the same"
continue
}
if($pscmdlet.ShouldProcess("${type}: $item Destination: $dst_dir\$dst_name", "Copy $type")) {
# If a file exists in the destination directory with the same name as the source file,
# the shell copy will overwrite it. Fake a copy-rename operation by creating a temporary
# directory, copy the file there, and then move it.
if((Test-Path "$dst_dir\$($Item.Name)") -and $dst_name -ne $Item.Name) {
do {
$tmp_dir = "$dst_dir\" + [System.IO.Path]::GetRandomFileName()
} while (Test-Path $tmp_dir)
New-Item -ItemType Directory $tmp_dir | Out-Null
$shell_tmp = $shell.NameSpace($tmp_dir)
$shell_tmp.CopyHere($item.Fullname, 0x10)
Move-Item -Force "$tmp_dir\$($Item.Name)" "$dst_dir\$dst_name"
Remove-Item $tmp_dir
} else {
$shell_dst.CopyHere($item.Fullname, 0x10)
if($item.Name -ne $dst_name) {
Move-Item -Force "$dst_dir\$($Item.Name)" "$dst_dir\$dst_name"
}
}
}
}
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell) | Out-Null
}