# Build TFS import xlsx from XML parts as a stored (uncompressed) ZIP. # Pure in-process .NET byte APIs: no external processes, no assembly loading. # Output file name uses ASCII+char trick to stay encoding-safe. $ErrorActionPreference = 'Stop' $parts = Join-Path $PSScriptRoot '_parts' $outName = 'TFS' + [char]0x5BFC + [char]0x5165 + [char]0x6A21 + [char]0x677F + '.xlsx' $out = Join-Path $PSScriptRoot $outName # ---------- CRC32 (IEEE 802.3) ---------- $tbl = New-Object 'System.Int64[]' 256 for ($i = 0; $i -lt 256; $i++) { $c = [int64]$i for ($k = 0; $k -lt 8; $k++) { if (($c -band 1) -eq 1) { $c = (3988292384 -bxor ($c -shr 1)) } else { $c = ($c -shr 1) } $c = $c -band 4294967295 } $tbl[$i] = $c } function Get-Crc32([byte[]]$data) { $crc = [int64]4294967295 foreach ($b in $data) { $idx = [int](($crc -bxor [int64]$b) -band 255) $crc = ($tbl[$idx] -bxor ($crc -shr 8)) -band 4294967295 } return ($crc -bxor 4294967295) } # ---------- entry list (source relative path, zip entry name) ---------- $entries = @( @{ src = '[Content_Types].xml'; zip = '[Content_Types].xml' }, @{ src = '_rels\.rels'; zip = '_rels/.rels' }, @{ src = 'xl\workbook.xml'; zip = 'xl/workbook.xml' }, @{ src = 'xl\_rels\workbook.xml.rels'; zip = 'xl/_rels/workbook.xml.rels' }, @{ src = 'xl\styles.xml'; zip = 'xl/styles.xml' }, @{ src = 'xl\worksheets\sheet1.xml'; zip = 'xl/worksheets/sheet1.xml' } ) $ms = New-Object System.IO.MemoryStream $central = New-Object System.Collections.ArrayList foreach ($e in $entries) { $srcPath = Join-Path $parts $e['src'] $bytes = [System.IO.File]::ReadAllBytes($srcPath) $nameBytes = [System.Text.Encoding]::ASCII.GetBytes([string]$e['zip']) $crc = Get-Crc32 $bytes $offset = $ms.Position $ms.Write([BitConverter]::GetBytes([uint32]0x04034b50), 0, 4) # local header signature $ms.Write([BitConverter]::GetBytes([uint16]20), 0, 2) # version needed $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # flags $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # method: stored $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # mod time $ms.Write([BitConverter]::GetBytes([uint16]33), 0, 2) # mod date 1980-01-01 $ms.Write([BitConverter]::GetBytes([uint32]$crc), 0, 4) $ms.Write([BitConverter]::GetBytes([uint32]$bytes.Length), 0, 4) $ms.Write([BitConverter]::GetBytes([uint32]$bytes.Length), 0, 4) $ms.Write([BitConverter]::GetBytes([uint16]$nameBytes.Length), 0, 2) $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # extra len $ms.Write($nameBytes, 0, $nameBytes.Length) $ms.Write($bytes, 0, $bytes.Length) [void]$central.Add(@{ name = $nameBytes; crc = $crc; size = $bytes.Length; offset = $offset }) } # ---------- central directory ---------- $cdStart = $ms.Position foreach ($c in $central) { $ms.Write([BitConverter]::GetBytes([uint32]0x02014b50), 0, 4) # central header signature $ms.Write([BitConverter]::GetBytes([uint16]20), 0, 2) # made by $ms.Write([BitConverter]::GetBytes([uint16]20), 0, 2) # version needed $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # flags $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # method $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # time $ms.Write([BitConverter]::GetBytes([uint16]33), 0, 2) # date $ms.Write([BitConverter]::GetBytes([uint32]$c['crc']), 0, 4) $ms.Write([BitConverter]::GetBytes([uint32]$c['size']), 0, 4) $ms.Write([BitConverter]::GetBytes([uint32]$c['size']), 0, 4) $ms.Write([BitConverter]::GetBytes([uint16]$c['name'].Length), 0, 2) $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # extra len $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # comment len $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # disk start $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # internal attrs $ms.Write([BitConverter]::GetBytes([uint32]0), 0, 4) # external attrs $ms.Write([BitConverter]::GetBytes([uint32]$c['offset']), 0, 4) $ms.Write($c['name'], 0, $c['name'].Length) } $cdSize = $ms.Position - $cdStart # ---------- end of central directory ---------- $ms.Write([BitConverter]::GetBytes([uint32]0x06054b50), 0, 4) $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # disk number $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # cd disk $ms.Write([BitConverter]::GetBytes([uint16]$central.Count), 0, 2) $ms.Write([BitConverter]::GetBytes([uint16]$central.Count), 0, 2) $ms.Write([BitConverter]::GetBytes([uint32]$cdSize), 0, 4) $ms.Write([BitConverter]::GetBytes([uint32]$cdStart), 0, 4) $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # comment len [System.IO.File]::WriteAllBytes($out, $ms.ToArray()) $ms.Dispose() Write-Output ('CREATED ' + $out + ' size=' + (New-Object System.IO.FileInfo $out).Length)