Finding recently modified files, and summing file sizes

I am new to PowerShell, and am struggling a bit with what is probably a fairly simple task.

I need to select all files that have been modified in the past seven days inside a directory and any subdirectories and their subdirectories. I then need to group the output by the directory containing the modified files, and sum the total byte size of the modified files.

I know how to do most, if not all of these things by themselves. There are couple of caveats, and they are what seem to be giving me the biggest headache. The directory structure can and will change from run to run (though the root will remain constant), therefore, I never know how many layers deep this needs to go. I also only need the files modified within the last seven days, BUT if there are directories/subdirectories that exist, and did not have files modified within this time period, I need them to show up too, and have something that tells me that the total number of bytes changed = 0 bytes.

Ideal output would look similar to:

C:\directory\A
     12345 bytes changed

C:\directory\A\a
     230495 bytes changed

C:\directory\A\b
     0 bytes changed

C:\directory\B
     0 bytes changed

C:\directory\B\a
     594 bytes changed

C:\directory\B\a\1
     230094 bytes changed

etc...
$directories = Get-ChildItem -Path T:\ -Directory -Recurse |
Select-Object -ExpandProperty FullName

$files = Get-ChildItem -Path T:\ -File -Recurse |
Where-Object LastWriteTime -ge (Get-Date).AddDays(-7) |
Group-Object -Property Directory

$totals = ForEach ($directory in $directories) {
    If($files.Name -notcontains $directory) {
        $sum = 0
    } Else {
        $sum = $files |
        Where-Object Name -eq $directory |
        Select-Object -ExpandProperty Group |
        Measure-Object -Property Length -Sum |
        Select-Object -ExpandProperty Sum
    }

    [pscustomobject]@{
        'Path' = $directory
        'Sum' = $sum
    }
}

$format = @"
{0}
     {1} bytes changed

"@

$totals |
Sort-Object -Property Path |
ForEach-Object {
    $format -f $_.Path,$_.Sum
}

Several things in there require powershell version 3+.

I think you’re probably trying to do a bit too much up front.

$directories = Get-ChildItem -Path T:\ -Directory -Recurse |
Select-Object -ExpandProperty FullName

Yes, but there’s no reason to strip the object down to FullName yet. Let’s just start with:

function Get-ChangedFileBytes {
$directories = Get-ChildItem -Path T:\ -Directory -Recurse
foreach ($directory in $directories) {
  $directoryname = $directory.fullname

  $changes = $directory | Get-ChildItem |
    Where-Object LastWriteTime -ge (Get-Date).AddDays(-7) |
    Measure -Property Size -Sum
  
  $changedbytes = $changes.sum

  $props = @{ 'Directory' = $directoryname
              'Changes'   = $changedbytes }
  New-Object -Type PSObject -Prop $props

}
}

That’ll produce objects with the directory names and number of changed bytes. From there, you can pipe it to a Format command or something if you want it pretty on the screen. I haven’t tested this, but hopefully the flow makes sense.

A) that works PERFECTLY

B) I actually understand ALMOST all of that.

I understand what the ending section is doing, that it is formatting the data, and outputting it, but would you mind briefly explaining why it works? The syntax is new to me. (specifically, what’s going on in the section below)

$format = @"
{0}
     {1} bytes changed

"@

$totals |
Sort-Object -Property Path |
ForEach-Object {
    $format -f $_.Path,$_.Sum

-f is the Format operator. It’s inserting $.Path (value 0) and $.Sum (value 1) into the {0} and {1} placeholders. The @" "@ thing is a here-string, and easier way to have a multi-line, formatted string literal.

I -personally- tend to go with the build-a-function approach, and have that function output only objects, and then handle visual display in some other step. That way, if I later want the output going to a CSV, or XML, or whatever, my original data-getting function doesn’t have to change (“Learn PowerShell Toolmaking in a Month of Lunches”). I also don’t like to accumulate more in variables then I have to, since it eats memory and slows things down. So I’ll tend to assemble and output each chunk of data to the pipeline.

But what Craig did is exactly what you asked for.

Thank you both for your responses. These ideas are essentially what I had in mind. One thing that I love about this, and didn’t even think to try is building that $directory variable on the fly inside the for-each loop. I had played around with creating variables in the middle of statements before, and didn’t have any success, but that’s probably because I was trying to set them equal to something. The last statement I typed there made total sense in my head, but I understand if you can’t quite follow my thought process here. :smiley: