I'm always talking about how much the object-nature of PowerShell makes all the difference in the world. Today, I have another example. Let's say you want to analyze a directory, perhaps a shared group folder for a department. And you want to identify files that haven't been modified in a while. I like this topic because it is real world and offers a good framework for demonstrating PowerShell techniques.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
You would like to divide the files into aging "buckets". Let's begin by getting all of the files. I'm using PowerShell 3.0 so you'll have to adjust parameters if you are using 2.0. You can run all of this interactively in the console, but I think you'll find using a script much easier.
$files = dir c:\work -recurse -file
Now, let's add a new property, or member, to the file object called FileAgeDays which will be the value of the number of days since the file was last modified, based on the LastWriteTime property. We'll use the Add-Member cmdlet to define this property.
$files | Add-Member ScriptProperty -Name FileAgeDays -Value { [int]((Get-Date) - ($this.LastWriteTime)).TotalDays }
The new property is technically a ScriptProperty so that we can run a scriptblock to define the value. In this case we're subtracting the LastwriteTime value of the each object from the current date and time. This will return a TimeStamp object but all we need is the TotalDays property which is cast as an integer, effectively rounding the value. In a pipelined expression like Select-Object you would use $_ to indicate the current object in the pipeline. Here, we can use $this.
Next, we'll add another script property to define our "bucket" property.
$files | Add-Member ScriptProperty -Name FileAge -Value { if ($this.FileAgeDays -ge 365) { "1year" } elseif ($this.FileAgeDays -ge 180) { "6Months" } elseif ($this.FileAgeDays -ge 90) { "90Days" } elseif ($this.FileAgeDays -ge 45) { "45Days" } else { "Current" } }
The script block can be as long as you need it to be. Here, we're using an If/ElseIf construct based on the FileAgeDays property we just created. If we look at $files now, we won't see these new properties.
But that is because the new properties aren't part of the default display settings. So we need to specify them.
Now, we can group the objects based on these new properties.
$files | Group FileAge -NoElement | sort Count -Descending
Or perhaps we'd like to drill down a bit more.
$grouped = $files | Group FileAge | Add-Member -MemberType ScriptProperty -Name SizeMB -Value { ($this.Group | Measure-Object Length -sum).Sum / 1MB } -PassThru
Now we've added a new member to the GroupInfo object that will show the total size of all files in each group by MB. Don't forget to use -Passthru to force PowerShell to write the new object back to the pipeline so it can be saved in the grouped variable. Finally, the result:
$grouped | Sort SizeMB -Descending | Format-Table SizeMB,Count,Name -AutoSize
And there you go. Because we're working with objects, adding new information is really quite easy. Certainly much easier than trying to do something like this in VBScript! And even if you don't need this specific solution, I hope that you picked up a technique or two.
I like this. I’m curious about using a scriptproperty rather than a noteproperty for a value that won’t be changing after it’s set. I generally ignore performance-related questions in PowerShell because generally administative tasks are the bottleneck rather than the language, but here it seems like calculating this each time isn’t needed.
That is a fair question. I use to use Noteproperties all the time. But then you need to use a ForEach-object loop to calculate the value use $_. The ScriptProperty is much easier in a pipelined expression. At least in the things I’ve done, I haven’t noticed any performance bottleneck.
Awesome. I thought the add-member looked a bit different (i.e. easier) from how I usually do it.
FWIW, the code would look like this:
$files | foreach-object { $_| add-member -MemberType NoteProperty -Value ([int]((Get-Date) – ($_.LastWriteTime)).TotalDays) -name FileAge -PassThru}
It doesn’t look quite as nice as what you had. 🙂
Hi, I am a dummy. Could you show the differences between V 3.0 and 2.0. My 2.0 says:
$files = dir c:\scripts -recurse -file
A parameter cannot be found that matches parameter name ‘file’.
I should have made it clearer. Sorry. In v2 if you want to get files only with Get-ChildItem you need a command like this:
dir c:\work | where { -not $_.PSIsContainer}
In 3.0 there is now the -File or -Directory parameter to use.
Thanks, I thought that was the case, but I wanted to be sure.
Here is my version 2 script:
$files = gci c:\scripts -rec | where { ! $_.PSIsContainer }
$files | Add-Member ScriptProperty -Name FileAgeDays -Value {
[int]((Get-Date) - ($this.LastWriteTime)).TotalDays }
$files | Add-Member ScriptProperty -Name FileAge -Value {
if ($this.FileAgeDays -ge 365) {
"1 year"
}
elseif ($this.FileAgeDays -ge 180) {
"6 Months"
}
elseif ($this.FileAgeDays -ge 90) {
"90 Days"
}
elseif ($this.FileAgeDays -ge 45) {
"45 Days"
}
else {
"Current"
}
}
$files | Group FileAge -NoElement | sort Count -Descending
$grouped = $files | Group FileAge |
Add-Member -MemberType ScriptProperty -Name SizeMB -Value {
($this.Group | Measure-Object Length -sum).Sum / 1MB
} -PassThru