Commands/Copy-OpenPackage.ps1

function Copy-OpenPackage
{
    <#
    .SYNOPSIS
        Copies Open Packages
    .DESCRIPTION
        Copies Contents from one packages to another.
    .EXAMPLE
        Copy-OpenPackage -DestinationPath ./Examples/Copy.docx -InputObject ./Examples/Sample.docx -Force
    .LINK
        Get-OpenPackage
    .LINK
        Select-OpenPackage
    #>

    [Alias('Copy-OP','cpop','cpOpenPackage')]
    param(
    # The destination.
    # If this is not a `[IO.Packaging.Package]`, it will be considered a file path.
    [Parameter(ValueFromPipelineByPropertyName)]    
    [Alias('DestinationPath')]
    [PSObject]
    $Destination,

    <#
    Includes the specified parts.
    
    Enter a wildcard pattern, such as `*.txt`

    Wildcards are permitted.
    #>

    [ValidateNotNullOrEmpty()]
    [SupportsWildcards()]
    [string[]]
    $Include,
    
    <#
    Excludes the specified parts.
    
    Enter a wildcard pattern, such as `*.txt`
    
    Wildcards are permitted.
    #>

    [ValidateNotNullOrEmpty()]
    [SupportsWildcards()]
    [string[]]
    $Exclude,

    <#
    Includes the specified content types.
    
    Enter a wildcard pattern, such as `text/*`
    #>

    [ValidateNotNullOrEmpty()]
    [SupportsWildcards()]
    [string[]]
    $IncludeContentType,
    
    <#
    Excludes the specified content types.
    
    Enter a wildcard pattern, such as `text/*`
    #>

    [ValidateNotNullOrEmpty()]
    [SupportsWildcards()]
    [string[]]
    $ExcludeContentType,

    # If set, will merge contents into an existing file.
    [switch]
    $Merge,

    # The input object
    [Parameter(ValueFromPipeline)]
    [PSObject]
    $InputObject,

    # If set, will update existing packages.
    [switch]
    $Force
    )

    begin {
        $selectOpenPackage = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Select-OpenPackage','Function')
    }

    process {
        # If the input was not a package
        if ($inputObject -isnot [IO.Packaging.Package]) {
            $loadedPackage = # see if it is a file we can load
                if ($InputObject -is [IO.FileInfo]) {
                    Get-OpenPackage $InputObject.FullName
                } elseif ($inputFile = Get-Item -ErrorAction Ignore -Path "$InputObject") {
                    Get-OpenPackage $inputFile.FullName
                }

            # If it was not, return.
            if ($loadedPackage -isnot [IO.Packaging.Package]) { return }
            $InputObject = $loadedPackage
        }
        
        $destinationPackage = 
            if ($Destination -is [IO.Packaging.Package]) {
                $Destination
            } elseif ($Destination) {
                # Get the absolute path of the destination, without creating the file,
                $unresolvedDestination = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Destination)

                # then see if the file exists.
                $fileExists = Test-Path $unresolvedDestination
                # If it does and we are not using the -Force
                if ($fileExists -and -not $force) {
                    # write an error
                    Write-Error "$unresolvedDestionation already exists, use -Force to overwrite" -Category ResourceExists
                    return
                }
                # If it did not exist, create it with New-Item -Force
                elseif (-not $Merge)
                {
                    # this will create intermediate paths.
                    $packageFile = New-Item -ItemType File -Path $unresolvedDestination -Force
                    if (-not $packageFile) { return }
                } elseif ($Merge) {
                    $packageFile = Get-Item -ItemType File -Path $unresolvedDestination
                }
                # Try to open or create our package for read and write.
                [IO.Packaging.Package]::Open($packageFile.FullName, 'OpenOrCreate', 'ReadWrite')
            } else {
                $memoryStream = [IO.MemoryStream]::new()
                [IO.Packaging.Package]::Open($memoryStream, 'OpenOrCreate', 'ReadWrite') |
                    Add-Member NoteProperty MemoryStream $memoryStream -Force -PassThru 
            }
                        
        # If we could not, we are done.
        if (-not $destinationPackage) { return }

        # Start off with the package properties
        foreach ($property in $InputObject.PackageProperties.psobject.properties) {
            if ($property.IsSettable) {
                $destinationPackage.PackageProperties.$($property.Name) =
                    $InputObject.PackageProperties.$($property.Name)
            }
        }

        # Get the input parts and relationships
        $inputPackageParts = $InputObject.GetParts()

        $selectSplat = [Ordered]@{InputObject=$InputObject}
        foreach ($key in $PSBoundParameters.Keys) {
            if ($selectOpenPackage.Parameters[$key]) {
                $selectSplat[$key] = $PSBoundParameters[$key]
            }
        }
        $inputPackageParts = Select-OpenPackage @selectSplat

        $inputPackageRelationships = @($InputObject.GetRelationships())

        # For each part in the input
        foreach ($inputPart in $inputPackageParts) {
            # Create or open a part in the destination
            $destinationPart =
                if (-not $destinationPackage.PartExists($inputPart.Uri)) {
                    $destinationPackage.CreatePart($inputPart.Uri, $inputPart.ContentType, $inputPart.CompressionOption)
                } else {
                    $destinationPackage.GetPart($inputPart.Uri)
                }

            # and copy the streams.
            $inputStream = $inputPart.GetStream('Open', 'Read')
            $destinationStream = $destinationPart.GetStream()
            $inputStream.CopyTo($destinationStream)
            $inputStream.Close()
            $destinationStream.Close()

            $partRelationships = @(
                try {
                    $inputPart.GetRelationships()
                } catch {
                    # Relationships cannot have relationships
                    # Also, we can't copy any relationship that throws an exception.
                    # So simply ignore the error.
                }
            )
            if ($partRelationships) {
                $inputPackageRelationships += $partRelationships
            }            
        }

        # Then, create any relationships that do not exist.
        foreach ($inputRelationship in $inputPackageRelationships) {            
            if ($inputRelationship.SourceUri -eq '/') {
                if (-not $destinationPackage.RelationshipExists($inputRelationship.id)) {
                    $null = $destinationPackage.CreateRelationship(
                        $inputRelationship.targetUri, 
                        $inputRelationship.targetMode, 
                        $inputRelationship.relationshipType,
                        $inputRelationship.id
                    )
                }
            } elseif ($inputRelationship.SourceUri -match '^/.') {
                $destinationPackagePart = $destinationPackage.GetPart($inputRelationship.SourceUri)
                if (-not $destinationPackagePart.RelationshipExists($inputRelationship.id)) {
                    $destinationPackagePart.CreateRelationship(
                        $inputRelationship.targetUri, 
                        $inputRelationship.targetMode, 
                        $inputRelationship.relationshipType,
                        $inputRelationship.id
                    )
                }
            }
        }        

        if ($destination -and $Destination -isnot [IO.Packaging.Package]) {
            # We can now close our package, writing the file.
            $destinationPackage.Close()
            # We want to open it right back up again as we output the updated file.
            Get-OpenPackage -FilePath $unresolvedDestination
        } else {
            $destinationPackage
        }        
    }
}