diff --git a/CHANGELOG.md b/CHANGELOG.md index bda97d3..3ef154c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # PSTimeMachine Change Log +## Version 1.0.3 (in progress) + - If a file is in use and can't be copied, PSTimeMachine will now keep running instead of terminating. + - If a copy-only backup is performed, file names will now be written to the log file. + - Resolve-Path has replaced -Replace for calculating relative paths. + ## Version 1.0.2 (2019-06-03) - Fixed a bug where an initial or -NoHardLinks backup would not show verbose errors when -Verbose was supplied. - Fixed a bug where the script would terminate before completion if transcription were not enabled. diff --git a/PSTimeMachine.ps1 b/PSTimeMachine.ps1 index 2b35a63..8c08e20 100755 --- a/PSTimeMachine.ps1 +++ b/PSTimeMachine.ps1 @@ -1,6 +1,6 @@ <# .NOTES -PSTimeMachine.ps1 - Version 1.0.2 +PSTimeMachine.ps1 - Version 1.0.3-beta6 (c) 2019 Colin Cogle This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. @@ -91,9 +91,11 @@ Param( [Switch]$NoStatistics = $false ) -# Save the old error preference. It's rude to clobber the user's environment. +# Save the old error preference and location. It's rude to clobber the user's environment. New-Variable -Option Constant -Name OldErrorActionPreference -Value $ErrorActionPreference +New-Variable -Option Constant -Name OldLocation -Value (Get-Location) $ErrorActionPreference = "Stop" +Set-Location -Path $SourcePath # We're going to create a folder to hold the backup, named for the current date and time. # Format everything except the year with leading zeroes. @@ -101,6 +103,7 @@ New-Variable -Option Constant -Name Today -Value (Get-Date) New-Variable -Option Constant -Name FolderName -Value (("{0:yyyy}-{0:MM}-{0:dd}T{0:hh}-{0:mm}-{0:ss}" -f $Today) + ".inProgress") # Start logging? +$DoVerboseCopy = $VerbosePreference -eq "Continue" If (-Not $NoLogging) { $script:LogFile = (New-TemporaryFile) Start-Transcript -Path (($script:LogFile).Name) @@ -131,63 +134,69 @@ $bytesCopied = 0 $bytesTotal = 0 Try { - # Has hardlinking support been disabled? If so, just do a copy. - If ($NoHardLinks) { - Write-Verbose "Hard-linking is disabled at user request. Copying all files." - Copy-Item -Path (Join-Path -Path $SourcePath "*") -Destination (Join-Path -Path $DestinationPath $FolderName) -Recurse + $DoCopyOnlyBackup = $NoHardLinks + + # Look for old backups in the same destination. + $PreviousBackups = (Get-ChildItem -Attributes Directory -Path $DestinationPath -Exclude "*.inProgress" -ErrorAction SilentlyContinue ` + | Where-Object {$_.Name -CMatch "\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(\.inProgress)?"} ` + | Sort-Object -Descending CreationTime + ) + If ($PreviousBackups.Count -gt 0) { + Write-Verbose "Previous backup found. Doing an incremental backup." + $DoCopyOnlyBackup = $false + $PreviousBackup = $PreviousBackups[0].Name } Else { - # Look for old backups in the same destination. - $PreviousBackups = (Get-ChildItem -Attributes Directory -Path $DestinationPath -Exclude "*.inProgress" -ErrorAction SilentlyContinue ` - | Where-Object {$_.Name -CMatch "\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(\.inProgress)?"} ` - | Sort-Object -Descending CreationTime - ) - If ($PreviousBackups.Count -eq 0) { - If ($FailIfOldBackupsAreMissing) { - Throw [System.Management.Automation.ItemNotFoundException] "No previous backups were found. Exiting at user request." - } - Write-Verbose "No previous backups exist. Creating an initial backup." - - $DoVerboseCopy = $VerbosePreference -eq "Continue" - Copy-Item -Path (Join-Path -Path $SourcePath "*") -Destination (Join-Path -Path $DestinationPath $FolderName) -Recurse -Verbose:$DoVerboseCopy + If ($FailIfOldBackupsAreMissing) { + Throw [System.Management.Automation.ItemNotFoundException] "No previous backups were found. Exiting at user request." } - Else { - # Get the most recent backup name. - $PreviousBackup = $PreviousBackups[0].Name + Write-Verbose "Previous backups not found. Doing a copy-only backup." + $DoCopyOnlyBackup = $true + } - Get-ChildItem -Path "$SourcePath\" -Recurse -ErrorAction Stop | ForEach-Object { - $RelativeSourceItemPath = (($_.FullName) -Replace [regex]::Escape("$((Get-Item $SourcePath).FullName)"),"") + Get-ChildItem -Path "$SourcePath\" -Recurse -FollowSymLink | ForEach-Object { + $RelativeSourceItemPath = $_.FullName | Resolve-Path -Relative + Write-Debug "Analyzing $_" - # Create directories, but compare files. - If ($_.Attributes -CMatch "Directory") { - Write-Debug "Creating folder $(Join-Path -Path $DestinationPath $FolderName $RelativeSourceItemPath)" - New-Item -Type Directory -Path (Join-Path -Path $DestinationPath $FolderName $RelativeSourceItemPath) | Out-Null + # Don't copy the log file. + If ($_.Name -eq (($script:LogFile).Name)) { + Continue + } + + # Create directories, but compare files. + If ($_.Attributes -CMatch "Directory") { + Write-Debug "Creating folder $(Join-Path -Path $DestinationPath $FolderName $RelativeSourceItemPath)" + New-Item -Type Directory -Path (Join-Path -Path $DestinationPath $FolderName $RelativeSourceItemPath) | Out-Null + } + Else { + # Compare file sizes and dates to determine if something has changed. + If (-Not $DoCopyOnlyBackup) { + $PreviousCopyOfFile = Get-Item -Path (Join-Path -Path $DestinationPath $PreviousBackup $RelativeSourceItemPath) + If (($_.LastWriteTime -eq $PreviousCopyOfFile.LastWriteTime) -And ($_.Length -eq $PreviousCopyOfFile.Length)) { + # The file has not changed since the last backup. Create a hard link. + $DestinationHardlink = @{ + ItemType = "HardLink" + Target = ($PreviousCopyOfFile.FullName) + Path = ($PreviousCopyOfFile | Split-Path -Parent) -Replace [regex]::Escape($PreviousBackup),$FolderName + Name = ($PreviousCopyOfFile.Name) + } + Write-Verbose "Linking: $RelativeSourceItemPath" + New-Item @DestinationHardlink | Out-Null } Else { - # Compare file sizes and dates to determine if something has changed. - $PreviousCopyOfFile = Get-Item -Path (Join-Path -Path $DestinationPath $PreviousBackup $RelativeSourceItemPath) - If (($_.LastWriteTime -eq $PreviousCopyOfFile.LastWriteTime) -And ($_.Length -eq $PreviousCopyOfFile.Length)) { - # The file has not changed since the last backup. Create a hard link. - $DestinationHardlink = @{ - ItemType = "HardLink" - Target = ($PreviousCopyOfFile.FullName) - Path = ($PreviousCopyOfFile | Split-Path -Parent) -Replace [regex]::Escape($PreviousBackup),$FolderName - Name = ($PreviousCopyOfFile.Name) - ErrorAction = "Stop" - } - Write-Verbose "Linking: $RelativeSourceItemPath" - New-Item @DestinationHardlink | Out-Null - } - Else { - Write-Verbose "Copying: $RelativeSourceItemPath" - Copy-Item -Path $_.FullName -Destination (Join-Path -Path $DestinationPath $FolderName $RelativeSourceItemPath) -ErrorAction Stop - $bytesCopied += $_.Length - } - $bytesTotal += $_.Length + Write-Verbose "Copying: $RelativeSourceItemPath" + Copy-Item -Path $_.FullName -Destination (Join-Path -Path $DestinationPath $FolderName $RelativeSourceItemPath) -Verbose:$DoVerboseCopy + $bytesCopied += $_.Length } + } Else { + Write-Verbose "Copying: $RelativeSourceItemPath" + Copy-Item -Path $_.FullName -Destination (Join-Path -Path $DestinationPath $FolderName $RelativeSourceItemPath) -Verbose:$DoVerboseCopy + $bytesCopied += $_.Length } + $bytesTotal += $_.Length } } + Write-Output "Backup completed at $(Get-Date)." Rename-Item -Path (Join-Path -Path $DestinationPath $FolderName) -NewName ($FolderName -CReplace '\.inProgress') If (-Not $NoStatistics -And $bytesTotal -gt 0) { @@ -202,11 +211,12 @@ Catch { Finally { If (-Not $NoLogging) { Write-Verbose "Moving log file to backup destination." - Stop-Transcript -ErrorAction SilentlyContinue + Stop-Transcript -ErrorAction SilentlyContinue | Out-Null Write-Debug "Moving $($_.FullName) to $(Join-Path -Path $DestinationPath 'PSTimeMachine.log')" Move-Item -Path (($script:LogFile).Name) -Destination (Join-Path -Path $DestinationPath $FolderName "PSTimeMachine.log") -ErrorAction Continue } - # Restore the user's preferred error action preference. + # Restore the user's environment. $ErrorActionPreference = $OldErrorActionPreference -} + Set-Location -Path $OldLocation +} \ No newline at end of file