Website via FTP with removal

PeterGPeterG Posts: 61 Silver 3
Here's a script for uploading a website via ftp, then deleting the old files.

Some notes on using this:
  • The website will go down for a very short period between renaming the current directory and renaming the new directory
  • If the upload fails then the current website is untouched, but the new temporary directory remains on the FTP server
#Inputs:
#$ftpDomain = MyDomain.com
#$ftpUserName = User01
#$ftpPassword = ********

# Uploads the contents of the deployed package to the given ftp site, then delete the old files

# This is done by uploading to a temporary file
# checking the upload worked
# and then swap the directories to `deploy` the website
# the finally delete the old wwwroot

$errors = ""
if ($null -eq $ftpDomain) {
	$errors = $errors + ", ftpDomain"
}
if ($null -eq $ftpUserName) {
	$errors = $errors + ", ftpUserName"
}
if ($null -eq $ftpPassword) {
	$errors = $errors + ", ftpPassword"
}

if (0 -ne $errors.length) {
# Throw an error and stop deployment
	Throw ($errors.trim(", ") + " must be set to push to ftp site")
}

$webrootDir = "wwwroot"
$guid1 = [guid]::NewGuid()
$guid2 = [guid]::NewGuid()
$ftpUrl = "ftp://$ftpDomain/$webrootDir"
$ftpTemp1 = "ftp://$ftpDomain/$guid1"
$ftpTemp2 = "ftp://$ftpDomain/$guid2"

# Upload the files
Function FtpUpload ([string]$webroot, [string]$localdirectory)
{
	cd $localdirectory
	$files = Get-ChildItem -recurse

	FTPCreateFolder (New-Object System.Uri($webroot)) 

	foreach($file in $files)
	{
		#Ignore the nuget package used for deployment
		if ($file.Name -eq "$RedGatePackageNameAndVersion.nupkg") {
			continue
		}
	
    	$uri = New-Object System.Uri($webroot + $file.FullName.Replace($localdirectory,""))
		
    	# if a folder
    	if( $file.DirectoryName -eq $null)
    	{
	        Write-Host "Create folder, URI: $uri"
        	FtpCreateFolder $uri
    	}
    	else
    	{
        	Write-Host "Upload file, URI: $uri, File: " + $file.FullName
        	FtpUploadFile $uri $file.FullName
    	}
	}
}

Function FtpUploadFile ([System.Uri]$uri, [string]$filePath)
{
    $ftp = [System.Net.FtpWebRequest]::Create($uri)
    $ftp.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
    $ftp.Credentials = new-object System.Net.NetworkCredential($ftpUserName,$ftpPassword)
    $ftp.UseBinary = $true
    $ftp.UsePassive = $true
    $content = [System.IO.File]::ReadAllBytes($filePath)
    $ftp.ContentLength = $content.Length
    $rs = $ftp.GetRequestStream()
    $rs.Write($content, 0, $content.Length)
    $rs.Close()
    $rs.Dispose()
}

Function FtpCreateFolder ([System.Uri]$uri)
{
    $ftp = [System.Net.FtpWebRequest]::Create($uri)
    $ftp.Method = [System.Net.WebRequestMethods+Ftp]::MakeDirectory
    $ftp.Credentials = new-object System.Net.NetworkCredential($ftpUserName,$ftpPassword)
    try
    {
        $rs = $ftp.GetResponse()
    }
    catch [System.Exception]
    {
    }
    if( $rs -ne $null )
    {
        $rs.Close()
    }
}

# Reads the entire stream in one go and return it
# instead of reading stream line by line
Function Receive-Stream ([System.IO.Stream]$reader)
{
	$encoding = [System.Text.Encoding]::GetEncoding( $null )
	$output = ""
	[byte[]]$buffer = new-object byte[] 4096
	[int]$total = [int]$count = 0
	do
	{
		$count = $reader.Read($buffer, 0, $buffer.Length)
		$output += $encoding.GetString($buffer, 0, $count)
	} while ($count -gt 0)
 
	$reader.Close()
	$output
}

# Delete the directory $path
# Start by recursively deleting files inside
# then remove the directory itself
Function FtpDelete ([string]$path)
{
	$itemsOnFTPtoDelete = New-Object System.Collections.ArrayList
	
	$uri = New-Object System.Uri($path)
    $ftp = [System.Net.FtpWebRequest]::Create($uri)
    $ftp.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
    $ftp.Credentials = new-object System.Net.NetworkCredential($ftpUserName,$ftpPassword)
	
	try
	{
		$rs = $ftp.GetResponse()
		$cache = (Receive-Stream $rs.GetResponseStream())
		
		# Split up the cache into a useable format with an item on each line
		$list = $cache -replace "(?:.|\n)*<PRE>\s+((?:.*|\n)+)\s+</PRE>(?:.|\n)*",'$1' -split "`n"

		# Convert the list of items into a useable format
		foreach($line in $list) {
			$null, $null, [string]$length, $name = $line -split '\s+'
			$name = ($name -join " ")
			
			if ($name.Length -eq 0) {
				continue
			}

			$ftpItem = New-Object PSObject -Property @{
				FullName      = $path.trim().trim('/') + '/' + $name
				Type          = $(if($length -eq (($length -as [int]) -as [string])) { "File" } else { $length })
			}
		
			[void]$itemsOnFTPtoDelete.Add($ftpItem)
   		}

		$rs.Close()
		
		# Delete each file, recursively deleting directories
		foreach ($item in $itemsOnFTPtoDelete) {
			$uri = New-Object System.Uri($item.FullName)
			if ("<DIR>" -ne $item.Type) {
				FtpDeleteFile $uri
			} else {
				FtpDelete $item.FullName
			}
		}
		FtpDeleteEmptyDirectory (New-Object System.Uri($path))
	}
	catch
	{
	}
}

