Commands/Write-OpenPackage.ps1

function Write-OpenPackage
{
    <#
    .SYNOPSIS
        Writes bytes to an Open Package
    .DESCRIPTION
        Writes bytes directly to an Open Package part. The part must already exist.
        
        This can be used to rapidly update small segments of a package.
        
        It can also corrupt package contents, and should be used with care.
        
        To set package parts, use Set-OpenPackage
    .EXAMPLE
        $package = OP @{"hello.txt" = "Hello world"}
        $package |
            Write-OpenPackage -Uri /hello.txt -Buffer ($outputEncoding.GetBytes("y")) |
            Get-OpenPackage ./hello.txt
    .LINK
        Get-OpenPackage
    .LINK
        Read-OpenPackage
    .LINK
        Set-OpenPackage
    #>

    [Alias('Write-OP', 'wrop', 'wrOpenPackage')]
    param(
    # The Package Part Uri. This is the path to the content within a package.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [Alias('PartUri')]
    [Uri]
    $Uri,

    # The content to write.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [Alias('Buffer','Text')]
    [PSObject]
    $Content,

    # The serialization depth.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateRange(1,100)]
    [int]
    $Depth = $(
        if ($FormatEnumerationLimit * 2 -gt 1) {
            $FormatEnumerationLimit * 2
        } else {
            4
        }
    ),

    # The options used to write the content.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('Options')]
    [Collections.IDictionary]
    $Option = [Ordered]@{},

    # The starting location for the write.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('PartStart')]
    [long]
    $RangeStart = 0,

    # The package.
    [Parameter(ValueFromPipeline)]
    [Alias('Package')]
    [PSObject]
    $InputObject
    )

    process {
        # If the input object is not a package,
        if ($InputObject -isnot [IO.Packaging.Package]) {
            # look for a property named package
            if ($inputObject.Package -is [IO.Packaging.Package]) {
                # and use that as our input.
                $inputObject = $inputObject.Package
            } else {
                # Otherwise, pass thru the input.
                return $InputObject
            }
        }


        $SlashPart = "$uri" -replace '^/?', '/'

        if (-not $InputObject.PartExists($SlashPart)) {
            return
        }

        

        $part = $InputObject.GetPart($SlashPart)
        if (-not $part) { return }

        # If we have a writer for the part, and no starting range
        if (
            (-not $PSBoundParameters.ContainsKey('RangeStart')) -and 
            $part.Writer -and $part.Write
        ) {
            # * Prepare the options
            if (-not $option) {
                $Option = [Ordered]@{}
            }
            # * Copy in depth
            if (-not $option.Depth) {
                $Option.Depth = $Depth
            }
            # * Call the writer
            $part.Write($Content, $option)
            # * and return.
            return $InputObject
        }

        # Otherwise get the stream
        $partStream = $part.GetStream()
        # seek to the location
        if ($RangeStart -lt $partStream.Length) {
            $null = $partStream.Seek($RangeStart, 'Begin')
        }

        # Make sure our content buffer is bytes
        $buffer = if ($content -is [byte[]]) {
            $content
        } elseif ($(
            $contentAsBytes = $content -as [byte[]]; $contentAsBytes            
        )) {
            $contentAsBytes
        } else {
            "$content"
        }
        
        # write the bytes to the stream
        $partStream.Write($Buffer, 0, $Buffer.Length)

        # close up
        $partStream.Close()
        $partStream.Dispose()
                  
        # and return the input object.
        return $InputObject
    }
}