Commands/Set-OpenPackage.ps1

function Set-OpenPackage
{
    <#
    .SYNOPSIS
        Sets Open Package content
    .DESCRIPTION
        Sets content in an Open Packaging Conventions archive.
    .EXAMPLE
        $miniServer = Get-OpenPackage |
            Set-OpenPackage -Uri '/index.html' -Content ([xml]"<h1>Hello World</h1>") -ContentType text/html |
            Start-OpenPackage
        Start-Process -FilePath $miniServer.Name
    .LINK
        Get-OpenPackage
    #>

    [Alias('Set-OP','sop','sOpenPackage')]
    param(
    # The uri to set
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [Alias('Url','Href','PartUri','PartUrl','LocalPath')]
    [uri]
    $Uri,    

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

    # The content type. By default, `text/plain`
    [Parameter(ValueFromPipelineByPropertyName)]    
    [string]
    $ContentType,

    # 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 input object.
    # This must be a package, and it must be writeable.
    [Parameter(ValueFromPipeline)]
    [Alias('Package')]
    [PSObject]
    $InputObject,

    # A content type map.
    # This maps extensions and URIs to a content type.
    [Collections.IDictionary]
    $TypeMap = $(
        ([PSCustomObject]@{PSTypeName='OpenPackage.ContentTypeMap'}).TypeMap
    ),

    # Sets a part, even if it already exists.
    [switch]
    $Force
    )

    process {
        # If there is no input, there is nothing to do
        if (-not $InputObject) { return }
        # If the input is not a package, pass it thru.
        if ($InputObject -isnot [IO.Packaging.Package]) {
            return $InputObject
        }

        # If the uri is not prefixed,
        if ($uri -notmatch '^/') {
            $uri = "/$uri" # add it to avoid easy errors.
        }

        # Create or recreate the part (otherwise writes might be partial)
        $part = 
            if ($InputObject.PartExists($uri)) {                
                if (-not $Force) {
                    Write-Error "'$uri' already exists, use -Force to overwrite"
                    return
                }

                $InputObject.GetPart($uri)
            } else {
                if (-not $PSBoundParameters['ContentType']) {
                    $extension = @($uri -split '\.' -ne '')[-1]
                    if ($typeMap.TypeMap.Contains($extension)) {
                        $contentType = $typeMap.TypeMap[$extension]
                    } else {
                        $contentType = 'text/plain'
                    }
                }
                $InputObject.CreatePart($uri, $ContentType)
            }

        # If we don't have a part, return
        if (-not $part) { return }

        # If we have a writer for the part:
        if ($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
        }
        
        # Get the stream
        $partStream = $part.GetStream()
        $partStream.SetLength(0)
        # First see if the content is a byte[]
        if ($content -is [byte[]]) {
            # if so, just write it
            $partStream.Write($content, 0, $content.Length)
        }
        # If the content is a stream,
        elseif ($content -is [IO.Stream]) {
            # copy it in.
            $content.CopyTo($partStream)
        }
        # If the content was xml or could be,
        elseif ($content -is [xml] -or ($contentXml = $content -as [xml])) {            
            if ($contentXml) { $content = $contentXml }
            $content.Save($partStream)
        } elseif ($content -is [string]) {
            # Put strings in as a byte array.
            $buffer = $OutputEncoding.GetBytes($content)
            $partStream.Write($buffer, 0, $buffer.Length)
        } elseif ($contentBytes = $content -as [byte[]]) {
            # Bytes are obviously a byte array
            $partStream.Write($contentBytes, 0, $contentBytes.Length)
        }         
        else {
            # and everything else is stringified
            $buffer = $OutputEncoding.GetBytes("$content")
            $partStream.Write($buffer, 0, $buffer.Length)
        }

        # Close the part stream
        $partStream.Close()
        
        # then pass it thru so we can keep piping.
        $inputObject
    }
}