[bgreco.net]: PS C:\> Copy-ItemGui

This 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"

		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