As I mentioned in the previous article working with errors was the biggest pain of most of the entries for the first advanced event.
Firstly the description does not say the errors need to be handled in any way, it only mentions you need to clearly display them, PowerShell does that by default. So if you would not use any error handling you would probably still comply with the requirements.
On the other hand when a standard PowerShell cmdlet fails it stops on the first error for each item, reports the error and jumps to the next item. This is what most of you tried to achieve, but not always successfully. Error records got stripped to just error messages informing user, terminating errors became non-terminating and vice versa.
The core of the task is to move files from one location to another. The Move-Item cmdlet is the most reasonable choice, but it has one limitation that must be taken into account: If the destination path (folder) does not exist the operation ends with a non-terminating exception.
For the sake of simplicity let’s assume the source and destination root paths exist because I tested their availability while validating the parameters. There I also made sure the path is a FileSystem path not a registry or other path (believe or not this is more important than the first check). I also assume the remaining parameters are valid.
The goal is to move as much files as I can to the new location. Freeing as much disk space as I can for Dr. Scripto.

My script is going to be laid out like this:

$sourceItem = #[1] Get items recursively and filter them according to the requirements.
foreach ($currentItem in $sourceItem)
{
	#[2] Form the path to the resulting directory.
	#[3] If the destination directory doesn't exist, create it.
	#[4] Move the current item.
}
  1. I ruled out “path does not exist” type of errors by validating the input, but still I can get access denied error. These errors are originating from the cmdlet and are non-terminating. Unless some core exception occurs I am pretty safe to assume the worst that could happen is ton of AccessDenied errors and empty $sourceItem collection. Putting this into the context of the function: Add as much files as you can to the collection.
  2. I am going to use .Replace() method of the System.String, there are two possible exceptions: ArgumentNullException and ArgumentException. Neither I am likely to hit because I validated my input.
  3. In this step I am going to create the directory for the files if it is not already in place. Item already exists exception is possible here, I will avoid this by testing the path before trying to create the folder. Any other exception coming from the cmdlet I want captured.
  4. Moving the current item may also produce quite a few non-terminating exceptions. If this operation fails I want to remove the previously created folder so I don’t leave any garbage behind.

If any of the last three steps (2, 3 or 4) fails the function should progress to the next item in the list. I am going to force this behavior using ErrorAction on the cmdlets and Try Catch block that will capture only two types of exceptions:

[Management.Automation.MethodInvocationException]

these may raise from .NET calls.

[Management.Automation.ActionPreferenceStopException]

these are raised when cmdlet fails and has the ErrorAction set to Stop.
All these exceptions were originally non-terminating, so after doing the clean-up I write them back to the screen using the Write-Error, keeping all the original information.
The important thing is most of the possible exceptions (like OutOfMemoryException) are still able to pass by unaffected. So if the script fails the terminating exceptions bubble up and possibly terminate the script, the non-terminating exceptions are written to the error stream and the scripts progresses.
Here is the example code:

$Source = 'C:\Windows\logs'
$Destination = 'G:'
$Before = (Get-Date).addDays(-90)
$Filter = '*.log'

$sourceItem = Get-ChildItem -Recurse -Path $Source -File -Filter $Filter |
	Where-Object {
		$_.LastWriteTime -lt $Before
	}

foreach ($currentItem in $sourceItem)
{
	$destinationCreated = $false
	try
	{
		$destinationDirectory = $currentItem.Directory.FullName.Replace($Source,$Destination)
		if (-not ( Test-Path -Path $destinationDirectory -ErrorAction Stop))
		{
			$destinationCreated = [bool]( New-Item -Path $destinationDirectory -ItemType Directory -ErrorAction Stop )
		}

		Move-Item -Path $currentItem.FullName -Destination $destinationDirectory -ErrorAction Stop
	}
	catch [Management.Automation.MethodInvocationException], [Management.Automation.ActionPreferenceStopException]
	{
		if ( $destinationCreated )
		{
			Remove-Item -Path $destinationDirectory -Force
		}

		Write-Error -ErrorRecord $_
	}
}