This code adds lines, even when using "-NoNewline"
$LST1="OsName","OsVersion","TimeZone","CsName"
$LST2="CsManufacturer","CsModel","CsSystemType","BiosBIOSVersion","BiosReleaseDate"
$MEM1 = (Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum | Foreach {"{0:N2}" -f ([math]::round(($_.Sum / 1GB),2))})
$Pro1 = "systemname","DeviceID","numberOfCores","NumberOfLogicalProcessors"
Add-Content OutText.txt "OS Information:" -NoNewline
Get-ComputerInfo -Property $LST1 | Format-List | Out-File -Encoding ASCII -FilePath OutText.txt -Append
Add-Content OutText.txt "Hardware Information:" -NoNewline
Get-ComputerInfo -Property $LST2 | Format-List | Out-File -Encoding ASCII -FilePath OutText.txt -Append
Add-Content OutText.txt "RAM: $RAM1 GB" -NoNewline
Get-WmiObject -class win32_processor -Property $Pro1 | Select-Object -Property $Pro1 | Out-File -FilePath OutText.txt -Encoding ASCII -Append
Too many lines breaks:
Theo has provided the crucial pointer in a comment:
(Get-ComputerInfo -Property $LST1 | Format-List | Out-String).Trim() |
Add-Content -Path $path -NoNewline
Let me elaborate:
To prevent leading and trailing empty lines in the output of Format-List from showing up in a file via Out-File / >, use Out-String to create an in-memory string representation of the formatted output first,
which then allows you to apply .Trim() to the resulting multi-line string in order to remove leading and trailing lines (whitespace in general) from Out-String's output.
Since Out-String itself renders the formatting instructions output by Format-List, you can then use Set-Content or Add-Content to save / append the resulting string to a file.
The behavior of Out-String:
Out-String produces the same for-display representation that you get by default in the console - or via other Out-* cmdlets, notably Out-File / > - as a single, multi-line string by default.
While this representation may itself contain empty lines, as is typical, Out-String additionally appends a trailing newline, even though there's no good reason to do so, as discussed in GitHub issue #14444.
In cases where you want to remove this extraneous trailing newline only, you can use the following approach, via the -replace operator (the operation works with both Windows-style CRLF newlines (\r\n) and Unix-style LF-only ones (\n)):
(... | Out-String) -replace '\r?\n\z'
Or, less efficiently, using the -Stream switch to output lines individually and then re-join them with newlines without a trailing one ("`n" creates a LF-only newline, which PowerShell accepts interchangeably with CRLF newlines ("`r`n"):
(... | Out-String -Stream) -join "`n"
Out-String applied to output from external programs:
Out-String can also be used to capture the lines output by external programs as a single, multi-line string (by default, PowerShell captures output line by line, resulting in an array of strings when captured in a variable).
However, this use of Out-String is problematic:
There too the trailing newline that is appended can be a nuisance.
In Windows PowerShell there's an additional nuisance (which has since been corrected in PowerShell (Core) 7+): If you use a 2>&1 to merge stderr output into the success output stream, the first stderr line is formatted like a PowerShell error.
Run cmd /c 'echo yes & echo no >&2' 2>&1 | Out-String to see the problem.
The following idiom avoids both problems (... represents your external-program call):
$multiLineString = [string[]] (... 2>&1) -join "`n"
Note: The above uses a LF-only newline to join the array elements, which is usually sufficient. Use "`r`n" for CRLF newlines or [Environment]::NewLine for the OS-appropriate newline sequence.
Example:
The following cmd.exe CLI call outputs both a stdout line and a stderr line, with 2>&1 on the PowerShell side merging the two into the success output stream.
PS> [string[]] (cmd /c 'echo yes & echo no >&2' 2>&1) -join "`n" |
ForEach-Object { "[$_]" } # just to visualize the string boundaries
[yes
no ]
Note: The trailing space after no is owed to the unusual behavior of cmd.exe's built-in echo command: it includes the space before the >&2 redirection in its output.
I use the following to strip the Cr/Lf added by Out-String.
$YourVariableHere = $($YourVariableHere.Substring(0,($YourVariableHere.Length-2)))
You can adjust the number at the end if there is more than one Cr/Lf you want to remove.
HTH
Related
My original config file (web1.config) has no extra line and when viewed in notepad (showing all characters) looks as:
<?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:
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 IO 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.
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.
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.
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
My original config file (web1.config) has no extra line and when viewed in notepad (showing all characters) looks as:
<?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:
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 IO 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.
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.
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.
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
I'm getting extra characters and lines when trying to modify hosts files. For example, this select string does not take anything out, but the two files are different:
get-content -Encoding ascii C:\Windows\system32\drivers\etc\hosts |
select-string -Encoding ascii -notmatch "thereisnolinelikethis" |
out-file -Encoding ascii c:\temp\testfile
PS C:\temp> (get-filehash C:\windows\system32\drivers\etc\hosts).hash
C54C246D2941F02083B85CE2774D271BD574F905BABE030CC1BB41A479A9420E
PS C:\temp> (Get-FileHash C:\temp\testfile).hash
AC6A1134C0892AD3C5530E58759A09C73D8E0E818EC867C9203B9B54E4B83566
I can confirm that your commands do inexplicably result in extra line breaks in the output file, in the start and in the end. Powershell also converts the tabs in the original file into four spaces instead.
While I cannot explain why, these commands do the same thing without these issues:
Try this code instead:
Get-Content -Path C:\Windows\System32\drivers\etc\hosts -Encoding Ascii |
Where-Object { -not $_.Contains("thereisnolinelikethis") } |
Out-File -FilePath "c:\temp\testfile" -Encoding Ascii
I think this is more of an issue with PowerShell's F&O (formatting & output) engine. Keep in mind that Select-String outputs a rich object called MatchInfo. When that object reaches the end of the output it needs to be rendered to a string. I think it is that rendering/formatting that injects the extra line. One of the properties on MatchInfo is the line that was matched (or notmatched). If you pass just the Line property down the pipe, it seems to work better (hashes match):
Get-Content C:\Windows\system32\drivers\etc\hosts |
Select-String -notmatch "thereisnolinelikethis" |
Foreach {$_.Line} |
Out-File -Encoding ascii c:\temp\testfile
BTW you only need to specify ASCII encoding when outputting back to the file. Everywhere else in PowerShell, just let the string flow as Unicode.
All that said, I would use Where-Object instead of Select-String for this scenario. Where-Object is a filtering command which is what you want. Select-String takes input of one form (string) and converts it to a different object (MatchInfo).
Out-File adds a trailing NewLine ("`r`n") to the testfile file.
C:\Windows\System32\drivers\etc\hosts does not contain a trailing newline out of the box, which is why you get a different FileHash
If you open the files with a StreamReader, you'll see that the underlying stream differs in length (due to the trailing newline in the new file):
PS C:\> $Hosts = [System.IO.StreamReader]"C:\Windows\System32\drivers\etc\hosts"
PS C:\> $Tests = [System.IO.StreamReader]"C:\temp\testfile"
PS C:\> $Hosts.BaseStream.Length
822
PS C:\> $Tests.BaseStream.Length
824
PS C:\> $Tests.BaseStream.Position = 822; $Tests.Read(); $Tests.Read()
13
10
ASCII characters 13 (0x0D) and 10 (0x0A) correspond to [System.Environment]::NewLine or CR+LF
I'm trying to tee a server's output to both the console and a file in Powershell 4. The file is ending up with a UTF-16 encoding, which is incompatible with some other tools I'm using. According to help tee -full:
Tee-Object uses Unicode enocding when it writes to files.
...
To specify the encoding, use the Out-File cmdlet
So tee doesn't support changing encoding, and the help for both tee and Out-File don't show any examples of splitting a stream and encoding it with UTF-8.
Is there a simple way in Powershell 4 to tee (or otherwise split a stream) to a file with UTF-8 encoding?
One option is to use Add-Content or Set-Content instead of Out-File.
The *-Content cmdlets use ASCII encoding by default, and have a -Passthru switch so you can write to the file, and then have the input pass through to the console:
Get-Childitem -Name | Set-Content file.txt -Passthru
You would have to use -Variable and then write it out to a file in a separate step.
$data = $null
Get-Process | Tee-Object -Variable data
$data | Out-File -Path $path -Encoding Utf8
At first glance it seems like it's easier to avoid tee altogether and just capture the output in a variable, then write it to the screen and to a file.
But because of the way the pipeline works, this method allows for a long running pipeline to display data on screen as it goes along. Unfortunately the same cannot be said for the file, which won't be written until afterwards.
Doing Both
An alternative is to roll your own tee so to speak:
[String]::Empty | Out-File -Path $path # initialize the file since we're appending later
Get-Process | ForEach-Object {
$_ | Out-File $path -Append -Encoding Utf
$_
}
That will write to the file and back to the pipeline, and it will happen as it goes along. It's probably quite slow though.
Tee-object seems to invoke out-file, so this will make tee output utf8:
$PSDefaultParameterValues = #{'Out-File:Encoding' = 'utf8'}
First create the file using appropriate flags then append to it:
Set-Content out $null -Encoding Unicode
...
cmd1 | tee out -Append
...
cmdn | tee out -Append
I'm outputting the contents of a directory to a txt file using the following command:
$SearchPath="c:\searchpath"
$Outpath="c:\outpath"
Get-ChildItem "$SearchPath" -Recurse | where {!$_.psiscontainer} | Format-Wide -Column 1'
| Out-File "$OutPath\Contents.txt" -Encoding ASCII -Width 200
What I end up with when I do this is a txt file with the information I need, but it adds numerous carriage returns I don't need, making the output harder to read.
This is what it looks like:
c:\searchpath\directory
name of file.txt
name of another file.txt
c:\searchpath\another directory
name of some file.txt
That makes a txt file that requires a lot of scrolling, but the actual information isn't that much, usually a lot less than a hundred lines.
I would like for it to look like:
c:\searchpath\directory
nameoffile.txt
c:\searchpath\another directory
another file.txt
This is what I've tried so far, not working
$configFiles=get-childitem "c:\outpath\*.txt" -rec
foreach ($file in $configFiles)
{
(Get-Content $file.PSPath) |
Foreach-Object {$_ -replace "'n", ""} |
Set-Content $file.PSPath
}
I've also tried 'r but both options leave the file unchanged.
Another attempt:
Select-String -Pattern "\w" -Path 'c:\outpath\contents.txt' | foreach {$_.line}'
| Set-Content -Path c:\outpath\contents2.txt
When I run that string without the Set-content at the end, it appears exactly as I need it in the ISE, but as soon as I add the Set-Content at the end, it once agains carriage returns where I don't need them.
Here's something interesting, if I create a text file with a few carriage returns and a few tabs, then if I use the same -replace script I've been using, but uset to replace the tabs, it works perfect. Butr and n do not work. It's almost as though it doesn't recognize them as escape characters. But if I addr and `n in the txt file then run the script, it still doesn't replace anything. Doesn't seem to know what to do with it.
Set-Content adds newlines by default. Replacing Set-Content by Out-File in your last attempt in your question will give you the file you want:
Select-String -Pattern "\w" -Path 'c:\outpath\contents.txt' | foreach {$_.line} |
Out-File -FilePath c:\outpath\contents2.txt
It's not 'r (apostrophe), it's a back tick: `r. That's the key above the tab key on the US keyboard layout. :)
You can simply avoid all those empty lines by using Select-Object -ExpandProperty Name:
Get-ChildItem "$SearchPath" -Recurse |
Where { !$_.PSIsContainer } |
Select-Object -ExpandProperty Name |
Out-File "$OutPath\Contents.txt" -Encoding ASCII -Width 200
... if you don't need the folder names.