Home > database >  PowerShell reading and writing compressed files with byte arrays
PowerShell reading and writing compressed files with byte arrays

Time:02-28

I'm re-writing a PowerShell script which works with archives. I'm using two functions from here

Expand-Archive without Importing and Exporting files

and can successfully read and write files to the archive. I've posted the whole program just in case it makes things clearer for someone to help me.

However, there are three issues (besides the fact that I don't really know what I'm doing).

1.) Most files have this error on when trying to run Add-ZipEntry -ZipFilePath ($OriginalArchivePath $PartFileDirectoryName) -EntryPath $entry.FullName -Content $fileBytes}

Cannot convert value "507" to type "System.Byte". Error: "Value was either too large or too small for an unsigned byte." (replace 507 with whatever number from the byte array is there)

2.) When it reads a file and adds it to the zip archive (*.imscc) it adds a character "a" to the beginning of the file contents.

3.) The only file it doesn't error on are text files, when I really want it to handle any file

Thank you for any assistance!

Update: I've tried using System.IO.BinaryWriter, with the same errors.

Add-Type -AssemblyName 'System.Windows.Forms'
Add-Type -AssemblyName 'System.IO.Compression'
Add-Type -AssemblyName 'System.IO.Compression.FileSystem'

function Folder-SuffixGenerator($SplitFileCounter)
{
    return ' (' $usrSuffix ' ' $SplitFileCounter ')'
}

function Get-ZipEntryContent(#returns the bytes of the first matching entry 
  [string] $ZipFilePath, #optional - specify a ZipStream or path 
  [IO.Stream] $ZipStream = (New-Object IO.FileStream($ZipFilePath, [IO.FileMode]::Open)),
  [string] $EntryPath){
    $ZipArchive = New-Object IO.Compression.ZipArchive($ZipStream, [IO.Compression.ZipArchiveMode]::Read)
    $buf = New-Object byte[] (0) #return an empty byte array if not found
    $ZipArchive.GetEntry($EntryPath) | ?{$_} | %{ #GetEntry returns first matching entry or null if there is no match
        $buf = New-Object byte[] ($_.Length)
        Write-Verbose "     reading: $($_.Name)"
        $_.Open().Read($buf,0,$buf.Length)
    }
    $ZipArchive.Dispose()
    $ZipStream.Close()
    $ZipStream.Dispose()
    return ,$buf 
}


function Add-ZipEntry(#Adds an entry to the $ZipStream. Sample call: Add-ZipEntry -ZipFilePath "$PSScriptRoot\temp.zip" -EntryPath Test.xml -Content ([text.encoding]::UTF8.GetBytes("Testing"))
  [string] $ZipFilePath, #optional - specify a ZipStream or path 
  [IO.Stream] $ZipStream = (New-Object IO.FileStream($ZipFilePath, [IO.FileMode]::OpenOrCreate)),
  [string] $EntryPath, 
  [byte[]] $Content, 
  [switch] $OverWrite, #if specified, will not create a second copy of an existing entry 
  [switch] $PassThru ){#return a copy of $ZipStream
    $ZipArchive = New-Object IO.Compression.ZipArchive($ZipStream, [IO.Compression.ZipArchiveMode]::Update, $true)
    $ExistingEntry = $ZipArchive.GetEntry($EntryPath) | ?{$_} 
    If($OverWrite -and $ExistingEntry){
        Write-Verbose "    deleting existing $($ExistingEntry.FullName)"
        $ExistingEntry.Delete()
    }
    $Entry = $ZipArchive.CreateEntry($EntryPath)
    $WriteStream = New-Object System.IO.StreamWriter($Entry.Open())
    $WriteStream.Write($Content,0,$Content.Length)
    $WriteStream.Flush()
    $WriteStream.Dispose()
    $ZipArchive.Dispose()
    If($PassThru){
        $OutStream = New-Object System.IO.MemoryStream
        $ZipStream.Seek(0, 'Begin') | Out-Null
        $ZipStream.CopyTo($OutStream)
    }
    $ZipStream.Close()
    $ZipStream.Dispose()
    If($PassThru){$OutStream}
}

$NoDeleteFiles = @('files_meta.xml' ,'course_settings.xml', 'assignment_groups.xml', 'canvas_export.txt', 'imsmanifest.xml')

Set-Variable usrSuffix -Option ReadOnly -Value 'part' -Force
$MaxImportFileSize = 1000
$compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
$SplitFileCounter = 1


$FileBrowser = New-Object System.Windows.Forms.OpenFileDialog
$FileBrowser.filter = "Canvas Export Files (*.imscc)| *.imscc"
[void]$FileBrowser.ShowDialog()
$FileBrowser.FileName

$FilePath = $FileBrowser.FileName

$OriginalArchivePath = $FilePath.Substring(0,$FilePath.Length-6)

$PartFileDirectoryName = $OriginalArchive   (Folder-SuffixGenerator($SplitFileCounter))   '.imscc'

$CourseZip = [IO.Compression.ZipFile]::OpenRead($FilePath)
$CourseZipFiles = $CourseZip.Entries | Sort Length -Descending
$CourseZip.Dispose()
<#
$SortingTable = $CourseZip.entries | Select Fullname,

  @{Name="Size";Expression={$_.length}},

  @{Name="CompressedSize";Expression={$_.Compressedlength}},

  @{Name="PctZip";Expression={[math]::Round(($_.compressedlength/$_.length)*100,2)}}|

Sort Size -Descending | format-table –AutoSize
#>

# Add mandatory files
ForEach($entry in $CourseZipFiles)
{
    if ($NoDeleteFiles.Contains($entry.Name)){
        Write-Output "Adding to Zip"   $entry.FullName
        # Add to Zip
        $fileBytes = Get-ZipEntryContent -ZipFilePath $FilePath -EntryPath $entry.FullName
        Add-ZipEntry -ZipFilePath ($OriginalArchivePath   $PartFileDirectoryName) -EntryPath $entry.FullName -Content $fileBytes
    }

    
}```

CodePudding user response:

System.IO.StreamWriter is a text writer, and therefore not suitable for writing raw bytes. Cannot convert value "507" to type "System.Byte" indicates that an inappropriate attempt was made to convert text - a .NET string composed of [char] instances which are in effect [uint16] code points (range 0x0 - 0xffff) - to [byte] instances (0x0 - 0xff). Therefore, any Unicode character whose code point is greater than 255 (0xff) will cause this error.

The solution is to use a .NET API that allows writing raw bytes, namely System.IO.BinaryWriter:

    $WriteStream = [System.IO.BinaryWriter]::new($Entry.Open())
    $WriteStream.Write($Content)
    $WriteStream.Flush()
    $WriteStream.Dispose()
  • Related