Functions/Copy-DscResource.ps1

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function Copy-DscResource
{
    <#
    .SYNOPSIS
    Copies DSC resources.
 
    .DESCRIPTION
    This function copies a DSC resource or a directory of DSC resources to a DSC pull server share/website. All files under `$Path` are copied.
     
    DSC requires all files have a checksum file (e.g. `localhost.mof.checksum`), which this function generates for you (in a temporary location).
     
    Only new files, or files whose checksums have changed, are copied. You can force all files to be copied with the `Force` switch.
 
    `Copy-DscResource` is new in Carbon 2.0.
 
    .EXAMPLE
    Copy-DscResource -Path 'localhost.mof' -Destination '\\dscserver\DscResources'
 
    Demonstrates how to copy a single resource to a resources SMB share. `localhost.mof` will only be copied if its checksum is different than what is in `\\dscserver\DscResources`.
 
    .EXAMPLE
    Copy-DscResource -Path 'C:\Projects\DscResources' -Destination '\\dscserver\DscResources'
 
    Demonstrates how to copy a directory of resources. Only files in the directory are copied. Every file in the source must have a `.checksum` file. Only files whose checksums are different between source and destination will be copied.
 
    .EXAMPLE
    Copy-DscResource -Path 'C:\Projects\DscResources' -Destination '\\dscserver\DscResources' -Recurse
 
    Demonstrates how to recursively copy files.
 
    .EXAMPLE
    Copy-DscResource -Path 'C:\Projects\DscResources' -Destination '\\dscserver\DscResources' -Force
 
    Demonstrates how to copy all files, even if their `.checksum` files are the same.
 
    .EXAMPLE
    Copy-DscResource -Path 'C:\Projects\DscResources' -Destination '\\dscserver\DscResources' -PassThru
 
    Demonstrates how to get `System.IO.FileInfo` objects for all resources copied to the destination. If all files are up-to-date, nothing is copied, and no objects are returned.
    #>

    [CmdletBinding()]
    [OutputType([IO.FileInfo])]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # The path to the DSC resource to copy. If a directory is given, all files in that directory are copied. Wildcards supported.
        $Path,

        [Parameter(Mandatory=$true)]
        [string]
        # The directory where the resources should be copied.
        $Destination,

        [Switch]
        # Recursively copy files from the source directory.
        $Recurse,

        [Switch]
        # Returns `IO.FileInfo` objects for each item copied to `Destination`.
        $PassThru,

        [Switch]
        # Copy resources, even if they are the same on the destination server.
        $Force
    )

    Set-StrictMode -Version 'Latest'

    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState

    $tempDir = New-TempDirectory -Prefix 'Carbon+Copy-DscResource+'

    try
    {
        foreach( $item in (Get-ChildItem -Path $Path -Exclude '*.checksum') )
        {
            $destinationPath = Join-Path -Path $Destination -ChildPath $item.Name
            if( $item.PSIsContainer )
            {
                if( $Recurse )
                {
                    if( -not (Test-Path -Path $destinationPath -PathType Container) )
                    {
                        New-Item -Path $destinationPath -ItemType 'Directory' | Out-Null
                    }
                    Copy-DscResource -Path $item.FullName -Destination $destinationPath -Recurse -Force:$Force -PassThru:$PassThru
                }
                continue
            }

            $sourceChecksumPath = '{0}.checksum' -f $item.Name
            $sourceChecksumPath = Join-Path -Path $tempDir -ChildPath $sourceChecksumPath
            $sourceChecksum = Get-FileHash -Path $item.FullName | Select-Object -ExpandProperty 'Hash'
            # hash files can't have any newline characters, so we can't use Set-Content
            [IO.File]::WriteAllText($sourceChecksumPath, $sourceChecksum)

            $destinationChecksum = ''

            $destinationChecksumPath = '{0}.checksum' -f $destinationPath
            if( (Test-Path -Path $destinationChecksumPath -PathType Leaf) )
            {
                $destinationChecksum = Get-Content -TotalCount 1 -Path $destinationChecksumPath
            }

            if( $Force -or -not (Test-Path -Path $destinationPath -PathType Leaf) -or ($sourceChecksum -ne $destinationChecksum) )
            {
                Copy-Item -Path $item -Destination $Destination -PassThru:$PassThru
                Copy-Item -Path $sourceChecksumPath -Destination $Destination -PassThru:$PassThru
            }
            else
            {
                Write-Verbose ('File ''{0}'' already up-to-date.' -f $destinationPath)
            }
        }
    }
    finally
    {
        Remove-Item -Path $tempDir -Recurse -Force -ErrorAction Ignore
    }
}