Some PowerShell goodness
- by KyleBurns
Ever work somewhere where processes dump files into folders to maintain an archive? Me too and Windows Explorer hates it. Very often I find myself needing to organize these files into subfolders so that I can go after files without locking up Windows Explorer and my answer used to be to write a program in something like C# to do the job. These programs will typically enumerate the files in a folder and move each file to a subdirectory named based on a datestamp. The last such program I wrote had to use lower-level Win32 API calls to perform the enumeration because it appears the standard .Net calls make use of the same method of enumerating the directories that Windows Explorer chokes on when dealing with a large number of entries in a particular directory, so a simple task was accomplished with a lot of code.
Of course, this little utility was just something I used to make my life easier and "not a production app", so it was in my local source folder and not source control when my hard drive died. So... I was getting ready to re-create it and thought it might be a good idea to play with PowerShell a bit - something I had been wanting to do but had not yet met a requirement to make me do it. The resulting script was amazingly succinct and even building the flexibility for parameterization and adding line breaks for readability was only about 25 lines long. Here's the code with discussion following:
param(
[Parameter(
Mandatory = $false,
Position = 0,
HelpMessage = "Root of the folders or share to archive. Be sure to end with appropriate path separator"
)]
[String] $folderRoot="\\fileServer\pathToFolderWithLotsOfFiles\",
[Parameter(
Mandatory = $false,
Position = 1
)]
[int] $days = 1
)
dir $folderRoot|?{(!($_.PsIsContainer)) -and ((get-date) - $_.lastwritetime).totaldays -gt $days }|%{
[string]$year=$([string]$_.lastwritetime.year)
[string]$month=$_.lastwritetime.month
[string]$day=$_.lastwritetime.day
$dir=$folderRoot+$year+"\"+$month+"\"+$day
if(!(test-path $dir)){
new-item -type container $dir
}
Write-output $_
move-item $_.fullname $dir
}
The script starts by declaring two parameters. The first parameter holds the path to the folder that I am going to be sorting into subdirectories. The path separator is intended to be included in this argument because I didn't want to mess with determining whether this was local or UNC and picking the right separator in code, but this could be easily improved upon using Path.Combine since PowerShell has access to the full framework libraries. The second parameter holds a minimum age in days for files to be removed from the root folder. The script then pipes the dir command through a query to include only files (by excluding containers) and of those, only entries that meet the age requirement based on the last modified datestamp. For each of those, the datestamp is used to construct a folder name in the format YYYY\MM\DD (if you're in an environment where even a day's worth of files need further divided, you could make this more granular) and the folder is created if it does not yet exist. Finally, the file is moved into the directory.
One of the things that was really cool about using PowerShell for this task is that the new-item command is smart enough to create the entire subdirectory structure with a single call. In previous code that I have written to do this kind of thing, I would have to test the entire tree leading down to the subfolder I want, leading to a lot of branching code that detracted from being able to quickly look at the code and understand the job it performs.
Overall, I have to say I'm really pleased with what has been done making PowerShell powerful and useful.