Correcting creation and last modified folder attributes using PowerShell

PowerShell logoThe first snow of the season came, over the weekend, so it seemed a good time to write a post that was a little off-piste. 🙂

It's a topic that I don't recall ever having broached: using PowerShell to mess with files and folders on your hard drive or a network server. This is my first serious use of PowerShell, so please use the code in this post with caution: I really can't promise it won't do bad things to your system.

A bit of background: a quick way to copy files around on Windows systems is using Explorer. This is nice and easy, but when it copies folders the new folders have their creation date/time set based on when they were created (i.e. copied). If you're copying data that has been around for any serious amount of time, this probably isn't what you want.

One solution is to use a better copying tool which maintains the folder attributes, of course (Robocopy being one solid alternative). But what about all those folders that were already copied and you no longer have the access to the originals?

Here's what I wanted to do: loop through the folders in question, and set the creation date/time of each one to be the creation date/time of the oldest contained item, and the "last modified" date/time as the last modified date/time of the most recent. This won't be the attribute vales you had before – those are gone, at this stage – but they'll be a lot ore reflective of the folder's contents and sorting and searching the contents much easier.

I'd think this would be a common enough requirement… I looked around for a tool that does it, but couldn't find one. Time to write my own!

I did a couple of iterations on this… here's the first I put together, which uses the standard -Recurse flag to handle nested folder structures:

$dir = "c:\temp" # Starting folder

Get-ChildItem -Path $dir -Recurse | # Call recursively      

where {$_.PSIsContainer} | # Only folders

foreach {

  $item = $_.FullName # Get the full name of the item

  $kids = Get-ChildItem -Path $item # Get its children and the oldest/latest

  $oldest = $kids | Sort-Object CreationTime | Select-Object -First 1

  $latest = $kids | Sort-Object LastWriteTime -Descending | Select-Object -First 1

 

  # Try to set the creation and last modified times

  # (these may throw exceptions if in use by another process, e.g. Explorer)

 

  try {

    if (($oldest -ne $null) -and ($oldest.CreationTime -ne $null)){

      $_.CreationTime = $oldest.CreationTime

    }

    if (($latest -ne $null) -and ($latest.LastWriteTime -ne $null)) {

      $_.LastWriteTime = $latest.LastWriteTime

    }

 

    Write-Host -NoNewline "Processed "

    Write-Host $item

  }

  catch {

    Write-Host -NoNewline "Item not accessible: "

    Write-Host $item

  }

}

 

The problem with this first version is that it runs breadth-first, and you therefore might need to run it multiple times for the right date/time values to bubble their way up to the surface of your file system. So I went back to the drawing board and came up with a recursive, depth-first implementation. This should only need one pass to work properly, all being well:

function RecursiveUpdateAttributes($root) {

 

  # Get the full name of the item

 

  $item = $root.FullName

 

  # Get its top-level children

 

  $kids = Get-ChildItem -Path $item

 

  # Recursively process the child folders

 

  foreach($kid in $kids) {

    if (($kid -ne $null) -and ($kid.PSIsContainer)) {

      RecursiveUpdateAttributes $kid

    }

  }

 

  # Determine the oldest and youngest children

 

  $oldest = $kids | Sort-Object CreationTime | Select-Object -First 1

  $latest = $kids | Sort-Object LastWriteTime -Descending | Select-Object -First 1

 

  try {

 

    # Set the attributes

 

    if (($oldest -ne $null) -and

        ($oldest.CreationTime -ne $null) -and

        ($root.CreationTime -ne $null)) {

      $root.CreationTime = $oldest.CreationTime

    }

 

    if (($latest -ne $null) -and

        ($latest.LastWriteTime -ne $null) -and

        ($root.LastWriteTime -ne $null)) {

      $root.LastWriteTime = $latest.LastWriteTime

    }

 

    Write-Host -NoNewline "Processed "

    Write-Host $item

  }

  catch {

    Write-Host -NoNewline "Item not accessible: "

    Write-Host $item

  }

}

 

$dir = "c:\temp" # Starting folder

RecursiveUpdateAttributes(Get-Item -Path $dir);

 

If you have an error suggesting scripts can't be run on your system, you might try launching "PowerShell.exe -ExecutionPolicy Bypass" and calling the script from there.

If you're familiar with the PowerShell environment and have suggestions on how to improve the script, please post a comment!

5 responses to “Correcting creation and last modified folder attributes using PowerShell”

  1. Pierre de la Verre Avatar
    Pierre de la Verre

    Hi Kean

    besides the PowerShell code: Have you tried "FolderTimeUpdate"?
    nirsoft.net/util...

    1. Hi Pierre,

      Of course such a tool had to exist - it seems a very obvious requirement - it's just a shame I didn't find it during my various searches.

      On the other hand I'm happy to have found an interesting problem I could use as an excuse to learn a little PowerShell. And I'm sure the code will be useful to someone wanting to take it further.

      Regards,

      Kean

      1. Pierre de la Verre Avatar
        Pierre de la Verre

        > Of course such a tool had to exist
        Nirsoft is the "treasure chest of 42" - of all and everything ....

        > And I'm sure the code will be useful to someone wanting to take it further.
        Yes, I'm sure. I have to start with PowerShell too - sometimes 😉

  2. Hi Kean,

    Possible it can be interesting for you: PowerShell can be hosted inside of AutoCAD. More info is here: https://www.theswamp.org/in...

    1. Hi Andrey,

      I did see this, earlier in the week. Interesting stuff.

      I wonder whether it's possible to use it with the "dynamic .NET" capabilities off the ObjectId class... that would simplify the code significantly and only incur a modest performance overhead that's probably completely acceptable in a scripting environment.

      Kean

Leave a Reply to Pierre de la Verre Cancel reply

Your email address will not be published. Required fields are marked *