src/Public/Move-OldFilesToArchive.ps1
|
#TODO: Test thoroughly (~95% confident that it works as intended) #TODO: Add Param to maintain permissions on moved files and folders #TODO: Refactor to use CablersPowershell-Base functions where possible <# .SYNOPSIS Moves files older than a specified date to an archive folder. .DESCRIPTION This function will move files older than a specified date to an archive folder. This is based on the last time the files were accessed, not created/modified. The archive date is specified as a parameter and can be any date in the past. The archive date can be specified as a string (e.g. "01/01/2020") or as a [datetime] object (e.g. "(Get-Date).AddDays(-30)"). The function will recursively search through all subfolders of the source folder and move any files that meet the criteria to the destination folder while maintaining the existing folder structure. The function will also take ownership of the files before moving them to ensure that the move operation is successful. It is recommended to run the function as a dry run first to ensure that the correct files are being moved. It is also recommended to run the Remove-EmptyFolders function after running this to clean up the source directory. .PARAMETER Source The source folder containing the files to be archived. .PARAMETER Destination The destination folder where the files will be moved to. .PARAMETER ArchiveDate The date before which files will be moved to the archive folder. .PARAMETER Quiet Suppresses the timer and total file size output. Will still return the full list of moved files. (Required in the recursive call to prevent output spam) .PARAMETER DryRun Performs a dry run of the operation without actually moving any files. Will return the full list of files that would have been moved. Will still output the time taken (this is not indicative of the actual time it would take to move the files). Will still output the total file size of the files that would have been moved. Both outputs will still be suppressed if the Quiet switch is used. .EXAMPLE Move-OldFilesToArchive -Source "C:\Temp" -Destination "C:\Archive" -ArchiveDate "01/01/2020" -DryRun This example will perform a dry run of the operation, moving files not accessed since 01/01/2020 from the "C:\Temp" folder to the "C:\Archive" folder. .EXAMPLE Move-OldFilesToArchive -Source "C:\Temp" -Destination "C:\Archive" -ArchiveDate (Get-Date).AddDays(-30) -Quiet This example will move files not accessed in the last 30 days from the "C:\Temp" folder to the "C:\Archive" folder. This will not display the total time taken or the total file size moved. .OUTPUTS A list of moved files including the source path, destination path, file size, and last access time. .NOTES Author: Brad Bullock, Cablers Ltd Date: 17/10/2024 #> function Move-OldFilesToArchive { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Source, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Destination, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [object]$ArchiveDate, [Parameter(Mandatory = $false)] [switch]$Quiet, # Suppress timer and total file size output. Will still return the full list of moved files. [Parameter(Mandatory = $false)] [switch]$DryRun # Similar to whatif but without the whatif output, still returns full list of files at the end ) begin { # Initialize an array to store moved files $MovedFiles = @() # Validate that the source path exists and that it is a folder, not a file if (-not (Test-Path -Path $Source -PathType Container)) { Throw "Source path '$Source' does not exist or is not a folder." } # Validate that the destination path exists and that it is a folder, not a file. Create the folder if missing. if (Test-Path -Path $Destination -PathType Leaf) { Throw "Destination path '$Destination' is not a folder." } If (-not (Test-Path -Path $Destination)) { New-Item -Path $Destination -ItemType Directory | Out-Null } # Convert ArchiveDate to [datetime] if it is a string if ($ArchiveDate -is [string]) { try { $ArchiveDate = Get-Date $ArchiveDate #$ArchiveDate = [datetime]::Parse($ArchiveDate) # Alternative method, may be needed depending on testing } catch { Throw "ArchiveDate '$ArchiveDate' is not a valid datetime string." } } # Validate that the archive date is in the past if ($ArchiveDate -gt (Get-Date)) { Throw "Archive date must be in the past." } } process { if (-not ($Quiet)) { Write-Host "It is recommended to run this comman as a dry run first! Use `'Get-Help Move-OldFilesToArchive`' for documentation." -ForegroundColor Yellow Write-Host "Press Ctrl + C to cancel now if you need to do a dry run. Command will start in 5 seconds." -ForegroundColor Yellow Write-Host "Warning: This may move files that are not intended to be moved if the last access time is inaccurate." Start-Sleep -Seconds 5 $StartTime = Get-Date Write-Host "Beginning archive process from $Source to $Destination for files older than $ArchiveDate" Write-Host "Start Time: $StartTime" } $Files = Get-ChildItem -LiteralPath $Source -File foreach ($File in $Files) { if ($File.LastAccessTime -lt $ArchiveDate) { Write-Verbose "Moving $($File.fullname) - Last Access Time: $($File.lastaccesstime)" $RelativePath = $File.FullName.Replace($Source, "") $DestinationPath = Join-Path -Path $Destination -ChildPath $RelativePath $DestinationFolder = Split-Path -Path $DestinationPath -Parent Write-Verbose "Destination Path: $DestinationPath" # Take ownership of the file if (-not ($DryRun)) { if (-not (Test-Path -LiteralPath $DestinationFolder)) { New-Item -ItemType Directory -Path $DestinationFolder | Out-Null } $Acl = Get-Acl -LiteralPath $File.FullName $Acl.SetOwner([System.Security.Principal.WindowsIdentity]::GetCurrent().User) Set-Acl -LiteralPath $File.FullName -AclObject $Acl Move-Item -LiteralPath $File.FullName -Destination $DestinationPath } # Add the moved file to the array including length $Obj = [PSCustomObject]@{ Source = $File.FullName 'Size (Bytes)' = $File.Length LastAccessTime = $File.LastAccessTime Destination = $DestinationPath } $MovedFiles += $Obj } Else { Write-Verbose "Skipping $($File.fullname) - Last Access Time: $($File.lastaccesstime)" -ForegroundColor Yellow } } $Subfolders = Get-ChildItem -LiteralPath $Source -Directory foreach ($Subfolder in $Subfolders) { $SubfolderDestination = Join-Path -Path $Destination -ChildPath $Subfolder.Name $MovedFiles += Move-OldFilesToArchive -source $Subfolder.FullName -Destination $SubfolderDestination -ArchiveDate $ArchiveDate -Quiet -DryRun:$DryRun -Verbose:$VerbosePreference } return $MovedFiles } end { If (-Not $Quiet) { # Output time taken with seconds to 2 decimal places Write-Host "Archive Complete. Time taken: $(([math]::Round((New-TimeSpan -Start $StartTime -End (Get-Date)).TotalSeconds, 2))) seconds" If ($DryRun -or $WhatIf) { Write-Host "This was a dry run, no files were actually moved." -ForegroundColor Yellow } # Sum up the size of all moved files in bytes $totalSizeBytes = ($MovedFiles | Measure-Object -Property 'Size (Bytes)' -Sum).Sum # Convert total size to gigabytes $totalSizeGB = [math]::Round($totalSizeBytes / 1GB, 2) Write-Host "Total size of moved files: $totalSizeGB GB`n" } } } |