6

I want to move a folder—containing many sub-folders and files—from one NTFS drive to another but I need the metadata date and timestamp attribute values preserved from the original source on the new destination drive.

The Problem: I've noticed when I complete various copy operations for the task, the timestamp values for the "creation date", "last modified date", etc. are not preserved on folders containing at least one folder/file. I've also noticed that empty folders that are moved will eventually and automatically change their creation date to conform with the rest as well as when place something in the empty folder on the new drive.

enter image description here

My Effort: I've tried nearly everything I could find via Google on this subject (Robocopy, Richcopy, Microsoft SyncToy, Total Commander, Free Commander...and many, many more...)—all of which produce relatively the same results. Nothing has produced a comprehensive, 100% preservation of the source being moved. Robocopy and Richcopy (along with the "Commanders") come close, but I still experience issues (in all cases) where the creation date is incorrectly "preserved", the last modified date cannot be preserved at all, etc. The only true promise I've seen beyond that...is Powershell.


My PowerShell Journey

I stumbled upon this link:

https://stackoverflow.com/questions/34951911/powershell-move-item-folder-time-stamp

...with this script:

function Move-FileWithTimestamp {
[cmdletbinding()]
param(
[Parameter(Mandatory=$true,Position=0)][string]$Path,
[Parameter(Mandatory=$true,Position=1)][string]$Destination
)

$origLastAccessTime = ( Get-Item $Path ).LastAccessTime
$fileName = ( Get-Item $Path ).Name
Move-Item -Path $Path -Destination $Destination
$(Get-Item ($Destination+'\'+$fileName)).LastAccessTime = 
$origLastAccessTime
}

The script in the thread above still didn't perform any differently than the programs I listed, but at least here, I had a platform where I could possibly change/customize/tweak some things for my exact needs. So I did what I could with my limited knowledge in this realm (i.e. changing ".LastAccessTime" with ".CreationTime", swapping ".LastAccessTime" with "LastWriteTime", etc.), and eventually got relatively close to preserving all timestamps (I believe at one point, I had preserved last modified, last accessed, and last saved on my test folder). However, I still cannot seem to preserve the creation date correctly, and what I was able to accomplish with everything else obviously only applied to the lone test folder (and nothing else, such as the sub-folders within it...but this is only because I don't know how to script those things beyond a main directory).

I'm in over my head when it comes to this stuff, so am wondering if anyone out there wants to tackle this.

UPDATE: here's where I'm at now:

function Move-FileWithTimestamp {
[cmdletbinding()]
param(
[Parameter(Mandatory=$true,Position=0)][string]$Path,
[Parameter(Mandatory=$true,Position=1)][string]$Destination
)
$origCreationTime = ( Get-Item $Path ).CreationTime
$origLastWriteTime = ( Get-Item $Path ).LastWriteTime
$origLastAccessTime = ( Get-Item $Path ).CreationTime

$fileName = ( Get-Item $Path ).Name
Move-Item -Path $Path -Destination $Destination
$(Get-Item ($Destination+'\'+$fileName)).CreationTime = $origCreationTime
$(Get-Item ($Destination+'\'+$fileName)).LastWriteTime = 
$origLastWriteTime
$(Get-Item ($Destination+'\'+$fileName)).LastAccessTime = 
$origLastAccessTime
}

This appears to maintain the original creation time for the main folder being moved (as well as the last modified/write time), but obviously the last accessed time changes to the original creation time in the process (it seems as though when a folder is moved to a new drive, Windows, by default, changes the last accessed time during that process, and ALSO refers to this new last accessed time to create the new creation time of the folder in its new location (it DOESN'T refer to the original creation time whatsoever apparantly). I.E. if you try and set the new creation time to equal the original creation time, nothing will result, because the new last accessed time will by default automatically change the new creation time to equal it. So, if you force Windows to make the new last accessed time equal to the original creation time, then you ultimately wind up with the correct creation time, but incorrect last accessed time.

So, now I am stuck with an incorrect last accessed time, but correct times for everything else. Also, I have no idea how I am going to get this to apply to all subfolders as well, so let me know if anyone knows how to do that.

UPDATE:

function Move-FileWithTimestamp {
[cmdletbinding()]
param(
[Parameter(Mandatory=$true,Position=0)][string]$Path,
[Parameter(Mandatory=$true,Position=1)][string]$Destination
)
$origCreationTime = ( Get-Item $Path ).CreationTime
$origLastWriteTime = ( Get-Item $Path ).LastWriteTime
$origLastAccessTime = ( Get-Item $Path ).CreationTime
$origChildCreationTime = ( Get-ChildItem $Path ).CreationTime
$origChildLastWriteTime = ( Get-ChildItem $Path ).LastWriteTime
$origChildLastAccessTime = ( Get-ChildItem $Path ).CreationTime


$fileName = ( Get-Item $Path ).Name
Move-Item -Path $Path -Destination $Destination
$(Get-Item ($Destination+'\'+$fileName)).CreationTime = $origCreationTime
$(Get-Item ($Destination+'\'+$fileName)).LastWriteTime = $origLastWriteTime
$(Get-Item ($Destination+'\'+$fileName)).LastAccessTime = 
$origLastAccessTime
$(Get-ChildItem ($Destination+'\'+$fileName)) | ForEach-Object { 
$_.CreationTime = $origChildCreationTime }
$(Get-ChildItem ($Destination+'\'+$fileName)) | ForEach-Object { 
$_.LastWriteTime = $origChildLastWriteTime }
$(Get-ChildItem ($Destination+'\'+$fileName)) | ForEach-Object { 
$_.LastAccessTime = $origChildLastAccessTime }
}

Now, I have a main folder and one if it's sub-folders with correct creation and last modified dates (but not last accessed). I have no idea how to accomplish this for the rest of the sub-folders in the main folder, and also for any sub-folders within those sub-folders.

2
  • Are you telling me Robocopy /copyall doesn’t work? Oct 12, 2018 at 3:12
  • 1
    Copied to comment for OP: To answer your question, no, robocopy does not preserve the 3 major timestamps for me on folders (created, last modified, and last accessed). I've already attempted all switches. I definitely think there is a way to accomplish this with Powershell however...I am just far too unfamiliar with coding and scripting...which is why I'm hoping someone out there can take one look and be like, "oh, that's simple".
    – fixer1234
    Oct 12, 2018 at 3:52

2 Answers 2

6

Move directory tree and retain all timestamp attribute values

So your goal is to ensure that files and folders that are moved from a source location over to a destination location have their LastWriteTime, LastAccessTime, and CreationTime attribute values retained from the source location where those originated.

Essentially this. . .

  • Uses Copy-Item rather than Move-Item
  • Loops through source and sets timestamp attribute variables values to then use Set-ItemProperty to set those same values to those properties in the destination for all folders and files recursively
  • Explicitly does the same Set-ItemProperty timestamp attribute value set loop for folder objects only
  • Uses Remove-Item to then delete the original source file objects only cleaning those up
  • Uses Remove-Item to then delete the original source folder object only cleaning those up

Script

$src = "C:\Src\Folder\123\"
$dest = "X:\Dest\Folder\321\"
$src = $src.Replace("\","\\")

$i = Get-ChildItem -Path $src -Recurse
$i | % {     ## -- All files and folders

    $apath = $_.FullName -Replace $src,""
    $cpath = $dest + $apath

    Copy-Item -Path $_.FullName -Destination $cpath -Force

    If (Test-Path $cpath)
       {
           Set-ItemProperty -Path $cpath -Name CreationTime -Value $_.CreationTime
           Set-ItemProperty -Path $cpath -Name LastWriteTime -Value $_.LastWriteTime
           Set-ItemProperty -Path $cpath -Name LastAccessTime -Value $_.LastAccessTime
       }
    }

$d = Get-ChildItem -Path $src -Recurse -Directory
$d | % {     ## -- Folders only

    $apath = $_.FullName -Replace $src,""
    $cpath = $dest + $apath

    If (Test-Path $cpath)
       {
           Set-ItemProperty -Path $cpath -Name CreationTime -Value $_.CreationTime
           Set-ItemProperty -Path $cpath -Name LastWriteTime -Value $_.LastWriteTime
           Set-ItemProperty -Path $cpath -Name LastAccessTime -Value $_.LastAccessTime
       }
    }


$f = Get-ChildItem -Path $src -Recurse -File
$f | % {     ## -- Delete files only

    $apath = $_.FullName -Replace $src,"" 
    $cpath = $dest + $apath

    If (Test-Path $cpath)
       {
            Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
       }
    }


$d | % {     ## -- Delete directories only

    $apath = $_ -Replace $src,"" 
    $cpath = $dest + $apath

    If (Test-Path $cpath)
       {
            Remove-Item $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
       }
    }

Further Resources

3
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – DavidPostill
    Oct 13, 2018 at 19:03
  • @DavidPostill Can you provide me a link to read over how I can do this? I always get long comments and never get a link to make a chat so curious if there was another way with some reading I can learn how to move these over to a chat myself so mods don't get flagged and such. Oct 13, 2018 at 19:08
  • 1
    AFAIK only mods can do this.
    – DavidPostill
    Oct 13, 2018 at 19:17
1

With NTFS, you can only move files and folders on a single disk. Moving a directory on a single disk is an operation on the file pointers and not the data locations on the disk.

NTFS move (single disk)

You can simply call the .NET Move function from Powershell.
1. Create a file move.ps1

2.Copy in the following and change the paths as needed.

$Source = "C:\test"
$Destination = "C:\test1"
[System.IO.Directory]::Move($Source, $Destination)
  1. Right Click on the file and select Run in Powershell.

If you are "moving" from one disk to another, you need to perform an NTFS copy and then delete the source. The default behavior of NTFS is to update the folder dates because technically, they are newly created folders (NTFS pointers) on the new disk. To override this behavior you must set the dates on the new copies of the files and folders as posted by others. Here is yet another way to do the same thing. Here I call C# code from Powershell.

NTFS "move" (multiple disks) - Recursively copy source to destination, copy attributes, then delete source.

$source = @"
using System;
using System.IO;

public class DirectoryCopyExample
{
    public static void DirectoryCopy(string sourceDirName, string destDirName)
    {
        // Get the subdirectories for the specified directory.
        DirectoryInfo dir = new DirectoryInfo(sourceDirName);

        if (!dir.Exists)
        {
            throw new DirectoryNotFoundException(
                "Source directory does not exist or could not be found: "
                + sourceDirName);
        }

        // Since we are deleting the files in the source directory, we need to save the dates before they are modified
        DateTime dirCreationTime = Directory.GetCreationTime(sourceDirName);
        DateTime dirLastAccessTime = Directory.GetLastAccessTime(sourceDirName);
        DateTime dirLastWriteTime = Directory.GetLastWriteTime(sourceDirName);

        DirectoryInfo[] dirs = dir.GetDirectories();
        // If the destination directory doesn't exist, create it.
        if (!Directory.Exists(destDirName))
        {
            Directory.CreateDirectory(destDirName);        
        }

        // Get the files in the directory and copy them to the new location.
        FileInfo[] files = dir.GetFiles();
        foreach (FileInfo file in files)
        {
            string temppath = Path.Combine(destDirName, file.Name);

            file.CopyTo(temppath, false);

            File.SetCreationTime(temppath, File.GetCreationTime(file.FullName));
            File.SetLastAccessTime(temppath, File.GetLastAccessTime(file.FullName));
            File.SetLastWriteTime(temppath, File.GetLastWriteTime(file.FullName));

            File.Delete(file.FullName);
        }

        // Recursively copy all sub directories
        foreach (DirectoryInfo subdir in dirs)
        {
            string temppath = Path.Combine(destDirName, subdir.Name);
            DirectoryCopy(subdir.FullName, temppath);
        }

        Directory.SetCreationTime(destDirName, dirCreationTime);
        Directory.SetLastAccessTime(destDirName, dirLastAccessTime);
        Directory.SetLastWriteTime(destDirName, dirLastWriteTime);

        Directory.Delete(sourceDirName);

    }
}
"@

Add-Type -TypeDefinition $source

[DirectoryCopyExample]::DirectoryCopy("C:\test", "D:\test")

Code borrowed from here:

https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories

and here

https://stackoverflow.com/questions/2143460/how-to-convert-c-sharp-code-to-a-powershell-script

Disclaimer: This has been minimally tested. Use at your own risk.

2

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .