Snippets

SeanB Backup to AWS S3

Created by SeanB
#$DebugPreference = "Continue"

import-module "C:\Program Files (x86)\AWS Tools\PowerShell\AWSPowerShell\AWSPowerShell.psd1"
add-type -assemblyName "System.Web"

$AccessKeyId = (get-item env:aws-access-key-id -erroraction SilentlyContinue).value 
$AccessKeySecret = (get-item env:aws-access-key-secret -erroraction SilentlyContinue).value 

if ( !($AccessKeyId) -or !($AccessKeySecret)) {
	$ok = ($AccessKeyId) -or ($AccessKeySecret)
	Write-Host "AWS Access keys have not been created."
	Write-Host "==================================================================="
	Write-Host "To run this script you must create two environment variables called"
	Write-Host "aws-access-key-id and aws-access-key-secret"
	Write-Host "--"
	break
} 

# Regions: us-east-1, us-west-2, us-west-1, eu-west-1, ap-southeast-1
$region = "eu-west-1"   # Regions: us-east-1, us-west-2, us-west-1, eu-west-1, ap-southeast-1

$blocksize = (1024*1024*5)
$startblocks = (1024*1024*16)
$FilesList = @()

$md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider

Function CreateShadowCopy($drive) {
	Write-Debug("Create a shadow copy for $drive")
	$s1 = (gwmi -list win32_shadowcopy).Create($drive,"ClientAccessible")
	$s2 = $s1.ShadowID

	Write-Debug("Created Shadow Copy $s2")
	return $s2
}

function DeleteShadowCopy($shadow) {
	$shadow_obj = (gwmi Win32_ShadowCopy) | ? { $_.ID -eq $shadow }
	
	$newId = $shadow_obj.ID
	Write-Debug("deleting $newID")
	$shadow_obj.Delete()
}

function ShadowFolder($shadow) {
	$shadow_obj = (gwmi Win32_ShadowCopy) | ? { $_.ID -eq $shadow }
	$path = $shadow_obj.DeviceObject
	return $path 
}


function AmazonEtagHashForFile($filename) {
    $lines = 0
    [byte[]] $binHash = @()

    $reader = [System.IO.File]::Open($filename,"OPEN","READ")
	
	if ((Get-Item $filename).length -gt $startblocks) {
		$buf = new-object byte[] $blocksize
		while (($read_len = $reader.Read($buf,0,$buf.length)) -ne 0){
			$lines   += 1
			$binHash += $md5.ComputeHash($buf,0,$read_len)
		}
		$binHash=$md5.ComputeHash( $binHash )
	}
	else {
	    $lines   = 1
		$binHash += $md5.ComputeHash($reader)
	}

    $reader.Close()
    
	$hash = [System.BitConverter]::ToString( $binHash )
    $hash = $hash.Replace("-","").ToLower()

    if ($lines -gt 1) {
        $hash = $hash + "-$lines"
    }

    return $hash
}

function FindInCachedFileList($tofile) {
	Foreach ($item in $FilesList) {
	    if ($item.key -eq $tofile) {
			return $item
	    }
	}
}


function s3BackupFile($file, $folder, $BucketID) {
	if (!$file.PSIsContainer) {
		
		$fromFile = $file.fullname.ToLower()
		$filename = $file.fullname.ToLower()
		
		$filename = $filename.replace("c:\shadowcopy\" , $folder)
        $filename = $filename.replace("\" , "/")
        $filename = [System.Web.HttpUtility]::UrlEncode($filename) 
        $filename = $filename.replace("%2f" , "/")
		
		$s3file = FindInCachedFileList($filename)
		if ($s3file -eq $null) {
			Write-Host($fromFile + " -> $filename (not found)" )
			Write-S3Object -BucketName $BucketID -File $fromfile -Key $filename
			return 
		}
		
		$hash = AmazonEtagHashForFile($file.fullname)
		$etag =$s3file.etag.Replace('"',"")

		if ($etag -ne $hash) {
		    Write-Debug("found file. etag differs")
			Write-Host("$fromFile ($hash)  -> $filename ($etag)" )
			Write-S3Object -BucketName $BucketID -File $fromfile -Key $filename
		}
	}
}

function s3_filelist($folder, $BucketID) {
	$marker = ""
	$maxkeys = 1000
	$files = @()

	do {
		$s3files = (Get-S3Object -Marker $marker -MaxKeys $maxkeys -BucketName $BucketID -KeyPrefix $folder )
		$s3files | Foreach-Object{
			$s3Hash = @{"key" = $_.key; "etag" = $_.etag}
			$files += $s3Hash 
			$marker = $_.key
		}
	} while ($s3files.length -eq $maxkeys)

	return $files
}


function folder_backup($BucketID, $BackupTo, $BackupFrom, $BackupDrive) {
	Write-Host "Starting the backup from $BackupFrom" 
	Write-Host "============================================="

	Write-Host("Backing up to S3")
	if (Test-Path "c:\shadowcopy") {
	    Write-Host("Currently running a backup. If we aren't then remove folder c:\shadowcopy")
		break
	}


	Set-AWSCredentials -AccessKey $AccessKeyId -SecretKey $AccessKeySecret
	Set-DefaultAWSRegion $region
	$day = ([string](get-date).dayofweek).ToLower()
	$snapshot_folder = $BackupTo + "/" + $day + "/" 

	Write-Host("Writing to folder $snapshot_folder")
	Write-Debug $BucketID
	$FilesList =  (s3_filelist $snapshot_folder $BucketID)

	Write-Host("Creating a shadow copy")
	$shadow=CreateShadowCopy($BackupDrive)

	$folder = ShadowFolder($shadow)
	$folder = $folder + $BackupFrom + "\"

	Write-Host("Reading from folder $folder")
	Write-Debug("====================================================")

	cmd /c mklink /d c:\shadowcopy "$folder"

	Write-Host("Removing items on S3 that no longer exist...")
	$FilesList | Foreach-Object{
		if ($_.key) {
			$LocalFile =  $_.key.replace($snapshot_folder,"c:\shadowcopy\")
			$LocalFile = $LocalFile.replace("/" , "\")
			$LocalFile = [System.Web.HttpUtility]::UrlDecode($LocalFile) 
			
			if (!(Test-Path $LocalFile)) {
				$DeleteAmazonFile = $_.key
				Write-Host ("Deleting... $DeleteAmazonFile from $LocalFile")
				$response = Remove-S3Object -BucketName $BucketID -Key $DeleteAmazonFile -Force
			}
		}
	}

	$files = Get-ChildItem "C:\shadowcopy" -Recurse
	$files | Foreach-Object{
	    s3BackupFile $_ $snapshot_folder $BucketID
	}

	Write-Host("Removing shadow copy")
	cmd /c rmdir c:\shadowcopy
	DeleteShadowCopy($shadow)
	Write-Host("Finished")



}

folder_backup "Selectcs-Common"	"REPORTS"			"\VSMS8\REPORTS"		"D:\"
folder_backup "Selectcs-Common"	"COMMONFILES"		"\VSMS8\COMMONFILES"    "D:\"
folder_backup "Selectcs-Common"	"PROGS"				"\VSMS8\SMS7\PROGS"     "D:\"
folder_backup "Selectcs-Common"	"BITMAPS"			"\VSMS8\BITMAPS"        "D:\"
folder_backup "Selectcs-Common"	"PROGRAMS"			"\VSMS8\PROGRAMS"       "D:\"
folder_backup "Selectcs-Common"	"SCRIPTS"			"\SCRIPTS"              "D:\"


#folder_backup "selectcs-backup" "sean-scripts-2" "\scripts" "e:\"


Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.