pub const UNTAR: &str = "# The project into a Windows machine: unpacks the tar.gz $archive into $dest (replacing an older\n# copy), leaving out the archive\'s first $strip folders. Plain .NET (GZipStream), so it runs on\n# every Windows Isoloom supports: PowerShell 2.0 and later, and no tar.exe before Windows Server\n# 2019. Symbolic links are left out (counted).\n& {\n $ErrorActionPreference = \'Stop\'\n $sep = [IO.Path]::DirectorySeparatorChar\n $new = \"$dest.new\"\n if (Test-Path $new) { Remove-Item $new -Recurse -Force }\n [void][IO.Directory]::CreateDirectory($new)\n $file = New-Object IO.FileStream($archive, [IO.FileMode]::Open, [IO.FileAccess]::Read)\n $in = New-Object IO.Compression.GZipStream($file, [IO.Compression.CompressionMode]::Decompress)\n $utf8 = New-Object Text.UTF8Encoding($false)\n $header = New-Object byte[] 512\n $chunk = New-Object byte[] 65536\n function Fill([byte[]]$b, [int]$n) {\n $got = 0\n while ($got -lt $n) {\n $r = $in.Read($b, $got, $n - $got)\n if ($r -le 0) { throw \"$archive ends early\" }\n $got += $r\n }\n }\n function Field([int]$at, [int]$len) {\n $end = [Array]::IndexOf($header, [byte]0, $at, $len)\n if ($end -lt 0) { $end = $at + $len }\n $utf8.GetString($header, $at, $end - $at)\n }\n # Copies $size bytes to $out (or skips them, with no $out), then the padding after them.\n function Body([long]$size, $out) {\n $left = $size\n while ($left -gt 0) {\n $n = [int][Math]::Min([long]$chunk.Length, $left)\n Fill $chunk $n\n if ($out) { $out.Write($chunk, 0, $n) }\n $left -= $n\n }\n $pad = [int]((512 - $size % 512) % 512)\n if ($pad) { Fill $chunk $pad }\n }\n $pax = @{}\n $links = 0\n $files = 0\n while ($true) {\n Fill $header 512\n if ($header[148] -eq 0) { break } # the end: zero blocks have no checksum\n $type = [char]$header[156]\n $sizeText = (Field 124 12).Trim()\n $size = [long]0\n if ($sizeText) { $size = [Convert]::ToInt64($sizeText, 8) }\n if ($pax[\'size\']) { $size = [long]$pax[\'size\'] }\n if ($type -eq \'x\') {\n # pax records (\"<length> key=value\\n\"): a long path, a large size, for the next entry.\n $ms = New-Object IO.MemoryStream\n Body $size $ms\n $b = $ms.ToArray()\n $i = 0\n while ($i -lt $b.Length) {\n $sp = [Array]::IndexOf($b, [byte]32, $i)\n $len = [int]$utf8.GetString($b, $i, $sp - $i)\n $eq = [Array]::IndexOf($b, [byte]61, $sp)\n $pax[$utf8.GetString($b, $sp + 1, $eq - $sp - 1)] = $utf8.GetString($b, $eq + 1, $i + $len - $eq - 2)\n $i += $len\n }\n continue\n }\n if ($type -eq \'L\' -or $type -eq \'K\') {\n # GNU tar\'s long name (or link target) for the next entry.\n $ms = New-Object IO.MemoryStream\n Body $size $ms\n if ($type -eq \'L\') { $pax[\'path\'] = $utf8.GetString($ms.ToArray()).TrimEnd([char]0) }\n continue\n }\n $name = Field 0 100\n $prefix = Field 345 155\n if ($prefix) { $name = \"$prefix/$name\" }\n if ($pax[\'path\']) { $name = $pax[\'path\'] }\n $pax = @{}\n $parts = @($name.Split(\'/\') | Where-Object { $_ -ne \'\' -and $_ -ne \'.\' })\n if ($parts.Count -le $strip) { Body $size $null; continue }\n $path = Join-Path $new ([string]::Join([string]$sep, [string[]]($parts[$strip..($parts.Count - 1)])))\n if ($type -eq \'5\' -or $type -eq \'0\' -or $type -eq [char]0) {\n # A name Windows can\'t take (`:`, `*`, too long...) is left out, with a warning.\n $out = $null\n try {\n if ($type -eq \'5\') {\n [void][IO.Directory]::CreateDirectory($path)\n } else {\n [void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($path))\n $out = [IO.File]::Create($path)\n }\n } catch {\n Write-Warning \"Not copied: $name ($($_.Exception.Message))\"\n Body $size $null\n continue\n }\n if ($out) {\n try { Body $size $out } finally { $out.Close() }\n $files++\n } else {\n Body $size $null\n }\n } else {\n if ($type -eq \'2\') { $links++ }\n Body $size $null\n }\n }\n $in.Close()\n if (Test-Path $dest) { Remove-Item $dest -Recurse -Force }\n Move-Item $new $dest\n Remove-Item $archive -Force\n $note = \'\'\n if ($links) { $note = \" ($links symbolic links left out)\" }\n \"$files files in $dest$note\"\n}\n";Expand description
Unpacks the project’s tar.gz on Windows ($archive, $dest, $strip set before it): no
tar.exe before Windows Server 2019.