从 Powershell 中使用 System.IO.FileStream.WriteByte() 时文件中出现意外字符

Unexpected characters in file when using System.IO.FileStream.WriteByte() from within Powershell

请考虑以下 Powershell 脚本:

Out-File -FilePath "c:\batch\vss\test.txt" -NoNewline -InputObject "0"

$Stream = [System.IO.File]::Open("c:\batch\vss\test.txt",
                                 [System.IO.FileMode]::Open,
                                 [System.IO.FileAccess]::Write,
                                 [System.IO.FileShare]::ReadWrite)
$Stream.WriteByte(49)

$Stream.Dispose()

执行脚本后,文件 c:\batch\vss\test.txt 包含意外字符。它包含以下 4 个字节(十六进制):

31 fe 30 00

从这些中,只有第一个是预期的 (0x31 = 49)。 其他的是意想不到的。 [最后一句是错误的 - 请参阅更新/解决方案部分]

我已验证此文件在执行第一行脚本后正好包含一个字节(0x30,即'0'的ASCII码) .所以肯定是 .WriteByte() 添加了额外的字符。 [这个说法是错误的 - 请参阅更新/解决方案]

奇怪的是 .WriteByte() 部分按预期工作:显然,它覆盖了文件中的第一个字节,即脚本第一行 [=57= 之后的 0x30 ], 0x31.

但是为什么它会添加其他三个字节,我该如何防止呢?我是否在 Powershell 中以错误的方式使用了 .NET 库(这里是 Powershell 新手......)? [这个问题无效,因为 .WriteByte() 没有添加其他三个字节 - 请参阅更新部分/ 解决方案]

更新/解决方案

Mathias R. Jessen 的回答完全正确。但是,我想简短地解释一下为什么我自己没有看到这个(尽管之前知道字节顺序标记):

我结合使用 Notepad++ 和十六进制编辑器插件来调查发生了什么。显然,当文件被更改时,这个组合在更新打开文件的 HEX 视图方面存在问题,这就是为什么它让我走错了路。这是我第二次遇到这个问题,所以我以后一定会使用其他 HEX 编辑器。

答案写好后,我重新调查了一下,这次用的是HxD,一下子就明白了。

长话短说:这实际上不是 System.IO.FileStream.WriteByte() 的问题,而是用于调查的工具的问题。

解决方案很简单:如果我想要文件中的一个字节,我可以使用 Out-File 和另一种编码,或者我可以使用 .WriteByte() 首先创建文件。

在 Windows PowerShell 中,Out-File 默认为 little-endian UTF-16 编码(在 Windows 中俗称 Unicode 编码)。

当您使用值 "0" 执行 Out-File 时,它会将以下字节序列写入磁盘文件:

ff fe 48 00
\___/ \___/
  |     | 
  |    UTF-16LE encoded "0"
UTF-16LE byte order mark

当你用[FileMode]::Open调用[File]::Open()时,它returns一个FileStream对象指向最低偏移量文件,因此 WriteByte(49) 最终会覆盖 byte-order 标记的第一部分:

49 fe 48 00
 |
Overwritten

如果您总是想覆盖现有文件中的任何数据,请使用 [FileMode]::Truncate:

$Stream = [System.IO.File]::Open("c:\batch\vss\test.txt",
                                 [System.IO.FileMode]::Open,
                                 [System.IO.FileAccess]::Truncate,
                                 [System.IO.FileShare]::ReadWrite)

如果您想截断文件以手动删除多余的数据,请使用 SetLength:

$Stream.SetLength(1)