The 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!