Private/Core.IO.ps1
|
function Test-OIIsWindows { [CmdletBinding()] param() $isWindowsVariable = Get-Variable -Name IsWindows -Scope Global -ErrorAction SilentlyContinue if ($null -ne $isWindowsVariable) { return [bool]$isWindowsVariable.Value } return ($env:OS -eq 'Windows_NT') } function Resolve-OIEncoding { [CmdletBinding(DefaultParameterSetName = 'Path')] param( [Parameter(Mandatory = $true, ParameterSetName = 'Path')] [string]$Path, [Parameter(Mandatory = $true, ParameterSetName = 'Stream')] [System.IO.Stream]$Stream, [Parameter(Mandatory = $true)] [ValidateSet('Auto', 'Default', 'UTF8', 'UTF16LE', 'UTF16BE', 'ASCII')] [string]$Name ) $detectedName = $Name $preambleLength = 0 $header = New-Object byte[] 4 $read = 0 $sourceStream = $Stream $ownsStream = $false $originalPosition = $null try { if ($PSCmdlet.ParameterSetName -eq 'Path') { $sourceStream = [System.IO.File]::Open( $Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read ) $ownsStream = $true } elseif (-not $sourceStream.CanSeek) { throw 'Encoding resolution requires a seekable stream.' } if (-not $ownsStream) { $originalPosition = $sourceStream.Position } $null = $sourceStream.Seek(0, [System.IO.SeekOrigin]::Begin) $read = $sourceStream.Read($header, 0, $header.Length) } finally { if ($ownsStream -and $null -ne $sourceStream) { $sourceStream.Dispose() } elseif ($null -ne $sourceStream -and $null -ne $originalPosition) { $null = $sourceStream.Seek([int64]$originalPosition, [System.IO.SeekOrigin]::Begin) } } $hasUtf8Bom = $read -ge 3 -and $header[0] -eq 0xEF -and $header[1] -eq 0xBB -and $header[2] -eq 0xBF $hasUtf16LeBom = $read -ge 2 -and $header[0] -eq 0xFF -and $header[1] -eq 0xFE $hasUtf16BeBom = $read -ge 2 -and $header[0] -eq 0xFE -and $header[1] -eq 0xFF if ($Name -eq 'Auto') { if ($hasUtf8Bom) { $detectedName = 'UTF8' } elseif ($hasUtf16LeBom) { $detectedName = 'UTF16LE' } elseif ($hasUtf16BeBom) { $detectedName = 'UTF16BE' } else { # UTF-8 is the safest cross-platform default for modern source files. $detectedName = 'UTF8' } } if (($detectedName -eq 'UTF8' -and $hasUtf8Bom)) { $preambleLength = 3 } elseif ($detectedName -eq 'UTF16LE' -and $hasUtf16LeBom) { $preambleLength = 2 } elseif ($detectedName -eq 'UTF16BE' -and $hasUtf16BeBom) { $preambleLength = 2 } switch ($detectedName) { 'UTF8' { $encoding = New-Object System.Text.UTF8Encoding($false, $false) $newline = [byte[]](0x0A) $carriageReturn = [byte[]](0x0D) $unitSize = 1 } 'UTF16LE' { $encoding = New-Object System.Text.UnicodeEncoding($false, $false, $false) $newline = [byte[]](0x0A, 0x00) $carriageReturn = [byte[]](0x0D, 0x00) $unitSize = 2 } 'UTF16BE' { $encoding = New-Object System.Text.UnicodeEncoding($true, $false, $false) $newline = [byte[]](0x00, 0x0A) $carriageReturn = [byte[]](0x00, 0x0D) $unitSize = 2 } 'ASCII' { $encoding = [System.Text.Encoding]::ASCII $newline = [byte[]](0x0A) $carriageReturn = [byte[]](0x0D) $unitSize = 1 } default { $encoding = [System.Text.Encoding]::Default $newline = [byte[]](0x0A) $carriageReturn = [byte[]](0x0D) $unitSize = 1 $detectedName = 'Default' } } return [pscustomobject]@{ RequestedName = $Name DetectedName = $detectedName Encoding = $encoding NewlineBytes = $newline CarriageReturn = $carriageReturn UnitSize = $unitSize PreambleLength = $preambleLength } } function Open-OIFileContext { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Path, [Parameter(Mandatory = $true)] [string]$EncodingName ) $resolved = (Resolve-Path -LiteralPath $Path -ErrorAction Stop).Path $item = Get-Item -LiteralPath $resolved -ErrorAction Stop if ($item.PSIsContainer) { throw "Path is a directory, not a file: $resolved" } $stream = $null try { $stream = [System.IO.File]::Open( $resolved, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read ) $encodingInfo = Resolve-OIEncoding -Stream $stream -Name $EncodingName return [pscustomobject]@{ Path = $resolved Length = [int64]$stream.Length Stream = $stream EncodingInfo = $encodingInfo } } catch { if ($null -ne $stream) { $stream.Dispose() } throw } } function Close-OIFileContext { [CmdletBinding()] param( [Parameter(ValueFromPipeline = $true)] [AllowNull()] [object]$Context ) process { if ($null -ne $Context -and $null -ne $Context.Stream) { $Context.Stream.Dispose() } } } function Read-OIFileRange { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [System.IO.FileStream]$Stream, [Parameter(Mandatory = $true)] [ValidateScript({ $_ -ge 0 })] [int64]$Start, [Parameter(Mandatory = $true)] [ValidateScript({ $_ -ge 0 })] [int]$Length ) if ($Length -eq 0) { return ,([byte[]]@()) } if ($Start -ge $Stream.Length) { return ,([byte[]]@()) } $available = [int64]$Stream.Length - $Start $effectiveLength = [int][Math]::Min([int64]$Length, $available) $buffer = New-Object byte[] $effectiveLength $null = $Stream.Seek($Start, [System.IO.SeekOrigin]::Begin) $total = 0 while ($total -lt $effectiveLength) { $read = $Stream.Read($buffer, $total, $effectiveLength - $total) if ($read -le 0) { break } $total += $read } if ($total -eq $effectiveLength) { return ,$buffer } $trimmed = New-Object byte[] $total if ($total -gt 0) { [System.Buffer]::BlockCopy($buffer, 0, $trimmed, 0, $total) } return ,$trimmed } function Get-OIStreamSha256 { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [System.IO.Stream]$Stream ) if (-not $Stream.CanSeek -or -not $Stream.CanRead) { throw 'SHA-256 calculation requires a readable and seekable stream.' } $originalPosition = $Stream.Position $sha256 = $null try { $null = $Stream.Seek(0, [System.IO.SeekOrigin]::Begin) $sha256 = [System.Security.Cryptography.SHA256]::Create() $hashBytes = $sha256.ComputeHash($Stream) return (($hashBytes | ForEach-Object { $_.ToString('x2') }) -join '') } finally { if ($null -ne $sha256) { $sha256.Dispose() } $null = $Stream.Seek($originalPosition, [System.IO.SeekOrigin]::Begin) } } function Copy-OIStreamPrefix { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [System.IO.Stream]$SourceStream, [Parameter(Mandatory = $true)] [string]$DestinationPath, [Parameter(Mandatory = $true)] [ValidateScript({ $_ -ge 0 })] [int64]$Length, [ValidateNotNull()] [byte[]]$Buffer ) if (-not $SourceStream.CanSeek -or -not $SourceStream.CanRead) { throw 'The source stream must be readable and seekable.' } if ($Length -gt $SourceStream.Length) { throw "Requested prefix length $Length exceeds source length $($SourceStream.Length)." } $destination = $null $originalPosition = $SourceStream.Position try { $destination = [System.IO.File]::Open( $DestinationPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None ) $null = $SourceStream.Seek(0, [System.IO.SeekOrigin]::Begin) $remaining = $Length if ($null -eq $Buffer -or $Buffer.Length -eq 0) { $Buffer = New-Object byte[] (1024 * 1024) } while ($remaining -gt 0) { $toRead = [int][Math]::Min([int64]$Buffer.Length, $remaining) $read = $SourceStream.Read($Buffer, 0, $toRead) if ($read -le 0) { break } $destination.Write($Buffer, 0, $read) $remaining -= $read } if ($remaining -ne 0) { throw "The source stream ended before the requested $Length-byte prefix was copied." } } finally { if ($null -ne $destination) { $destination.Dispose() } $null = $SourceStream.Seek($originalPosition, [System.IO.SeekOrigin]::Begin) } } |