Function FtpDeleteFile ([System.Uri]$uri)
{
	Write-Host "Deleting $uri"
    $ftp = [System.Net.FtpWebRequest]::Create($uri)
    $ftp.Method = [System.Net.WebRequestMethods+Ftp]::DeleteFile
    $ftp.Credentials = new-object System.Net.NetworkCredential($ftpUserName,$frpPassword)
	
    try
    {
        $rs = $ftp.GetResponse()
    }
    catch [System.Exception]
    {
    }
    if( $rs -ne $null )
    {
        $rs.Close()
    }
}

Function FtpDeleteEmptyDirectory ([System.Uri]$uri)
{
	Write-Host "Deleting directory $uri"
    $ftp = [System.Net.FtpWebRequest]::Create($uri)
    $ftp = [System.Net.FtpWebRequest]$ftp
    $ftp.Method = [System.Net.WebRequestMethods+Ftp]::RemoveDirectory
    $ftp.Credentials = new-object System.Net.NetworkCredential($ftpUserName,$ftpPassword)
	
    try
    {
        $rs = $ftp.GetResponse()
    }
    catch [System.Exception]
    {
    }
    if( $rs -ne $null )
    {
        $rs.Close()
    }
}

Function FtpRename ([System.Uri]$uri, [string]$newname)
{
	Write-Host "Renaming $uri to $newname"
    $ftp = [System.Net.FtpWebRequest]::Create($uri)
    $ftp.Method = [System.Net.WebRequestMethods+Ftp]::Rename
    $ftp.Credentials = new-object System.Net.NetworkCredential($ftpUserName,$ftpPassword)
	$ftp.RenameTo = $newname
	
    try
    {
        $rs = $ftp.GetResponse()
    }
    catch [System.Exception]
    {
    }
    if( $rs -ne $null )
    {
        $rs.Close()
    }
}

# Checks the files in $webroot versus $localdirectory
# recursively called for directories
Function FtpValidate ([string]$webroot, [string]$localdirectory)
{
	Write-Host "Validating directory $localdirectory was uploaded"
	$itemsOnFTP = New-Object System.Collections.ArrayList
	cd $localdirectory
	$uploadedFiles = Get-ChildItem
	
	$uri = New-Object System.Uri($webroot)
    $ftp = [System.Net.FtpWebRequest]::Create($uri)
    $ftp.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
    $ftp.Credentials = new-object System.Net.NetworkCredential($ftpUserName,$ftpPassword)
	
	try
	{
		# Get the items stored on the ftp at the webroot location
		$rs = $ftp.GetResponse()
		$cache = (Receive-Stream $rs.GetResponseStream())
		
		$list = $cache -replace "(?:.|\n)*<PRE>\s+((?:.*|\n)+)\s+</PRE>(?:.|\n)*",'$1' -split "`n"

		foreach($line in $list) {
			$null, $null, [string]$length, $name = $line -split '\s+'
			$name = ($name -join " ").Trim()
			
			if ($name.Length -eq 0) {
				continue
			}

			$ftpItem = New-Object PSObject -Property @{
				Name		  = $name
				FullName      = $webroot.trim().trim('/') + '/' + $name
				Length        = $(if($length -eq (($length -as [int]) -as [string])) { [int]$length } else { $null })
				Type          = $(if($length -eq (($length -as [int]) -as [string])) { "File" } else { $length })
			}
			
			[void]$itemsOnFTP.Add($ftpItem)
   		}

		$rs.Close()
	} catch {}
	foreach ($file in $uploadedFiles) {
		# ignore the package used for deployment
		if ($file.Name -eq "$RedGatePackageNameAndVersion.nupkg") {
			continue
		}
		# Check each item has been uploaded correctly
		
		Write-Host "Validating item: $file"
		
		$found = $null
		foreach ($ftpItem in $itemsOnFTP) {
			if ($ftpItem.Name -eq $file.Name) { $found = $ftpItem }
		}
		# If it doesn't exist on ftp then error
		if ($found -eq $null) {
			throw "$file failed to uploaded"
		}
		
    	# if a folder
    	if( $file.DirectoryName -eq $null)
    	{
			# check the internals are the same
	        FtpValidate $found.FullName $file.FullName
    	} else {
			# check the file lengths are equal
			if ($found.Length -ne $file.Length) {
				throw "$file was not uploaded correctly"
			}
		}
	}
}

$myDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Write-Host "Uploading new files"
FtpUpload $ftpTemp1 $myDir
Write-Host "Validating upload"
FtpValidate $ftpTemp1 $myDir
Write-Host "Validation Successful"
Write-Host "Swapping to new website"
FtpRename (New-Object System.Uri($ftpUrl)) $guid2
FtpRename (New-Object System.Uri($ftpTemp1)) $webrootDir
Write-Host "Delete old files"
FtpDelete $ftpTemp2

Peter Gerrard

Software Engineer
Redgate Software
Sign In or Register to comment.