File Aging With PowerShell

A very common solution that screams for an automated solution is to get a file aging report:  Given a specific folder, how many files are there and how old are they?  Here is one possible solution that uses PowerShell. It doesn’t require WMI or the VBScript FileSystemObject, only native PowerShell. The script only requires that you specify a folder name or path to check. This can be a local folder or a UNC. When finished you get a simple, but colorized, report.

I’ve attached the script for your convenience because sometimes copying and pasting from a web page doesn’t provide the proper formatting. Rename the file with a .ps1 extension to run.  It is not signed so you’ll have to test it in a non-production environment where code signing is set to unrestricted or sign it with your certificate before running.

Let me show you the script first and then I’ll go over a few elements.

# FileAging.ps1
# This script developed and tested with PowerShell RTM

Param($dir)
if ($dir -isnot [string]) {
Write-Host “ERROR: You must specify a directory path!” `
-foregroundcolor “RED” -backgroundcolor “Black”;`
return
}

#verify $Dir exists as a path
if (Test-Path $Dir) {

    $now=Get-Date
    #only get file objects, not folder objects
    $files=Get-ChildItem -path $dir -recurse | `
    where {($_.GetType()).name -eq “FileInfo”}
    clear-host
   
    #initialize
    $Total2yr=0
    $Total90=0
    $Total1yr=0
    $Total30=0
    $Total7=0
    $TotalCurrent=0
    $2yrs=0
    $1yr=0
    $3mo=0
    $1mo=0
    $1wk=0
    $current=0
    $count=0
   
    #enumerate files and get information
    foreach ($file in $files) {
        $age=($now.subtract(($file.LastWriteTime))).days
        $count=$count+1
        Write-Progress -Activity “File Aging Report” `
        -status $file.DirectoryName -currentoperation $file.name
        switch ($age) {
         {$age -ge 730} {$2yrs=$2yrs+1;$Total2yr=$Total2Yr+$file.length;`
         break}
         {$age -ge 365} {$1yr=$1yr+1;$Total1yr=$Total1Yr+$file.length;`
         break}
         {$age -ge 90} {$3Mo=$3Mo+1;$Total90=$Total90+$file.length;`
         break}
         {$age -ge 30} {$1Mo=$1Mo+1;$Total30=$Total30+$file.length;`
         break}
         {$age -ge 7} {$1wk=$1wk+1;$Total7=$Total7+$file.length;break}
         {$age -lt 7}  {$current=$current+1;$TotalCurrent=`
         $TotalCurrent+$file.Length;break}
         }
    }

    $GrandTotal=$Total2yr+$Total1yr+$Total90+$Total30+$Total7+$TotalCurrent
   
    #format file size totals to MB
    $GrandTotal=”{0:N2}” -f ($GrandTotal/1048576)
    $Total2yr=”{0:N2}” -f ($Total2yr/1048576)
    $Total90=”{0:N2}” -f ($Total90/1048576)
    $Total1yr=”{0:N2}” -f ($Total1yr/1048576)
    $Total30=”{0:N2}” -f ($Total30/1048576)
    $Total7=”{0:N2}” -f ($Total7/1048576)
    $TotalCurrent=”{0:N2}” -f ($TotalCurrent/1048576)
   
    clear-host
    Write-Host “File Aging for” $dir.ToUpper()
    Write-Host “2 years:” $2yrs “files” $Total2yr “MB” -foregroundcolor “Red”
    Write-Host “1 year:” $1yr “files” $Total1yr “MB” -foregroundcolor “Magenta”
    Write-Host “3 months:” $3mo “files” $Total90 “MB” -foregroundcolor “Yellow”
    Write-Host “1 month:” $1mo “files” $Total30 “MB” -foregroundcolor “Cyan”
    Write-Host “1 week:” $1wk “files” $Total7 “MB” -foregroundcolor “Green”
    Write-Host “Current:” $current “files” $TotalCurrent “MB”
    Write-Host `n
    Write-Host “Totals:” $count “files” $GrandTotal “MB”
    Write-Host `n
  }
  else {
  Write-Host “Failed to find” $Dir.ToUpper() -foregroundcolor “Red” `
  -backgroundcolor “Black”
  }

The script starts expecting a run time parameter that will be called $dir. If you forget to specify something, I’ve added and If statement to see if you entered a string.  If not, the script produces a simple error message. I could have used Throw to generate an error if $dir was not defined, but PowerShell includes detailed error information that personally I found annoying and unnecessary, at least for my purposes.

Param($dir)
if ($dir -isnot [string]) {
Write-Host “ERROR: You must specify a directory path!” `
-foregroundcolor “RED” -backgroundcolor “Black”;`
return
}

If $dir is present, I still want to make sure you entered a path, so I call Test-Path to validate the parameter. If it is a path, then the main body of the script is run.

if (Test-Path $Dir)

Otherwise an error message is displayed and the script ends:

else {
  Write-Host “Failed to find” $Dir.ToUpper() -foregroundcolor “Red” `
  -backgroundcolor “Black”
  }

Since this is an aging report, I want to calculate how much time has elapsed from right now so I create a variable, $now, that I can use later in my calculations. Next I call Get-ChildItem with recurse to get every file.  I use a Where expression to skip any child items that are folders.

$files=Get-ChildItem -path $dir -recurse | `
 where {($_.GetType()).name -eq “FileInfo”}

Once I have my collection of file information, I can process it and do all of my calculations.  This is done with the ForEach construct. I calculate the age of each file by calling the Subtract method of $Now, subtracting it from the LastWriteTime property of each file. I’m setting this in days.  I also keep count of the number of files processed.

        $age=($now.subtract(($file.LastWriteTime))).days
$count=$count+1

In order to provide some feedback, I’m using Write-Progress to report on, well, progress. The cmdlet requires an Activity and Status parameters. Think of them as title bars. The status is updated to reflect the current directory and the CurrentOperation property is set to display the current file name.

Write-Progress -Activity “File Aging Report” `
-status $file.DirectoryName -currentoperation `
$file.name

The meat of the script is the Switch statement. Instead of checking if $age is a specific number, I can use a calculated expression. So, if the $age is >= 730 days then I update my counters that are keeping track of the number of files that meet this criteria as well as a running total of the total size.

switch ($age) {
         {$age -ge 730} {$2yrs=$2yrs+1;$Total2yr=$Total2Yr+$file.length;`
         break}
         {$age -ge 365} {$1yr=$1yr+1;$Total1yr=$Total1Yr+$file.length;`
         break}
         {$age -ge 90} {$3mo=$3Mo+1;$Total90=$Total90+$file.length;`
         break}
         {$age -ge 30} {$1Mo=$1Mo+1;$Total30=$Total30+$file.length;`
         break}
         {$age -ge 7} {$1wk=$1wk+1;$Total7=$Total7+$file.length;break}
         {$age -lt 7}  {$current=$current+1;$TotalCurrent=`
         $TotalCurrent+$file.Length;break}
         }

In order to pretty up the output, I spend a moment re-formatting the file size totals so that they come out in MB to a reasonable number of decimal places.

#format file size totals to MB
    $GrandTotal=”{0:N2}” -f ($GrandTotal/1048576)
    $Total2yr=”{0:N2}” -f ($Total2yr/1048576)
    $Total90=”{0:N2}” -f ($Total90/1048576)
    $Total1yr=”{0:N2}” -f ($Total1yr/1048576)
    $Total30=”{0:N2}” -f ($Total30/1048576)
    $Total7=”{0:N2}” -f ($Total7/1048576)
    $TotalCurrent=”{0:N2}” -f ($TotalCurrent/1048576)

Finally, I use Write-Host to display the information with some helpful color coding.

Write-Host “File Aging for” $dir.ToUpper()
    Write-Host “2 years:” $2yrs “files” $Total2yr “MB” -foregroundcolor “Red”
    Write-Host “1 year:” $1yr “files” $Total1yr “MB” -foregroundcolor “Magenta”
    Write-Host “3 months:” $3mo “files” $Total90 “MB” -foregroundcolor “Yellow”
    Write-Host “1 month:” $1mo “files” $Total30 “MB” -foregroundcolor “Cyan”
    Write-Host “1 week:” $1wk “files” $Total7 “MB” -foregroundcolor “Green”
    Write-Host “Current:” $current “files” $TotalCurrent “MB”
    Write-Host `n
    Write-Host “Totals:” $count “files” $GrandTotal “MB”
    Write-Host `n

To run the script in PowerShell simply run .\FileAging.ps1 foldername

In the near future, I’ll return to this script and show you how to take it to the next level.