Last active
February 1, 2019 08:20
-
-
Save bmaingret/46a8caa5e6be38edf3f619b03230214f to your computer and use it in GitHub Desktop.
Copy/Move all files that have been modified a few days ago from a source to a daily backup directory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
# | |
.Synopsis | |
.DESCRIPTION | |
Copy all files from $sourceDirectory to a directory named $targetDirectory\'yyyy-mm-dd' based on current day that have been modified at least $minimumAgeInDays days ago | |
Typical usage is the purge of a directory: copy files older than $minimumAgeInDays days to another directory | |
.NOTES | |
Created By: Baptiste Maingret | |
#> | |
################# | |
# CONFIGURATION # | |
################# | |
# Source directory from which the files will be moved | |
$sourceDirectory = 'C:/src' | |
# Target directory in which a new folder for each day will be created, storing the moved files | |
$targetDirectory = 'C:/target' | |
# Directory for the log files | |
$logDirectory = 'C:/target' | |
# Only a few daily purge directories will be kept. If this parameter is set, older directories won't be removed but will be moved to this directory. If left empty they will be removed | |
$purgeTrash = '' | |
# Only the number of $PurgeToKeep daily directories will be kept | |
$PurgeToKeep = 10 | |
# Only files olders than $MinimumAgeInDays will be moved | |
$MinimumAgeInDays = 1 | |
# Either set it to $True or $False depending if you want to make a dry-run or not | |
$dryrun = $False | |
# Set mode to copy or move. Default to copy if not indicated here. | |
$mode = "move" | |
######################### | |
# /END of CONFIGURATION # | |
######################### | |
########## | |
# LOGGER # | |
########## | |
<# | |
.Synopsis | |
Write-Log writes a message to a specified log file with the current time stamp. | |
.DESCRIPTION | |
The Write-Log function is designed to add logging capability to other scripts. | |
In addition to writing output and/or verbose you can write to a log file for | |
later debugging. | |
.NOTES | |
Created by: Jason Wasser @wasserja | |
Modified: 11/24/2015 09:30:19 AM | |
Changelog: | |
* Code simplification and clarification - thanks to @juneb_get_help | |
* Added documentation. | |
* Renamed LogPath parameter to Path to keep it standard - thanks to @JeffHicks | |
* Revised the Force switch to work as it should - thanks to @JeffHicks | |
To Do: | |
* Add error handling if trying to create a log file in a inaccessible location. | |
* Add ability to write $Message to $Verbose or $Error pipelines to eliminate | |
duplicates. | |
.PARAMETER Message | |
Message is the content that you wish to add to the log file. | |
.PARAMETER Path | |
The path to the log file to which you would like to write. By default the function will | |
create the path and file if it does not exist. | |
.PARAMETER Level | |
Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational) | |
.PARAMETER NoClobber | |
Use NoClobber if you do not wish to overwrite an existing file. | |
.EXAMPLE | |
Write-Log -Message 'Log message' | |
Writes the message to c:\Logs\PowerShellLog.log. | |
.EXAMPLE | |
Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log | |
Writes the content to the specified log file and creates the path and file specified. | |
.EXAMPLE | |
Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error | |
Writes the message to the specified log file as an error message, and writes the message to the error pipeline. | |
.LINK | |
https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0 | |
#> | |
function Write-Log | |
{ | |
[CmdletBinding()] | |
Param | |
( | |
[Parameter(Mandatory=$true, | |
ValueFromPipeline=$true)] | |
[ValidateNotNullOrEmpty()] | |
[Alias("LogContent")] | |
[string]$Message, | |
[Parameter(Mandatory=$false)] | |
[Alias('LogPath')] | |
[string]$Path='C:\Logs\PowerShellLog.log', | |
[Parameter(Mandatory=$false)] | |
[ValidateSet("Error","Warn","Info")] | |
[string]$Level="Info", | |
[Parameter(Mandatory=$false)] | |
[switch]$NoClobber | |
) | |
Begin | |
{ | |
# Set VerbosePreference to Continue so that verbose messages are displayed. | |
$VerbosePreference = 'Continue' | |
} | |
Process | |
{ | |
# If the file already exists and NoClobber was specified, do not write to the log. | |
if ((Test-Path $Path) -AND $NoClobber) { | |
Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name." | |
Return | |
} | |
# If attempting to write to a log file in a folder/path that doesn't exist create the file including the path. | |
elseif (!(Test-Path $Path)) { | |
Write-Verbose "Creating $Path." | |
$NewLogFile = New-Item $Path -Force -ItemType File | |
} | |
else { | |
# Nothing to see here yet. | |
} | |
# Format Date for our Log File | |
$FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
# Write message to error, warning, or verbose pipeline and specify $LevelText | |
switch ($Level) { | |
'Error' { | |
Write-Error $Message | |
$LevelText = 'ERROR:' | |
} | |
'Warn' { | |
Write-Warning $Message | |
$LevelText = 'WARNING:' | |
} | |
'Info' { | |
Write-Verbose $Message | |
$LevelText = 'INFO:' | |
} | |
} | |
# Write log entry to $Path | |
"$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append -Encoding UTF8 | |
} | |
End | |
{ | |
} | |
} | |
################# | |
# END OF LOGGER # | |
################# | |
<# | |
.NOTES | |
Created by https://stackoverflow.com/users/11421/mladen-mihajlovic | |
.LINK https://stackoverflow.com/a/40887001 | |
#> | |
function DisplayInBytes($num) | |
{ | |
$suffix = "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" | |
$index = 0 | |
while ($num -gt 1kb) | |
{ | |
$num = $num / 1kb | |
$index++ | |
} | |
"{0:N1} {1}" -f $num, $suffix[$index] | |
} | |
########## | |
# SCRIPT # | |
########## | |
$todayString = (Get-Date).ToString("yyyy-MM-dd") | |
$minAge = (Get-Date).AddDays(-$minimumAgeInDays) | |
$todayBackupDirectory = Join-Path -Path $targetDirectory -ChildPath $todayString | |
$todayLog = $todayString + ".log" | |
$todayLogPath = Join-Path -Path $logDirectory -ChildPath $todayLog | |
$runner = "$($env:UserDomain)\$($env:UserName)@$($env:computername)>" | |
$placeHolder = "$todayString." + "0_tmp_purge_0" | |
$placeHolderPath = Join-Path -Path $sourceDirectory -ChildPath $placeHolder | |
$executionPath = (Get-Item -Path ".\").FullName | |
$modes = @{copy = "Copy-Item"; move = "Move-Item"} | |
$command = if ($modes.ContainsKey($mode)) { $modes.$mode} else { $modes.copy } | |
$whatif = @{WhatIf = $dryrun} | |
Write-Log -Level Info -Message "$runner <BEGIN SCRIPT>" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner # CONFIGURATION" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Dry-run: $($whatif.WhatIf)" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Mode: $mode" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Source directory: $sourceDirectory" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Target directory: $targetDirectory" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Today's backup directory: $todayBackupDirectory" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Log path: $todayLogPath" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Files older than $minimumAgeInDays days will be purged" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Only $purgeToKeep purge folders will be kept" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Execution path $executionPath" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>>>>> These parameters can be changed in $PSCommandPath" -Path $todayLogPath | |
# Check if today's backup directory already exists | |
Write-Log -Level Info -Message "$runner # PURGE FILES" -Path $todayLogPath | |
If ((Test-Path -Path $todayBackupDirectory)){ | |
Write-Log -Level Info -Message "$runner >>> Today's directory already exists: $todayBackupDirectory" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>> Purge probably has already been done." -Path $todayLogPath | |
} | |
Else { | |
# Create today's backup directory | |
Write-Log -Level Info -Message "$runner >>> Create today's backup directory $todayBackupDirectory" -Path $todayLogPath | |
Try { | |
New-Item -ItemType directory -Path $todayBackupDirectory -errorAction stop @whatif | Out-Null | |
} | |
Catch { | |
$Exception = $_ | |
$ErrorMessage = $_.Exception.Message | |
Write-Log -Level Error -Message "$runner >>>>>> Error creating target directory $todayBackupDirectory : $ErrorMessage" -Path $todayLogPath | |
Write-Log -Level Error -Message "$runner >>>>>> Ending script" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner <END SCRIPT>" -Path $todayLogPath | |
exit | |
} | |
# Check if placeholder file already exists and else create it | |
If (!(Test-Path -Path $placeHolderPath )) { | |
Write-Log -Level Info -Message "$runner >>> Create placeholder $placeHolderPath" -Path $todayLogPath | |
Try { | |
New-Item -Path $placeHolderPath -type "File" @whatif | Out-Null | |
} | |
Catch { | |
$Exception = $_ | |
$ErrorMessage = $_.Exception.Message | |
Write-Log -Level Error -Message "$runner >>>>>> Error creating placeholder $placeHolderPath : $ErrorMessage" -Path $todayLogPath | |
Write-Log -Level Error -Message "$runner >>>>>> Ending script" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner <END SCRIPT>" -Path $todayLogPath | |
exit | |
} | |
} | |
# Find files that need to be purged | |
Write-Log -Level Info -Message "$runner >>> Finding files to be purged in $sour" -Path $todayLogPath | |
$filesToPurge = Get-ChildItem -Path $sourceDirectory -File -Recurse -Exclude $placeHolder | Where-Object { $_.CreationTime.Date -le $minAge -and $_.LastWriteTime.Date -le $minAge } | |
# Init some summary variables | |
$totalFilesCount = $filesToPurge.Count | |
$totalSize = ($filesToPurge | Measure-Object -Sum Length).Sum | |
$failedFilesCount = 0 | |
$failedSize = 0 | |
$failedFiles = @{} | |
Write-Log -Level Info -Message "$runner >>>>>>$totalFilesCount files to be purged ($(DisplayInBytes($totalSize))):" -Path $todayLogPath | |
foreach($file in $filesToPurge) { | |
Write-Log -Level Info -Message "$runner >>>>>>>>> $file" -Path $todayLogPath | |
} | |
Write-Log -Level Info -Message "$runner >>>>>> Starting purging files:" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>>>>> From: $sourceDirectory" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>>>>> To: $todayBackupDirectory" -Path $todayLogPath | |
# To get the relative path of the files to purge inside the source directory we need to set the location as follow | |
Set-Location $sourceDirectory | |
# We copy/move each file to the target directory | |
foreach($file in $filesToPurge) { | |
# Relative path for the source directory does not resolve to . but instead to src directory inside parent (eg. "..\src"), causing issues afterwards | |
If ( $file.DirectoryName -eq $sourceDirectory ){ | |
$relativePath = "." | |
} | |
Else { | |
$relativePath = $file.DirectoryName | Resolve-Path -Relative | |
} | |
$targetPath = Join-Path -Path $todayBackupDirectory -ChildPath $relativePath | |
Write-Log -Level Info -Message "$runner >>>>>>>>> Purging $file to $targetPath ($(DisplayInBytes($file.Length)))" -Path $todayLogPath | |
Try { | |
New-Item -ItemType directory -Path $targetPath -Force -errorAction stop @whatif | Out-Null | |
$parameters = @{ | |
Path = $file; | |
Destination = $targetPath; | |
errorAction = "stop"; | |
WhatIf = $whatif.WhatIf | |
} | |
Invoke-Expression ($command + ' @parameters') | Out-Null | |
} | |
Catch { | |
$Exception = $_ | |
$ErrorMessage = $_.Exception.Message | |
Write-Log -Level Warn -Message "$runner >>>>>> Error purging $file : $ErrorMessage" -Path $todayLogPath | |
$failedFilesCount += 1 | |
$failedSize += $file.Length | |
$failedFiles[$file.FullName] = $ErrorMessage | |
} | |
} | |
Write-Log -Level Info -Message "$runner >>>>>> Purge ended:" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>>>>>>> Total files: $totalFilesCount ($(DisplayInBytes($totalSize)))" -Path $todayLogPath | |
Write-Log -Level Info -Message "$runner >>>>>>>> Failed files: $failedFilesCount ($(DisplayInBytes($failedSize))) : " -Path $todayLogPath | |
ForEach($file in $failedFiles.Keys){ | |
Write-Log -Level Info -Message "$runner >>>>>>>>>>> Failed: $file : $($failedFiles.$file)" -Path $todayLogPath | |
} | |
Write-Log -Level Info -Message "$runner >>> Remove placeholder $placeholder" -Path $todayLogPath | |
Try { | |
Remove-Item -Path $placeHolderPath -ErrorAction Stop @whatif | Out-Null | |
} | |
Catch { | |
$Exception = $_ | |
$ErrorMessage = $_.Exception.Message | |
Write-Log -Level Warn -Message "$runner >>>>>> Failed to remove placeholder file : $ErrorMessage" -Path $todayLogPath | |
} | |
} | |
Write-Log -Level Info -Message "$runner # CLEANING OF OLD PURGE FOLDERS" -Path $todayLogPath | |
$Folders = Get-ChildItem -Path $targetDirectory -Exclude $purgeTrash -Directory | Foreach {$_.Name} | |
$Folders = [string[]]$Folders | |
$Folders = @($Folders) -match '^\d{4}-\d{2}-\d{2}$' | |
$Count = $Folders.Count | |
If ($Count -gt $purgeToKeep) { | |
Write-Log -Message "$runner >>> $Count folders found" -Path $todayLogPath | |
$Folders = $Folders | sort | |
$FoldersToRemove = @($Folders[0..($Count-$purgeToKeep-1)]) | |
Foreach ($PurgeFolder in $FoldersToRemove) { | |
$PurgeFolderDate = [datetime]::ParseExact($PurgeFolder,'yyyy-MM-dd', $null) | |
If ($PurgeFolderDate -le ((Get-Date).AddDays(-$purgeToKeep))) { | |
Try { | |
If ($purgeTrash -eq ''){ | |
Remove-Item (Join-Path -Path $targetDirectory -ChildPath $PurgeFolder) -Force -Recurse -ErrorAction Stop @whatif | Out-Null | |
Write-Log -Level Info -Message "$runner >>>>>> $($PurgeFolder) removed" -Path $todayLogPath | |
} | |
Else { | |
New-Item -ItemType directory -Path $purgeTrash -Force -ErrorAction Stop @whatif | Out-Null | |
Move-Item -Path (Join-Path -Path $targetDirectory -ChildPath $PurgeFolder) -Destination $purgeTrash -ErrorAction Stop @whatif | Out-Null | |
Write-Log -Level Info -Message "$runner >>>>>> $($PurgeFolder) moved to $purgeTrash" -Path $todayLogPath | |
} | |
} | |
Catch { | |
$ErrorMessage = $_.Exception.Message | |
Write-Log -Level Warn -Message "$runner >>>>>> $($PurgeFolder) not removed : $ErrorMessage" -Path $todayLogPath | |
} | |
} | |
} | |
} | |
Else { | |
Write-Log -Message "$runner >>> Only $Count folders found. No clean-up needed" -Path $todayLogPath | |
} | |
Write-Log -Level Info -Message "$runner <END SCRIPT>" -Path $todayLogPath | |
################# | |
# END OF SCRIPT # | |
################# |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment