Cannot write help text to a file in PowerShell - powershell

I was trying to write help text to a file with
Set-Content -path "help.txt" -Value $(help -Full "help")
Then I found that help cmdlet generates an object rather than text.
But simply adding toString() at the end does not work either.
So how can I get clean text from help command and write it to file using Set-Content?

In order to capture output as it would print on the screen, use either output redirection operator >, or pipe to cmdlet Out-File, which is required if you want to use an output character encoding other than the default, UTF-16 LE:
help -full help > help.txt # invariably creates a UTF-16 LE file
help -full help | Out-File help.txt # equivalent, but supports -Encoding <name>
By contrast, Set-Content:
does not use PowerShell's default output formatting; instead, it applies (at least conceptually) a .ToString() call to each input object, which may or may not give a meaningful representation.
creates ASCII files by default, but, like Out-File, it supports different encodings via the
-Encoding parameter.

Related

Why is PS Get-ChildItem so difficult

I did a ton of reading and searching about a way to have Get-ChildItem return a dir listing in wide format, in alphabetical order, with the number of files and directories in the current directory. Here is a image of what I ended up with, but not using GCI.
I ended up writing a small PS file.
$bArgs = "--%/c"
$cArgs = "Dir /n/w"
& cmd.exe -ArgumentList $bArgs $cArgs
As you can see I ended up using the old cmd.exe and passing the variables I wanted. I made an alias in my PS $Profile to call this script.
Can this not be accomplished in PS v5.1? Thanks for any help or advice for an old noob.
PowerShell's for-display formatting differs from cmd.exe's, so if you want the formatting of the latter's internal dir command, you'll indeed have to call it via cmd /c, via a function you can place in your $PROFILE file (note that aliases in PowerShell are merely alternative names and can therefore not include baked-in arguments):
function lss { cmd /c dir /n /w /c $args }
Note that you lose a key benefit of PowerShell: the ability to process rich objects:
PowerShell-native commands output rich objects that enable robust programmatic processing; e.g., Get-ChildItem outputs System.IO.FileInfo and System.IO.DirectoryInfo instances; the aspect of for-display formatting is decoupled from the data output, and for-display formatting only kicks in when printing to the display (host), or when explicitly requested.
For instance, (Get-ChildItem -File).Name returns an array of all file names in the current directory.
By contrast, PowerShell can only use text to communicate with external programs, which makes processing cumbersome and brittle, if information must be extracted via text parsing.
As Pierre-Alain Vigeant notes, the following PowerShell command gives you at least similar output formatting as your dir command, though it lacks the combined-size and bytes-free summary at the bottom:
Get-ChildItem | Format-Wide -AutoSize
To wrap that up in a function, use:
function lss { Get-ChildItem #args | Format-Wide -Autosize }
Note, however, that - due to use of a Format-* cmdlet, all of which output objects that are formatting instructions rather than data - this function's output is also not suited to further programmatic processing.
A proper solution would require you to author custom formatting data and associate them with the System.IO.FileInfo and System.IO.DirectoryInfo types, which is nontrivial however.
See the conceptual about_Format.ps1xml help topic, Export-FormatData, Update-FormatData, and this answer for a simple example.

Powershell's Out-File adds a newline to the Top of the file - Out-File vs. Set-Content

I have the following powershell:
# Find all .csproj files
$csProjFiles = get-childitem ./ -include *.csproj -recurse
# Remove the packages.config include from the csproj files.
$csProjFiles | foreach ($_) {(get-content $_) |
select-string -pattern '<None Include="packages.config" />' -notmatch |
Out-File $_ -force}
And it seems to work fine. The line with the packages.config is not in the file after I run.
But after I run there is an extra newline at that TOP of the file. (Not the bottom.)
I am confused as to how that is getting there. What can I do to get rid of the extra newline char that this generates at the top of the file?
UPDATE:
I swapped out to a different way of doing this:
$csProjFiles | foreach ($_) {$currentFile = $_; (get-content $_) |
Where-Object {$_ -notmatch '<None Include="packages.config" />'} |
Set-Content $currentFile -force}
It works fine and does not have the extra line at the top of the file. But I wouldn't mind knowing why the top example was adding the extra line.
Out-File and redirection operators > / >> take arbitrary input objects and convert them to string representations as they would present in the console - that is, PowerShell's default output formatting is applied - and sends those string representations to the output file.
These string representations often have leading and/or trailing newlines for readability.
See Get-Help about_Format.ps1xml to learn more.
Set-Content is for input objects that are already strings or should be treated as strings.
PowerShell calls .psobject.ToString() on all input objects to obtain the string representation, which in most cases defers to the underlying .NET type's .ToString() method.
The resulting representations are typically not the same, and it's important to know when to choose which cmdlet / operator.
Additionally, the default character encodings differ:
Out-File and > / >> default to UTF-16 LE, which PowerShell calls Unicode in the context of the optional -Encoding parameter.
Set-Content defaults to your system's legacy "ANSI" code page (a single-byte, extended-ASCII code page), which PowerShell calls Default.
Note that the the docs as of PSv5.1 mistakenly claim that the default is ASCII.[1]
To change the encoding:
Ad-hoc change: Use the -Encoding parameter with Out-File or Set-Content to control the output character encoding explicitly.
You cannot change the encoding used by > / >> ad-hoc, but see below.
[PSv3+] Changing the default (use with caution): Use the $PSDefaultParameterValues mechanism (see Get-Help about_Parameters_DefaultValues), which enables setting default values for parameters:
Changing the default encoding for Out-File also changes it for > / >> in PSv5.1 or above[2].
To change it to UTF-8, for instance, use:
$PSDefaultParameterValues['Out-File:Encoding']='UTF8'
Note that in PSv5.0 or below you cannot change what encoding > and >> use.
If you change the default for Set-Content, be sure to change it for Add-Content too:
$PSDefaultParameterValues['Set-Content:Encoding'] = $PSDefaultParameterValues['Add-Content:Encoding'] ='UTF8'
You can also use wildcard patterns to represent the cmdlet / advanced function name to apply the default parameter value to; for instance, if you used $PSDefaultParameterValues['*:Encoding']='UTF8', then all cmdlets that have an -Encoding parameter would default to that value, but that is ill-advised, because in some cmdlets the -Encoding refers to the input encoding.
There is no single shared prefix among cmdlets that write to files that allows you to target all output cmdlets, but you can define a pattern for each of the verbs:
$enc = 'UTF8; $PSDefaultParameterValues += #{ 'Out-*:Encoding'=$enc; 'Set-*:Encoding'=$enc; 'Add-*:Encoding'=$enc; 'Export-*:Encoding'=$enc }
Caveat: $PSDefaultParameterValues is defined in the global scope, so any modifications you make to it take effect globally, and affect subsequent commands.
To limit changes to a script / function's scope and its descendent scopes, use a local $PSDefaultParameterValues variable, which you can either initialize to an empty hashtable to start from scratch ($PSDefaultParameterValues = #{}), or initialize to a clone of the global value ($PSDefaultParameterValues = $PSDefaultParameterValues.Clone())
Caveats:
Using the utf8 encoding in Windows PowerShell invariably creates UTF-8 files with a BOM. (Commendably, in PowerShell [Core] v6+ it does not, and this edition even consistently defaults to BOM-less UTF-8; however, you can create a BOM on demand with utf8BOM.
However, if you're running Windows 10 and you're willing to switch to BOM-less UTF-8 encoding system-wide - which can have side effects - even Windows PowerShell can be made to use BOM-less UTF-8 consistently - see this answer.
In the case at hand, the output objects are [Microsoft.PowerShell.Commands.MatchInfo] instances output by Select-String:
Using default formatting, as happens with Out-File, they output an empty line above, and two empty lines below (with multiple instances printing in a contiguous block between a single set of the empty lines above and below).
If you call .psobject.ToString() on them, as happens with Set-File, they evaluate to just the matching lines (with no origin-path prefix, given that input was provided via the pipeline rather than as filenames via the -Path / -LiteralPath parameters), with no leading or trailing empty lines.
That said, had you piped to | Select-Object -ExpandProperty Line or simply | ForEach-Object Line in order to explicitly output just the matching lines as strings, both Out-File and Set-Content would have yielded the same result (except for their default encoding).
P.S.: LotPing's observation is correct: You seem to be confusing the foreach statement with the ForEach-Object cmdlet (which, regrettably, is also known by built-in alias foreach, causing confusion).
The ForEach-Object cmdlet doesn't need an explicit definition for $_: in the (implied -Process) script block you pass to it, $_ is automatically defined to be the input object at hand.
Your ($_) argument to foreach (ForEach-Object) is effectively ignored: because it evaluates to $null: automatic variable $_, when used outside of special contexts - such as script blocks in the pipeline - effectively evaluates to $null, and putting (...) around it makes no difference, so you're effectively passing $null, which is ignored.
[1] Verify that ASCII is not the default as follows: '0x{0:x}' -f $('ä' | Set-Content t.txt; $b=[System.IO.File]::ReadAllBytes("$PWD\t.txt")[0]; ri t.txt; $b) yields 0xe4 on an en-US system, which is the Windows-1252 code point for ä (which coincides with the Unicode codepoint, but the output is a single-byte-encoded file with no BOM).
If you use -Encoding ASCII explicitly, you get 0x3f, the code point for literal ?, because that's what using ASCII converts all non-ASCII chars. to.
[2] PetSerAl found the source-code location that shows that > and >> are effective aliases for Out-File [-Append], and he points out that redefining Out-File therefore also redefines > / >>; similarly, specifying a default encoding via $PSDefaultParameterValues for Out-File also takes effect for > / >>.
Windows PowerShell v5.1 is the minimum version that works this way..
Tip of the hat to PetSerAl for his help.

Override Powershell > shortcut

In Powershell using > is the same as using | Out-File, so I can write
"something" > file.txt and It will write 'something' into file.txt . This is what I expect of a shell. Unfortunately, Powershell uses Unicode for writing file.txt. The only way to change it into UTF-8 is to write the quite long command:
"something" | Out-File file.txt -Encoding UTF8
I want to override the > shortcut, so that it adds the UTF-8 encoding by default. Is there a way to do that?
NOT A DUPLICATE CLARIFICATION:
This is not a duplicate. As is explained clearly here, Out-File has a hard-coded default. I don't want to change Out-File's behavior, I want to change >'s behavior.
No, can't be done
Even the documentation alludes to this.
From the last paragraph of Get-Help about_Redirection:
When you are
writing to files, the redirection operators use Unicode encoding. If
the file has a different encoding, the output might not be formatted
correctly. To redirect content to non-Unicode files, use the Out-File
cmdlet with its Encoding parameter.
(emphasis added)
The output encoding can be overriden by changing the $OutputEncoding variable. However, that only works for piping output into executables. It doesn't work for redirection operators. If you need a specific encoding for file output you must use Out-File or Set-Content with the -Encoding parameter (or a StreamWriter).

Output ("echo") a variable to a text file

I'm running a PowerShell script against many servers, and it is logging output to a text file.
I'd like to capture the server the script is currently running on. So far I have:
$file = "\\server\share\file.txt"
$computername = $env:computername
$computername | Add-Content -Path $file
This last line adds question marks in the output file. Oops.
How do I output a variable to a text file in PowerShell?
The simplest Hello World example...
$hello = "Hello World"
$hello | Out-File c:\debug.txt
Note: The answer below is written from the perspective of Windows PowerShell.
However, it applies to the cross-platform PowerShell (Core) v6+ as well, except that the latter - commendably - consistently defaults to BOM-less UTF-8 as the character encoding, which is the most widely compatible one across platforms and cultures..
To complement bigtv's helpful answer helpful answer with a more concise alternative and background information:
# > $file is effectively the same as | Out-File $file
# Objects are written the same way they display in the console.
# Default character encoding is UTF-16LE (mostly 2 bytes per char.), with BOM.
# Use Out-File -Encoding <name> to change the encoding.
$env:computername > $file
# Set-Content calls .ToString() on each object to output.
# Default character encoding is "ANSI" (culture-specific, single-byte).
# Use Set-Content -Encoding <name> to change the encoding.
# Use Set-Content rather than Add-Content; the latter is for *appending* to a file.
$env:computername | Set-Content $file
When outputting to a text file, you have 2 fundamental choices that use different object representations and, in Windows PowerShell (as opposed to PowerShell Core), also employ different default character encodings:
Out-File (or >) / Out-File -Append (or >>):
Suitable for output objects of any type, because PowerShell's default output formatting is applied to the output objects.
In other words: you get the same output as when printing to the console.
The default encoding, which can be changed with the -Encoding parameter, is Unicode, which is UTF-16LE in which most characters are encoded as 2 bytes. The advantage of a Unicode encoding such as UTF-16LE is that it is a global alphabet, capable of encoding all characters from all human languages.
In PSv5.1+, you can change the encoding used by > and >>, via the $PSDefaultParameterValues preference variable, taking advantage of the fact that > and >> are now effectively aliases of Out-File and Out-File -Append. To change to UTF-8 (invariably with a BOM, in Windows PowerShell), for instance, use:
$PSDefaultParameterValues['Out-File:Encoding']='UTF8'
Set-Content / Add-Content:
For writing strings and instances of types known to have meaningful string representations, such as the .NET primitive data types (Booleans, integers, ...).
.psobject.ToString() method is called on each output object, which results in meaningless representations for types that don't explicitly implement a meaningful representation; [hashtable] instances are an example:
#{ one = 1 } | Set-Content t.txt writes literal System.Collections.Hashtable to t.txt, which is the result of #{ one = 1 }.ToString().
The default encoding, which can be changed with the -Encoding parameter, is Default, which is the system's active ANSI code page, i.e. the single-byte culture-specific legacy encoding for non-Unicode applications, which is most commonly Windows-1252.
Note that the documentation currently incorrectly claims that ASCII is the default encoding.
Note that Add-Content's purpose is to append content to an existing file, and it is only equivalent to Set-Content if the target file doesn't exist yet.
If the file exists and is nonempty, Add-Content tries to match the existing encoding.
Out-File / > / Set-Content / Add-Content all act culture-sensitively, i.e., they produce representations suitable for the current culture (locale), if available (though custom formatting data is free to define its own, culture-invariant representation - see Get-Help about_format.ps1xml).
This contrasts with PowerShell's string expansion (string interpolation in double-quoted strings), which is culture-invariant - see this answer of mine.
As for performance:
Since Set-Content doesn't have to apply default formatting to its input, it performs better, and therefore is the preferred choice if your input is composed of strings and/or of objects whose default stringification via the standard .NET .ToString() method is sufficient.
As for the OP's symptom with Add-Content:
Since $env:COMPUTERNAME cannot contain non-ASCII characters (or verbatim ? characters), Add-Content's addition to the file should not result in ? characters, and the likeliest explanation is that the ? instances were part of the preexisting content in output file $file, which Add-Content appended to.
After some trial and error, I found that
$computername = $env:computername
works to get a computer name, but sending $computername to a file via Add-Content doesn't work.
I also tried $computername.Value.
Instead, if I use
$computername = get-content env:computername
I can send it to a text file using
$computername | Out-File $file
Your sample code seems to be OK. Thus, the root problem needs to be dug up somehow. Let's eliminate chance for typos in the script. First off, make sure you put Set-Strictmode -Version 2.0 in the beginning of your script. This will help you to catch misspelled variable names. Like so,
# Test.ps1
set-strictmode -version 2.0 # Comment this line and no error will be reported.
$foo = "bar"
set-content -path ./test.txt -value $fo # Error! Should be "$foo"
PS C:\temp> .\test.ps1
The variable '$fo' cannot be retrieved because it has not been set.
At C:\temp\test.ps1:3 char:40
+ set-content -path ./test.txt -value $fo <<<<
+ CategoryInfo : InvalidOperation: (fo:Token) [], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefined
The next part about question marks sounds like you have a problem with Unicode. What's the output when you type the file with Powershell like so,
$file = "\\server\share\file.txt"
cat $file
Here is an easy one:
$myVar > "c:\myfilepath\myfilename.myextension"
You can also try:
Get-content "c:\someOtherPath\someOtherFile.myextension" > "c:\myfilepath\myfilename.myextension"

Powershell generates .bat, and put special character

I'm currently working with powershell in order to create a .bat script.
I put text in .bat script with >>
For example,
Write "start program xxx" >> script.bat
but when i try to execute this script.bat with cmd, it says :
"■s" is not recognize ... etc.
And in powershell it says : 'þp' is not recognize ..
So I guess that doing >> script put special character at the beginning of the line. If someone got information on this. And what those "■s" and 'þp' are.
The file redirection operators (>> etc.) will write text encoded in UTF-16. If the file already contains text in a different encoding everything will be confused (and I'm not use of cmd.exe understands UTF-16 at all.
Easier to use Out-File with the -encoding parameter to specify something consistent. Use the -append switch parameter to append rather than overwriting.
Eg.
"Some text" | Out-File -encoding ASCII -append -FilePath 'script.bat`
(If you find yourself writing the same out-file and parameters, then put it in a helper advanced function that will read pipeline input to encapsulate the out-file.)