chore: 初始化 TFS 2013 工作项批量导入工具集
- TfsExcelImporter: WinForms 桌面版,走 TFS Client OM(本环境 wit REST 路由 404,OM 是唯一可行通道) - tfs_import.py: REST 版命令行导入器,供 wit 接口可用的 TFS 实例使用 - templates: Excel 导入模板及其 OOXML 源部件 - README: 两套工具定位、配置、环境坑点与验证方式 - 真实台账与本机工作笔记不入库,仅提交 work_items.example.csv 说明列结构
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user