Home > Software engineering >  Why does [IO.File]::WriteAllText concatenate the Get-Content -tail ## output, and Set-Content does n
Why does [IO.File]::WriteAllText concatenate the Get-Content -tail ## output, and Set-Content does n

Time:11-26

If this is a duplicate question for this specific issue, forgive me and please kindly point me in the right direction - I have not seen this in Stack Overflow so far.

I'm using PowerShell to run a set of commands as follows to find 'NUL' char's in a file. (-replace works just fine - this is not my problem)

$findString = '\x00'
$replString = 'DERPS'
$foo = [IO.File]::ReadAllText("C:\SomeFile.txt")
$bar = $foo -replace $findString, $replString
[IO.File]::WriteAllText("C:\SomeFile_2.txt", $bar)
$Lines = Get-Content "C:\SomeFile_2.txt" -tail 10
#I have also used the following with the same effect:
#$Lines = Get-Content "C:\SomeFile_2.txt" |Select-Object -last 10
[IO.File]::WriteAllText("C:\FOOBARRED.txt", $Lines)
Set-Content "C:\NOT_FOOBARRED.txt" $Lines

What I'm experiencing is with [IO.File] it concatenates the last 10 lines into one string:

Line 1 of 10 Line 2 of 10 Line 3 of 10 Line 4 of 10 Line 5 of 10 Line 6 of 10 Line 7 of 10 Line 8 of 10 Line 9 of 10 DERPSDERPSDERPSDERPSDERPS

With Set-Content my output looks correct:

Line 1 of 10
Line 2 of 10
Line 3 of 10
Line 4 of 10
Line 5 of 10 
Line 6 of 10 
Line 7 of 10
Line 8 of 10
Line 9 of 10
DERPSDERPSDERPSDERPSDERPS

Can anyone explain what the difference is? Is it correct to assume the "WriteAllText" Command works when it's a raw format, but if it's processed in anyway it will accept the data as one large blob?

CodePudding user response:

[IO.File]::WriteAllText() expects a single (potentially multiline) string as the argument to its contents parameter.

By contrast, Get-Content -Tail 10 returns an array of strings (when captured in a variable), each containing a line from the file with the trailing newline stripped.

PowerShell performs many automatic type conversions, and while they're usually helpful, sometimes they're not: here, it automatically converts the array to a single-line string by concatenating the elements with a single space each[1], as the following example demonstrates:

PS> "$('line1', 'line2')" # stringify an array
line1 line2  # elements were joined with a space

This automatic stringification of the array stored in $Lines in the context of your [IO.File]::WriteAllText() call caused your problem.

Therefore, use [IO.File]::WriteAllLines() instead (note: Lines rather than Text), which expects an array of strings and writes its elements as lines to the file, terminated with a (platform-native) newline each.

In other words: It acts like Set-Content when given an array of strings, though note that in Windows PowerShell you'll end up with an ANSI-encoded file by default with Set-Content, whereas .NET - and now PowerShell (Core) 7 , consistently - default to BOM-less UTF-8.


As an aside:

  • A PowerShell alternative to [IO.File]::ReadAllText() is to use Get-Content with the -Raw switch.

  • You're already aware of Set-Content as the PowerShell alternative to [IO.File]::WriteAllLines(). It can also act as an alternative to [IO.File]::WriteAllText() if you pass it a single, multiline string and also specify the
    -NoNewline switch.


[1] A space character is the default, which you can override via the $OFS preference variable, though that is rarely seen in practice.

  • Related