Skip to content

Instantly share code, notes, and snippets.

@YuriyGuts
Created November 3, 2013 19:52
Show Gist options
  • Save YuriyGuts/7294085 to your computer and use it in GitHub Desktop.
Save YuriyGuts/7294085 to your computer and use it in GitHub Desktop.
A PowerShell script that dumps the entire file tree of the specified folder(s) and saves the results to a ZIP archive.
<#
.SYNOPSIS
Dumps the entire file tree of the specified folder(s) and saves the results to a ZIP archive.
.PARAMETER SourceFolderList
Path to the folder(s) to retrieve the file tree for. Multiple folders should be separated by the pipe character ("|").
.PARAMETER BackupStorageList
Path to the folder(s) where the ZIP file will be stored. Multiple folders should be separated by the pipe character ("|").
.PARAMETER BackupName
Name of the backup (will be included in the ZIP file name).
.PARAMETER CombineIntoSingleArchive
Optional switch. When used with multiple source folders, specifies that the output files should be put in a single archive instead of separate ones.
.PARAMETER SuppressAutogeneratedName
Optional switch. If specified, auto-generated parts will not be appended to the backup file name.
.EXAMPLE
Dump-FileTree -SourceFolder "D:\My Projects" -BackupStorage "E:\Backup" -BackupName "Projects"
.EXAMPLE
Dump-FileTree -SourceFolderList "D:\Music|E:\Video" -BackupStorageList "E:\Backup|Y:\Backup|Z:\Backup" -BackupName "Media Library" -CombineIntoSingleArchive -SuppressAutogeneratedName
.EXAMPLE
Dump-FileTree -src "D:\Music|E:\Video" -dest "E:\Backup|Y:\Backup|Z:\Backup" -name "Media Library" -combine -suppressautoname
#>
param
(
[Parameter(Mandatory = $true, HelpMessage="Path to the folder(s) to retrieve the file tree for. Multiple folders should be separated by the pipe character (`"|`").")]
[Alias("src")]
[Alias("SourceFolder")]
[string]
$SourceFolderList,
[Parameter(Mandatory = $true, HelpMessage="Path to the folder(s) where the ZIP file will be stored. Multiple folders should be separated by the pipe character (`"|`").")]
[Alias("dest")]
[Alias("BackupFolder")]
[Alias("BackupFolderList")]
[Alias("BackupStorage")]
[string]
$BackupStorageList,
[Parameter(Mandatory = $false, HelpMessage="Name of the backup (will be included in the ZIP file name).")]
[Alias("name")]
[string]
$BackupName = $env:COMPUTERNAME,
[Parameter(Mandatory = $false, HelpMessage="Optional switch. When used with multiple source folders, specifies that the output files should be put in a single archive instead of separate ones.")]
[Alias("combine")]
[Switch]
$CombineIntoSingleArchive,
[Parameter(Mandatory = $false, HelpMessage="Optional switch. If specified, auto-generated parts will not be appended to the backup file name.")]
[Alias("suppressautoname")]
[Switch]
$SuppressAutoGeneratedName
)
function Dump-DirectoryTree([string]$rootPath)
{
Get-ChildItem -Path $rootPath -Directory -Force -Recurse -ErrorAction SilentlyContinue |
Sort-Object FullName |
&{
process
{
"{0:yyyy-MM-dd HH:mm} {1}" -f $_.CreationTime, $_.FullName
}
}
}
function Dump-FileTree([string]$rootPath)
{
Get-ChildItem -Path $rootPath -Force -Recurse -ErrorAction SilentlyContinue |
Sort-Object FullName |
&{
begin
{
$fileCount = 0
$directoryCount = 0
$overallSize = 0
}
process
{
$fileSizeColumnText = " " * 10 + "<DIR>"
if ($_.PsIsContainer)
{
$directoryCount++
}
else
{
$fileCount++
$overallSize += $_.Length
$fileSizeColumnText = ("{0,15:N0}" -f $_.Length)
}
"{0} {1:yyyy-MM-dd HH:mm} {2}" -f $fileSizeColumnText, $_.LastAccessTime, $_.FullName
}
end
{
""
"Total: {0:N0} files, {1:N0} directories, {2:N0} bytes" -f $fileCount, $directoryCount, $overallSize
""
}
}
}
function Dump-DirectoryAndFileTree([string]$rootPath)
{
$directoryTree = Dump-DirectoryTree -rootPath $rootPath
$fileTree = Dump-FileTree -rootPath $rootPath
$separator = "-" * 50
$separator
"Directory tree of $rootPath"
$separator
$directoryTree
""
$separator
"File tree of $rootPath"
$separator
$fileTree
}
function Sanitize-FileName([string]$fileName)
{
$invalidChars = [IO.Path]::GetInvalidFileNameChars() + [IO.Path]::GetInvalidPathChars();
[Text.StringBuilder]$stringBuilder = New-Object System.Text.StringBuilder($fileName)
foreach ($char in $invalidChars)
{
$stringBuilder = $stringBuilder.Replace($char, "_")
}
$result = $stringBuilder.ToString().Trim(@(' ', '_'))
return $result
}
function Get-DumpFileName([string]$baseName, [bool]$useAutoGeneratedName)
{
if ($useAutoGeneratedName)
{
return "{0} File Tree Backup {1:yyyy-MM-dd} T{1:HHmmss}.txt" -f $baseName, (Get-Date)
}
else
{
return $baseName
}
}
function Get-DumpZipFileName([string]$dumpFileName)
{
# Replace any extension with .zip
$dotIndex = $dumpFileName.LastIndexOf(".");
if ($dotIndex -ge 0)
{
return $dumpFileName.Substring(0, $dotIndex) + ".zip"
}
else
{
return $dumpFileName + ".zip"
}
}
function Add-ZipArchive([string]$sourceFile, [string]$zipFile)
{
if (-not (Test-Path $zipFile))
{
Set-Content $zipFile ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
(Get-ChildItem $zipFile).IsReadOnly = $false
}
$shellApplication = New-Object -COMObject "Shell.Application"
$zipPackage = $shellApplication.NameSpace($zipFile)
# Wait for the shell to compress the file.
$fileCountBefore = $shellApplication.NameSpace($zipPackage).Items().Count
$zipPackage.CopyHere($sourceFile)
$fileCountAfter = $fileCountBefore
while ($fileCountAfter -eq $fileCountBefore)
{
Start-Sleep -Milliseconds 250
$fileCountAfter = $shellApplication.NameSpace($zipPackage).Items().Count
}
Start-Sleep -Milliseconds 2000
}
function Get-FixedDrives()
{
return [IO.DriveInfo]::GetDrives() | Where { $_.DriveType -eq "Fixed" }
}
function Do-BackupJob
{
param
(
[string]$sourceDirectory,
[string]$backupStorage,
[string]$backupName,
[string]$backupZipFileName,
[bool]$useAutoGeneratedName
)
Write-Host "Processing $sourceDirectory... " -NoNewline
if ([String]::IsNullOrEmpty($sourceDirectory) -or (-not (Test-Path $sourceDirectory)))
{
$sourceDirectory = (Get-Location).Path
}
if ([String]::IsNullOrEmpty($backupStorage) -or (-not (Test-Path $backupStorage)))
{
$backupStorage = (Get-Location).Path
}
$dumpFileName = ([IO.Path]::Combine($backupStorage, (Get-DumpFileName -baseName $backupName -useAutoGeneratedName $useAutoGeneratedName)))
Dump-DirectoryAndFileTree -rootPath $sourceDirectory > $dumpFileName
$zipFileName = $backupZipFileName
if ([String]::IsNullOrEmpty($backupZipFileName))
{
$zipFileName = Get-DumpZipFileName([string]$dumpFileName)
}
Add-ZipArchive -sourceFile $dumpFileName -zipFile $zipFileName
Remove-Item $dumpFileName
Write-Host "Done"
}
# ---------- Entry point ----------
Write-Host "File tree dump in progress"
$startTime = Get-Date
$sourceFolders = $SourceFolderList.Split("|")
$backupLocations = $BackupStorageList.Split("|")
$resultZipFiles = @()
$backupZipFileName = $null
if ($CombineIntoSingleArchive)
{
$backupZipFileName = (Get-DumpZipFileName ([IO.Path]::Combine($backupLocations[0], (Get-DumpFileName -baseName (Sanitize-FileName $backupName) -useAutoGeneratedName (-not $SuppressAutoGeneratedName)))))
$resultZipFiles += $backupZipFileName
}
foreach ($sourceDir in $sourceFolders)
{
if (-not $CombineIntoSingleArchive)
{
if ($sourceFolders.Length -gt 1)
{
$baseName = $BackupName + " " + $sourceDir
}
else
{
$baseName = $BackupName
}
$backupZipFileName = (Get-DumpZipFileName ([IO.Path]::Combine($backupLocations[0], (Get-DumpFileName -baseName (Sanitize-FileName $baseName) -useAutoGeneratedName (-not $SuppressAutoGeneratedName)))))
$resultZipFiles += $backupZipFileName
}
Do-BackupJob -sourceDirectory $sourceDir -backupStorage $backupLocations[0] -backupName (Sanitize-FileName $sourceDir) -backupZipFileName $backupZipFileName -useAutoGeneratedName (-not $SuppressAutoGeneratedName)
}
for ($i = 1; $i -lt $backupLocations.Length; $i++)
{
foreach ($zipFile in $resultZipFiles)
{
Copy-Item -Path $zipFile -Destination $backupLocations[$i] -Force
}
}
$endTime = Get-Date
$timeElapsed = ($endTime - $startTime).TotalSeconds
Write-Host ("Completed in {0:N1} seconds" -f $timeElapsed)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment