Build a Better Copy-Item Cmdlet


Summary: Sean Kearney investigates how to navigate file system content.

Honorary Scripting Guy, Sean Kearney, is here with an early holiday gift—a way to deal with copying many files and getting some kind of progress on the update.

I ran into this problem when I was duplicating file structures onto USB keys for Windows to Go a few months ago. Maybe “problem” is not the correct word. When I execute Copy-Item to duplicate a file structure, it works properly. There is, of course, some “finicky” issues.

As one reader correctly pointed out the other week, one issue arises if the folder name already exists when you copy. It creates a new folder under the parent instead of merging.

Another issue (and for me personally, the more irritating one) is when I copy a large pile of files. There is no status on exactly where it is.

When I copy a Nano Server structure, this is seconds, so I may not care. However, if you’re copying a larger structure, and in the midst is, oh I don’t know, a 3 gigabyte WIM file for Windows to Go, that might be annoying. It’s always good to know if you need to grab a coffee.

Why not use Robocopy? Nothing wrong with it. I personally like seeing how I can do it with Windows PowerShell in the event that Robocopy.exe falls into the category of Netsh and becomes deprecated.

And I’m a bit of a nerd…so, “Just cuz.”

Let’s imagine we’re going to duplicate a massive structure. The current PowerShell process is:

Copy-Item C:\Foo D:\Foo –recurse –force

It all works. It “runs away for a bit” and then returns when it’s done.

Our other option is to build a little script (or maybe an advanced function) to play with Get-Childitem and Copy-Item. That could work!

First I’ll define a command that includes two key parameters, Source and Destination:

Function Copy-WithProgress

{

 [CmdletBinding()]

 Param

 (

  [Parameter(Mandatory=$true,

     ValueFromPipelineByPropertyName=$true,

     Position=0)]

  $Source,

  [Parameter(Mandatory=$true,

     ValueFromPipelineByPropertyName=$true,

     Position=0)]

  $Destination

 )

I will start by ensuring that the $Source folder is in all lowercase letters. This will come in handy later when I use it to replace itself. (Yes, you heard that correctly.)

$Source=$Source.tolower()

Now I get the entire folder structure and store it as an object:

$Filelist=Get-Childitem $Source –Recurse

Now that I have this as a full object, I can get useful properties to use with the Write-Progress cmdlet. I can get the count of all the objects that would be copied with this command:

$Total=$Filelist.count

I establish a counter so that as I copy each file, I can determine how far I am from the end:

$Position=0

Stepping through the list of files is quite simple in PowerShell by using a For loop:

 foreach ($File in $Filelist)

 {

On each file, I’m going to need only the part that does not include the original source folder. I can pull this off with a simple Replace method on the content:

  $Filename=$File.Fullname.tolower().replace($Source,'')

Now I rebuild the path for the destination:

  $DestinationFile=($Destination+$Filename)

I can now do something more useful—show what I’m doing and how far I am!

  Write-Progress -Activity "Copying data from $source to $Destination" -Status "Copying File $Filename" -PercentComplete (($Position/$total)*100)

  Copy-Item $File.FullName -Destination $DestinationFile

I can bump up the counter:

  $Position++

 }

}

And voila!

If you copy a big folder in this manner, you’ll get something like this:

Copy-WithProgress –source C:\NanoServer –Destination C:\NanoNew

Image of command output

Of course, this command is far from complete. This is a direct “assume you’re going to copy it all” approach. We could improve with some better error trapping and perhaps a way to pass more data for Get-Childltem to filter on.

But this was fun to play with on the weekend. Save yourself some typing. This command is sitting in its current state in the DeployImage module that I discussed earlier in the month. You can find it in the PowerShell Gallery, GitHub, and even on the TechNet Script Repository. For more details, see the series Use DeployImage Module and PowerShell to Build a Nano Server.

We’ll see you all tomorrow with our annual Scripting Guys Holiday Special: The Five Days of PowerShell 5!

I invite you to follow the Scripting Guys on Twitter and Facebook. If you have any questions, send email to them at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, always remember that with great PowerShell comes great responsibility.

Sean Kearney, Honorary Scripting Guy, Cloud and Datacenter Management MVP 

Comments (1)

  1. Excellent and straight forward code.

    I have a script that copies ~5GB of files across the network, so I was very glad to find this as you can imagine waiting with no indication of progress for thousands of files totaling multiple gigabytes to copy with Copy-Item was painful. 🙂

Skip to main content