PSDependScripts/FileSystem.ps1

<#
    .SYNOPSIS
        EXPERIMENTAL: Use Robocopy or Copy-Item for folder and file dependencies, respectively.
 
    .DESCRIPTION
        EXPERIMENTAL: Use Robocopy or Copy-Item for folder and file dependencies, respectively.
 
        Runs in the current session (i.e. as the current user)
 
        Relevant Dependency metadata:
            DependencyName (Key): The key for this dependency is used as the URL. This can be overridden by 'Source'
            Name: Optional file name for the downloaded file. Defaults to parsing filename from the URL
            Target: The folder to copy the source to. If the source is a file, we adjust this in Robocopy to append the source folder name.
            Source: The source folder or file to copy
 
    .PARAMETER Dependency
        Dependency to run
 
    .PARAMETER PSDependAction
        Test, Install, or Import the module. Defaults to Install
 
        Test: Return true or false on whether the dependency is in place
              IMPORTANT: If a folder exists, return $True, whether the contents are the same or not
                         If a file exists, we check the hash
        Install: Install the dependency
        Import: Import the dependency 'Target'. Override with ImportPath
 
    .PARAMETER ImportPath
        If specified with PSDependAction Import, we import this path, instead of the target (or target parent if target is a file)
 
    .PARAMETER Force
        If specified, and target is a folder, overwrite the target
 
    .PARAMETER Mirror
        If specified and the target is a folder, we effectively call robocopy /MIR (Can remove folders/files...)
 
    .EXAMPLE
 
        @{
            'notepad' = @{
                DependencyType = 'FileSystem'
                Source = 'C:\windows\notepad.exe'
                Target = 'C:\PSDependPesterTest'
            }
        }
 
        # Copy C:\Windows\Notepad.exe to C:\PSDependPesterTest\notepad.exe
 
    .EXAMPLE
 
        @{
            'psams' = @{
                DependencyType = 'FileSystem'
                Source = '\\FileServer\powershell\modules\psams'
                Target = 'C:\ProjectX'
            }
        }
 
        # Copy psams module to C:\ProjectX\psams
 
#>

[cmdletbinding()]
param (
    [PSTypeName('PSDepend.Dependency')]
    [psobject[]]
    $Dependency,

    [ValidateSet('Test', 'Install', 'Import')]
    [string[]]$PSDependAction = @('Install'),

    [string]$ImportPath
)

# Extract data from Dependency
$DependencyName = $Dependency.DependencyName
$Name = $Dependency.Name
$Target = $Dependency.Target
$Sources = @($Dependency.Source)

$TestOutput = @()
foreach ($Source in @($Sources)) {
    if (-not (Test-Path $Source)) {
        if (-not $PSDependAction -like 'Test') {
            Write-Error "Skipping $DependencyName, could not find source [$Sources] due to error:"
        }
        continue
    }
    $IsContainer = ( Get-Item $Source ).PSIsContainer
    
    # Resolve PSDrives.
    $Target = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Target)
    $Source = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Source)

    if ($IsContainer) {
        $Folder = Split-Path $Source -Leaf
        $Target = Join-Path $Target $Folder
        if (-not $ImportPath) { $ImportPath = $Target }
        # We don't test equality for containers yet
        if (Test-Path $Target) {
            $TestOutput += $true
        }
        else {
            $TestOutput += $false
        }
        if ($PSDependAction -like 'Install') {
            # TODO: Add non Windows equivalent...
            [string[]]$Arguments = "/XO"
            $Arguments += "/E"
            if ($Dependency.Parameters.Mirror -eq $True -or $Mirror) {
                $Arguments += "/PURGE"
            }

            Write-Verbose "Invoking ROBOCOPY.exe $Source $Target $Arguments"
            ROBOCOPY.exe $Source $Target @Arguments
        }
    }
    else {
        $SourceFolderPath = Split-Path $Source -Parent
        $SourceFileName = Split-Path $Source -Leaf
        $TargetFile = Join-Path $Target $SourceFileName
        $SourceHash = ( Get-Hash $Source ).SHA256
        $TargetHash = $null
        if (Test-Path $Target -PathType Leaf) {
            $TargetHash = ( Get-Hash $Target -ErrorAction SilentlyContinue -WarningAction SilentlyContinue ).SHA256
            $TargetPath = $Target
        }
        elseif (Test-Path $TargetFile -PathType Leaf) {
            $TargetHash = ( Get-Hash $TargetFile -ErrorAction SilentlyContinue -WarningAction SilentlyContinue ).SHA256
            $TargetPath = $TargetFile
        }
        Write-Verbose "Source [$Source] hash [$SourceHash]`n`tTarget [$TargetPath] hash [$TargetHash]"

        if ($TargetHash -ne $SourceHash) {
            Write-Verbose "Hashes do not match"
            if ($PSDependAction -like 'Install') {
                Write-Verbose "Copying file [$Source] to [$Target]"
                Copy-Item -Path $Source -Destination $Target -Force
            }
            if ($PSDependAction -like 'Test' -and $PSDependAction.count -eq 1) {
                $TestOutput += $false
            }
        }
        else {
            Write-Verbose "Matching hash: [$Source] = [$TargetFile]"
            if ($PSDependAction -like 'Test' -and $PSDependAction.count -eq 1) {
                $TestOutput += $True
            }
        }
    }
}

if ($PSDependAction -like 'Test' -and $PSDependAction.count -eq 1) {
    if (@($TestOutput) -contains $false -or @($TestOutput) -notcontains $true) {
        return $false
    }
    else {
        return $True
    }
}

if ($PSDependAction -like 'Import') {
    if (-not $ImportPath) {
        if (Test-Path $Target -PathType Leaf) {
            $ImportPath = Split-Path $Target -Parent
        }
        elseif (Test-Path $Target -PathType Container) {
            $ImportPath = $Target
        }
        else {
            Write-Error "Could not import target [$Target], path not found"
            return
        }
    }
    Import-PSDependModule $ImportPath
}