One of the most important coding methodologies to follow is ensuring you know and manage each way your code can “flow”. If you think of your code as a flow, it can branch off, return to various points, and encounter many conditions.
Error handling ensures you set up “nets” or a default place your code can flow to when something unexpected happens.
Let’s use a real-world scenario you might find yourself in, dealing with PowerShell error handling.
Building the Initial Script for File Cleanup
We need to clean up some old files. Our file server has been around forever, and we need to clean up some space. Management has decided to remove all files older than a specified number of days. We need to build a script that recursively searches a folder, finds all files older than a certain number of days, and removes them.
The task sounds easy enough, but this is the error-handling section, so you know some things will go wrong!
Let’s start to understand error handling by first building the scenario’s demo script without error handling to demonstrate the problem that error handling solves.
-
First, open a new VS Code tab.
Since we’re just trying a few things now, we won’t save the script yet. Temporarily tell VS Code that you’re about to write some PowerShell.
Hit Ctrl-Shift-P, type ‘lang’, select Choose Language Mode, type ‘power`, and choose PowerShell. Now VS Code knows you’ll be writing PowerShell.
-
Next, break the problem up into tasks, solving the most obvious one first.
For this instance, the task comes up with a command to read files in a directory.
Get-ChildItem -Path C:\OldForgottenFolder
-
Get-ChildItem
also returns directories we don’t need, so let’s limit that to only files.Get-ChildItem -Path C:\OldForgottenFolder -File
-
If there are files in those subdirectories, we need to get them too with
Recurse
.Get-ChildItem -Path C:\OldForgottenFolder -File -Recurse
-
Now that we have the command and parameters, it’s returning ALL files. We only need to find ones older than a certain number of days.
Since
Get-ChildItem
returns each file with aLastWriteTime
object property, we must filter on that property. We’ll use theWhere
filter to find the files with aLastWriteTime
less than a specified date.(Get-ChildItem -Path C:\OldForgottenFolder -File -Recurse).Where{$_.LastWriteTime -le ?????}
-
The date needs to be dynamic because “old” today will be different than “old” tomorrow.
Comment out the previous line because we’ll need it at some point and then figure out the date situation.
## (Get-ChildItem -Path C:\\OldForgottenFolder -File -Recurse).Where{$_.LastWriteTime -le ?????} $Now = Get-Date $Now
-
Now that we have today’s date let’s find a specific number of days before today to find the date. I’ll just put
30
in here temporarily since I know some files are more than five days old to do a rudimentary test.## (Get-ChildItem -Path C:\\OldForgottenFolder -File -Recurse).Where{$_.LastWriteTime -le ?????} $Now = Get-Date $LastWrite = $Now.AddDays(-30) $LastWrite
-
Done! Let’s put it together so far.
$Now = Get-Date $LastWrite = $Now.AddDays(-30) (Get-ChildItem -Path C:\OldForgottenFolder -File -Recurse).Where{$_.LastWriteTime -le $LastWrite}
We now have a tiny script that finds all files in a directory that are older than a specific number of days.
-
Next, we must add the ability to remove those older files. This is trivial using the
Remove-Item
cmdlet and the pipeline.$Now = Get-Date $LastWrite = $Now.AddDays(-30) (Get-ChildItem -Path C:\OldForgottenFolder -File -Recurse).Where{$_.LastWriteTime -le $LastWrite} | Remove-Item
-
Done! But wait, I have no idea which files it removed. There were also some errors which we’ll address in a few minutes. Let’s add some basic functionality.
$VerbosePreference = 'Continue' $Now = Get-Date $LastWrite = $Now.AddDays(-30) $oldFiles = (Get-ChildItem -Path C:\OldForgottenFolder -File -Recurse).Where{$_.LastWriteTime -le $LastWrite} foreach ($file in $oldFiles) { Remove-Item -Path $file.FullName Write-Verbose -Message "Successfully removed [$($file.FullName)]." }
-
You’ll need to include a loop like this to run some kind of code for each file. Here we’re not using the pipeline and instead putting all of the files found in an
oldFiles
variable, an array of file objects. We’re then runningRemove-Item
on each one like before, but this time including a verbose message telling us what file is being removed. -
Let’s now run this code and see what happens.
You can now see through the verbose message it removed some files. The code we now have is the guts we need to create the script. Let’s now create a real script from this in the following section.
Maximizing Flexibility and Reusability with Parameters
You’ve built your script, but it still has the potential to be flexible and reusable. How? Parameters will allow us to specify the directory and the age of the files we want to target, making the script more flexible.
-
Before we go much further, let’s save our work. Call it Remove-FileOlderThan.ps1.
Notice the verb/noun format with a dash. If possible, try to always create script names in the same manner as PowerShell commands for consistency and readability.
-
First, scripts are meant to be reusable. Chances are, you’ll probably want to use this script on different directories and different ages. We’ll need to introduce some parameters. To do that, we figure out what will change. The directory and number of days. Got it.
param ( [Parameter(Mandatory)] [string]$FolderPath,
[Parameter(Mandatory)] [int]$DaysOld
)
$Now = Get-Date
$LastWrite = $Now.AddDays(-30)
$oldFiles = (Get-ChildItem -Path C:\OldForgottenFolder -File -Recurse).Where{$_.LastWriteTime -le $LastWrite}
foreach ($file in $oldFiles) {
Remove-Item -Path $file.FullName
Write-Verbose -Message "Successfully removed [$($file.FullName)]."
}Add a
param
block at the top and define each parameter as mandatory since we must have a path and number for the script to function. Also, specify the type here as a best practice. -
Replace the static items we had in the code before with the parameter values.
param ( [Parameter(Mandatory)] [string]$FolderPath,
[Parameter(Mandatory)] [int]$DaysOld
)
$Now = Get-Date
$LastWrite = $Now.AddDays(-$DaysOld)
$oldFiles = (Get-ChildItem -Path $FolderPath -File -Recurse).Where{$_.LastWriteTime -le $LastWrite}
foreach ($file in $oldFiles) {
Remove-Item -Path $file.FullName
Write-Verbose -Message "Successfully removed [$($file.FullName)]."
} -
Let’s now run the script and see what happens.
C:\Scripts\Remove-FileOlderThan.ps1 -FolderPath C:\OldForgottenFolder -DaysOld 30 -Verbose
You can see how we have to specify the path of the folder and the number of days old as parameters. Use the
Verbose
parameter to see thatWrite-Verbose
line.PowerShell ran the script exactly as before, but we now have a parameterized script we can use on any directory or any age of files!
Taking a look at the output, we did hit some red text. Either you don’t have rights, or the file is read-only. But what files did it fail on? And how do you ensure those files are removed too?
Conclusion
In this tutorial, we built a script to clean up old files from a directory, ensuring flexibility by adding parameters. While the script works as intended, we saw that error handling was not yet addressed, which is crucial when dealing with real-world scenarios.
As we move forward, adding error management will allow us to handle issues, such as cmdlets throwing errors or files being inaccessible, helping us avoid script termination and providing detailed insights into what went wrong.
Stay tuned for the next demo! PowerShell 101: Terminating, Non-Terminating Errors, and Try/Catch.