Thursday, August 7, 2014

Applying old properties to new files in Windows

A while back, I used some kind of "cp -r" command in Linux to copy files from an old IDE hard drive to a newer SATA drive.  The IDE drive is 10 years old, and thus many of the files on there are of that vintage.  Unfortunately, I was not aware that cp does not properly transfer file attributes between Windows files (as these are NTFS partitions).  I could have simply used Windows to copy the files, but I didn't want this to happen:



Hence why the choice to use Linux.  Nevertheless, being somewhat of an archivist at heart, I was interested in retaining some of these original file properties, namely Created, Modified, and Accessed times.  I didn't want all my files looking like they came from 12/2/2013 when some of them are as old as 2002.  So, I went back into Windows to solve my problem.

The idea for this was derived from one of many related StackOverflow posts on the topic, plus some additional information was acquired in order to walk the directory trees recursively.  I have pasted the code below.  One issue I haven't been able to work through is that directory attributes are not copied properly, claiming you do not have enough permissions to modify the file.  I even tried running VS 2013 as Administrator, but it didn't help.  So, I solved this problem by taking any errors you encounter along the way and writing them to a CSV file which you can sort & search later on.

As for the IDE hard drive, it's been zeroed out and will be donated along with other old drives targeted in my downsizing plan.

I'm sure it could be much better-optimized for speed & multitasking, but nevertheless, the code below is for Visual C# 2013:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public long filesCount;
        public StreamWriter writer;

        public Form1()
        {
            InitializeComponent();
            filesCount = 0;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // Recursively go through all the files on the source drive/folder
            
            //File.SetCreationTime(tgtFile, File.GetCreationTime(srcFile));
            //File.SetLastAccessTime(tgtFile, File.GetLastAccessTime(srcFile));
            //File.SetLastWriteTime(tgtFile, File.GetLastWriteTime(srcFile));
            string driveLetter = "E";
            writer = new StreamWriter("c:\\Notes-On-Migration-Of-" + driveLetter + ".csv");
            DirectoryInfo dirSrc = new DirectoryInfo(driveLetter + ":\\");
            WalkDirectoryTree(dirSrc, "D:\\Win7\\");
            writer.Close();
            lblStatus.Text = "Number of files: " + filesCount;
        }

        private void WalkDirectoryTree(System.IO.DirectoryInfo root, string dirTgt)
        {
            System.IO.FileInfo[] files = null;
            System.IO.DirectoryInfo[] subDirs = null;

            DateTime ss = File.GetCreationTime(root.FullName);
            DateTime ee = File.GetLastAccessTime(root.FullName);
            DateTime xx = File.GetLastWriteTime(root.FullName);
            // First, process all the files directly under this folder 
            try
            {
                files = root.GetFiles("*");
                filesCount += files.LongLength;
                lblStatus.Text = "On " + root.FullName + "; " + filesCount + " files done";
                Application.DoEvents();
            }
            // This is thrown if even one of the files requires permissions greater 
            // than the application provides. 
            catch (Exception e)
            {
                // This code just writes out the message and continues to recurse. 
                // You may decide to do something different here. For example, you 
                // can try to elevate your privileges and access the file again.
                //writer.WriteLine(e.Message);
                textBox1.Text = textBox1.Text += e.Message + "\r\n";
            }

            if (files != null)
            {
                /*
                foreach (System.IO.FileInfo fi in files)
                {
                    // In this example, we only access the existing FileInfo object. If we 
                    // want to open, delete or modify the file, then 
                    // a try-catch block is required here to handle the case 
                    // where the file has been deleted since the call to TraverseTree().
                    try
                    {
                        Console.WriteLine(fi.FullName);
                        File.SetCreationTime(dirTgt + fi.Name, File.GetCreationTime(fi.FullName));
                        File.SetLastAccessTime(dirTgt + fi.Name, File.GetLastAccessTime(fi.FullName));
                        File.SetLastWriteTime(dirTgt + fi.Name, File.GetLastWriteTime(fi.FullName));
                    }
                    catch (Exception e)
                    {
                        writer.WriteLine(e.Message);
                        textBox1.Text = textBox1.Text += e.Message + "\r\n";
                    }
                }
                 * */

                // Now find all the subdirectories under this directory.
                try
                {
                    subDirs = root.GetDirectories();
                }
                catch (Exception e)
                {
                    writer.WriteLine("Corrupt directory," + root.FullName);
                    textBox1.Text = textBox1.Text += e.Message + "\r\n";
                }

                foreach (System.IO.DirectoryInfo dirInfo in subDirs)
                {
                    // Write the directory attributes too.
                    try
                    {
                        //MessageBox.Show("Target: " + dirTgt + dirInfo.Name + "\r\nSource: " + dirInfo.FullName);
                        File.SetCreationTime(dirTgt + dirInfo.Name, File.GetCreationTime(dirInfo.FullName));
                        File.SetLastAccessTime(dirTgt + dirInfo.Name, File.GetLastAccessTime(dirInfo.FullName));
                        File.SetLastWriteTime(dirTgt + dirInfo.Name, File.GetLastWriteTime(dirInfo.FullName));
                    }
                    catch (System.UnauthorizedAccessException e)
                    {
                        // Write to the CSV file the "Access Denied" tag, what we were trying to modify, and its original properties from the source hard drive
                        writer.WriteLine("Access denied," + dirTgt + dirInfo.Name + "," + File.GetCreationTime(dirInfo.FullName) + "," + File.GetLastAccessTime(dirInfo.FullName) + "," + File.GetLastWriteTime(dirInfo.FullName));
                        textBox1.Text = textBox1.Text += e.Message + "\r\n";
                    }
                    catch (System.IO.FileNotFoundException e)
                    {
                        // Attempt to copy the file from the source to the destination
                        try
                        {
                            File.Copy(dirInfo.FullName, dirTgt + dirInfo.Name, false);
                            string ifei = "kjbhwe";
                        }
                        catch (Exception e2)
                        {
                            // This happens when we don't have enough permission to copy the file for some reason
                            writer.WriteLine("File copy denied," + e.Message);
                            textBox1.Text = textBox1.Text += e.Message + "\r\n";
                        }
                    }
                    catch (Exception e)
                    {
                        // We don't really know what happened, so just write it down
                        writer.WriteLine(e.Message);
                        textBox1.Text = textBox1.Text += e.Message + "\r\n";
                    }
                    // Recursive call for each subdirectory.
                    WalkDirectoryTree(dirInfo, dirTgt + dirInfo.Name + "\\");
                }
            }
        }
    }
}