52

My original config file (web1.config) has no extra line and when viewed in notepad (showing all characters) looks as:

enter image description here

 <?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.6" />
    <httpRuntime targetFramework="4.6" />
  </system.web>
  <appSettings>
    <add key="myConnectionString" value="server=localhost;database=myDb;uid=myUser;password=myPass;" />
  </appSettings>
</configuration>

Now, I need to apply the script to change my database name to something else which looks like:

 Move-Item "web1.config" "webtemp.config"
Get-Content "webtemp.config" | ForEach-Object {$_ -replace "database=myDb;", "database=newDb;"} |Set-Content "web1.config" -Force
Remove-Item "webtemp.config"
Write-Output('Settings Changed')

So, the new file (web1.config) generated looks as:

enter image description here

Notice the extra line added at the end of the file (which is completely not needed) I tried all other options such as:

  • using out-file api
  • using .NET method System.IO.StreamWriter
  • using -nonewline flag (it converts all 10 lines into single line)
  • using different encoding options
  • tried replacing \r\n to \r (don't work as again set-content generates the crlf always)

I'm using PowerShell v5.1.

Ondrej Janacek
  • 12,486
  • 14
  • 59
  • 93
OmGanesh
  • 952
  • 1
  • 12
  • 24

3 Answers3

66

tl;dr (PSv5+; see bottom for older versions):

(Get-Content webtemp.config) -replace 'database=myDb;', 'database=newDb;' -join "`n" |
  Set-Content -NoNewline -Force web1.config

Note: Replace "`n" with "`r`n" if you want Windows-style CRLF line endings rather than Unix-style LF-only line endings (PowerShell and many utilities can handle both).


In PSv5+, Set-Content supports the -NoNewline switch, which instructs Set-Content not to add a newline (line break) after each input object. The same applies analogously to the Add-Content and Out-File cmdlets.

In other words: Set-Content -NoNewline directly concatenates the string representations of all its input objects:

PS> 'one', 'two' | Set-Content -NoNewline tmp.txt; Get-Content tmp.txt
onetwo

If what you're passing to Set-Content -NoNewline is a single string that already has embedded newlines, you can use it as-is and get the desired result:

PS> "one`ntwo" | Set-Content -NoNewline tmp.txt; "$(Get-Content -Raw tmp.txt)?"
one
two?

Note that Get-Content -Raw reads the file as a whole, as-is (aside from character decoding) and the fact that the ? appears directly after two implies that the file has no trailing newline.

In your case, since you're processing input lines one by one (via Get-Content without -Raw) and therefore outputting an array of lines (strings), you must first join them with a newline as the separator - between lines only - and pass the result to Set-Content -NoNewline, as shown at the top; here's a simplified example:

PS> ('one', 'two') -join "`n" | Set-Content -NoNewline tmp.txt; "$(Get-Content -Raw tmp.txt)?"
one
two?

'one', 'two' is a two-element string array that is a stand-in for your line-by-line processing command.

Encoding note:

In Windows PowerShell, Set-Content produces "ANSI"-encoded files by default, based on your system's legacy, single-byte code page.
To control the encoding explicitly, use the -Encoding parameter.


In PSv4-, a solution that uses the .NET Framework is needed:

PS> [System.IO.File]::WriteAllText('tmp.txt', ('one', 'two') -join "`n"); "$(Get-Content -Raw tmp.txt)?"
one
two?

Note that [System.IO.File]::WriteAllText(), in the absence of an encoding argument, defaults to BOM-less UTF-8.
Pass the desired [System.Text.Encoding] encoding instance as the 3rd argument as needed.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 3
    Powershell has some interesting design decisions... Like, under what circumstances would anyone want a newline appended? – usr Jan 09 '22 at 10:23
4

I never noticed this, so i did a quick search and found:

set-content adds newlines by default

The suggested solution is to encode your content to bytes and then use Set-Content with the -Encoding parameter.

Set-Content test.txt ([byte[]][char[]] "test") -Encoding Byte

I tested it myself so i can confirm that this works.

Prid
  • 1,272
  • 16
  • 20
SteloNLD
  • 541
  • 3
  • 12
  • 1
    Nice finding/answer +1. Just to mention this works the same on PowerShell 6.0.0beta4 on Linux/Darwin aside from appending only lf `0A` –  Jul 23 '17 at 17:49
  • 1
    This solution comes with a caveat: it essentially creates a single-byte ISO-8859-1-encoded file (ISO-8859-1 is largely, but not fully compatible with the most widely used legacy Windows code page, Windows-1252), which means that (a) you can't select a different output encoding and (b) the solution breaks with strings that contain characters that are not part of ISO-8859-1, such as `€`: The following command _breaks_, for instance: `Set-Content test.txt ([byte[]][char[]] "€") -Encoding Byte` – mklement0 Jul 23 '17 at 20:05
  • 2
    @LotPings: This solution doesn't append _anything_, which is the point (by contrast, any line breaks _already present in the input_ are likely to be LF-only on a Unix platform). However, as stated, this solution is problematic from an encoding perspective. – mklement0 Jul 23 '17 at 20:13
  • 1
    @mklement0 unlucky wording, difference is just the system specific line termination. –  Jul 23 '17 at 20:30
0

I see that's an xml file. This way doesn't add a newline.

[xml]$xml = get-content web1.config
$xml.configuration.appSettings.add.value = 
  $xml.configuration.appSettings.add.value -replace 'database=myDb;',
  'database=newDb;'
$xml.save("$pwd\web1.config")  # in case of .net weirdness
js2010
  • 23,033
  • 6
  • 64
  • 66