PSDeployScripts/FileSystemDiff.ps1

<#
    .SYNOPSIS
        Deploy using Copy-Item for folder and file deployments checking for code differences

    .DESCRIPTION
        Deploy files using Copy-Item, but first check if the code in the destination file has changed
        since the last deployment.

        Scenarios:
            1. File does not exist at destination: Copy from code control. Create a hash file.
            2. File exists at destination, but no hash file: Copy from code control. Create a hash file.
            3. File exists at destination, matching hash file: Copy from code control. Create a new hash file.
            4. File exists at destination, hash file does not match: two scenario's here:
                a. SaveDiff not set: Show warning that destination file is different. Overwrite from code control. Create new hash file.
                b. SaveDiff set: Show warning. Rename destination file. Copy from code control. Create new hash file.

    .PARAMETER Deployment
        Deployment to run

    .PARAMETER SaveDiff
        If a difference between the target file, and the target files saved hash is different that means
        the target file was changed outside of code control. Use this option to save the file before overwriting
        it with the proper file from code control.
#>

[CmdletBinding()]
Param (
    [ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PSDeploy.Deployment' })]
    [psobject[]]$Deployment,

    [switch]$SaveDiff
)

Write-Verbose "Starting local deployment with $($Deployment.count) sources"

#Local Deployment. Duplicate code. Sigh.
ForEach ($Map in $Deployment)
{
    If ($Map.SourceExists)
    {
        If ($Map.SourceType -eq "Directory")
        {
            $Files = Get-ChildItem $Map.Source -Recurse -File
            $Source = $Map.Source
        }
        Else 
        {
            $Files = @(Get-ItemProperty $Map.Source)
            $Source = Split-Path -Path $Map.Source
        }
        ForEach ($File in $Files)
        {
            ForEach ($Target in $Map.Targets)
            {
                #Checking for file differences from last deploy and file currently at target
                $OldFilePath = Join-Path -Path ($File.DirectoryName.ToLower().Replace($Source.ToLower(),$Target.ToLower())) -ChildPath $File.Name
                $OldHashPath = Join-Path -Path ($File.DirectoryName.ToLower().Replace($Source.ToLower(),$Target.ToLower())) -ChildPath "$($File.Name).hash"
                $OldFileParent = Split-Path -Path $OldFilePath

                If (-not (Test-Path $OldFileParent))
                {
                    $null = New-Item -Path $OldFileParent -ItemType Directory
                }
                ElseIf (Test-Path $OldHashPath)
                {
                    $OldHash = Import-Clixml -Path $OldHashPath
                    $NewHash = Get-FileHash -Path $OldFilePath
                    If ($OldHash.Hash -ne $NewHash.Hash)
                    {
                        Write-Warning "Difference was detected on target file $OldFilePath"
                        If ($SaveDiff)
                        {
                            $SaveDiffPath = Join-Path -Path (Split-Path -Path $OldFilePath) -ChildPath "$($File.BaseName)-$(Get-Date -Format 'MM-dd-yyyy-HH-mm-ss')$($File.Extension)"
                            Write-Verbose "Saving changed file as $SaveDiffPath"
                            Rename-Item -Path $OldFilePath -NewName "$($File.BaseName)-$(Get-Date -Format 'MM-dd-yyyy-HH-mm-ss')$($File.Extension)"
                        }
                    }
                }

                #Deploying the file
                Write-Verbose "Deploying file '$($File.Name)' to '$Target'"
                $HashFilePath = Join-Path -Path (Split-Path -Path $OldFilePath) -ChildPath "$($File.Name).hash"
                Copy-Item -Path $File.FullName -Destination $OldFilePath -Force -Confirm:$false
                
                $Hash = [PSCustomObject]@{
                    Hash = Get-FileHash -Path $File.FullName | Select -ExpandProperty Hash
                    File = $null #Get-Content $File.FullName #future proofing, not part of MVP
                }
                $Hash | Export-Clixml -Path $HashFilePath
            }
        }
    }
}