PSDeployScripts/FileSystem.ps1

<#
    .SYNOPSIS
        Deploy using Robocopy or Copy-Item for folder and file deployments, respectively.

    .DESCRIPTION
        Deploy using Robocopy or Copy-Item for folder and file deployments, respectively.

        Runs in the current session (i.e. as the current user)

    .PARAMETER Deployment
        Deployment to run

    .PARAMETER Mirror
        If specified and the source is a folder, we effectively call robocopy /MIR (Can remove folders/files...)
    
    .PARAMETER IncludeOlder
        If specified and the source is a folder, we effectively call robocopy without /XO (Exclude Older Files)
#>

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

    [switch]$Mirror,
    
    [switch]$IncludeOlder
)

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

#Local Deployment. Duplicate code. Sigh.
foreach($Map in $Deployment)
{
    if($Map.SourceExists)
    {
        $Targets = $Map.Targets
        foreach($Target in $Targets)
        {
            if($Map.SourceType -eq 'Directory')
            {
                if($null -ne (Get-Command -Name Robocopy -ErrorAction SilentlyContinue)){
                    if($Map.DeploymentOptions.includeolder -eq 'True' -or $IncludeOlder)
                    {
                        [string[]]$Arguments = "/E"
                    } else {
                        [string[]]$Arguments = "/XO"
                        $Arguments += "/E"
                    }
                    if($Map.DeploymentOptions.mirror -eq 'True' -or $Mirror)
                    {
                        [string[]]$Arguments = "/E"
                        $Arguments += "/PURGE"
                    }
                    # Resolve PSDrives.
                    $Target = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Target)

                    Write-Verbose "Invoking ROBOCOPY.exe $($Map.Source) $Target $Arguments"
                    Invoke-Robocopy -Path $Map.Source -Destination $Target -ArgumentList $Arguments
                }
                #TODO: If the platform is Unix, try to use rsync to perform this action
                # elseif(($PSVersionTable.Platform -eq 'Unix') -and ($null -ne (Get-Command -Name 'rsync' -ErrorAction SilentlyContinue))){}
                else{
                    #PowerShell Fall Back
                    # Write-Warning -Message 'Robocopy or rsync could not be found. Falling back to basic PowerShell based file copy'
                    Write-Warning -Message 'Robocopy could not be found. Falling back to basic PowerShell based file copy'
                    Try {
                        if(($Map.DeploymentOptions.mirror -eq 'True' -or $Mirror) -and (Test-Path -PathType Container -Path $Target)){
                            Get-ChildItem -Path $Target -Force | Remove-Item -Force -Recurse
                        }
                        if(!(Test-Path -PathType Container -Path $Target)){
                            New-Item -ItemType Directory -Path $Target -Force
                        }
                        Get-ChildItem -Path $Map.Source | Copy-Item -Destination $Target -Recurse
                    }
                    catch{
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                }
            }
            else
            {
                $SourceHash = ( Get-Hash $Map.Source ).SHA256
                $TargetHash = ( Get-Hash $Target -ErrorAction SilentlyContinue -WarningAction SilentlyContinue ).SHA256
                if($SourceHash -ne $TargetHash)
                {
                    Write-Verbose "Deploying file '$($Map.Source)' to '$Target'"
                    Try {
                        Copy-Item -Path $Map.Source -Destination $Target -Force
                    }
                    Catch [System.IO.IOException],[System.IO.DirectoryNotFoundException] {
                        $NewDir = $Target
                        if ($NewDir[-1] -ne '\')
                        {
                            $NewDir = Split-Path -Path $NewDir
                        }
                        $null = New-Item -ItemType Directory -Path $NewDir
                        Copy-Item -Path $Map.Source -Destination $Target -Force
                    }
                }
                else
                {
                    Write-Verbose "Skipping deployment with matching hash: '$($Map.Source)' = '$Target')"
                }
            }
        }
    }
}