How to create System.IO.StreamWriter with Encoding in Powershell?
I'm trying to create instance of StreamWriter with UTF8 encoding in PowerShell.
$f = New-Object System.IO.StreamWriter "a.txt", $false, [System.Text.Encoding]::UTF8
This throws error: New-Object : Cannot find an overload for "StreamWriter" and the argument count: "3".
I'm trying to invoke this constructor: https://msdn.microsoft.com/en-us/library/f5f5x7kt(v=vs.110).aspx
Still, I would like to know what is wrong with my original syntax.
Your original syntax (fundamentally correctly) uses argument mode, in which arguments are, loosely speaking, evaluated as follows:
- An argument that doesn't start with either
$
,(
, or@
is treated as a string, even if not quoted; notably,[
is not among these special characters.
Therefore, [System.Text.Encoding]::UTF8
is interpreted as a string literal rather than as an expression that returns an System.Text.Encoding
instance, and no System.IO.StreamWriter
constructor whose 3rd argument is a string can be found.
- Unfortunately, the error message only mentions the count of arguments, without indicating that an incorrect type might be the cause; this is a known problem - see this GitHub issue.
The correct solution, as mentioned in a comment by PetSerAl, is to enclose [System.Text.Encoding]::UTF8
in (...)
so as to force its evaluation in expression mode, where it yields the desired result.
Note that the above also implies that the "..."
(double quotes) around a.txt
aren't necessary (but do no harm), so we get:
Note: For brevity, I've omitted the initial System.
components from the full types in the following sample commands; e.g., IO.StreamWriter
refers to System.IO.StreamWriter
. Specifying the System.
part is optional in PowerShell in most contexts.
$f = New-Object IO.StreamWriter a.txt, $false, ([Text.Encoding]::UTF8)
Note that it is the ,
between the individual constructor arguments that causes them to be passed as an array - i.e., a single argument - to New-Object
, where it is (positionally) bound to the array-typed -ArgumentList
(-Args
) parameter.
As an aside: passing individual arguments positionally to separate parameters is more common, and requires spaces to separate them; e.g., Select-String foo t.txt
is parsed asSelect-String -Pattern foo -Path t.txt
.
Your own answer (since deleted) uses pseudo method syntax that is best avoided and only happens to work:
# AVOID: pseudo method syntax.
$f = New-Object IO.StreamWriter("a.txt", $false, [Text.Encoding]::UTF8)
Even though this looks like a method call (constructor call), it isn't, and is actually parsed as follows:
$f = New-Object IO.StreamWriter -ArgumentList ("a.txt", $false, [Text.Encoding]::UTF8)
That is, you've enclosed the original argument array in (...)
, which causes its elements to be parsed in expression mode, including [Text.Encoding]::UTF8
, which happened to solve your problem.
Note that - unlike in argument mode - string a.txt
does have to be enclosed in "..."
(or '...'
) in expression mode.
As an aside:
-
Set-StrictMode
-Version 2
or higher, among other things, prevents using pseudo-method syntax, but only (a) for two or more arguments and (b) only if the argument list ((...)
) follows a command name (rather than an argument, as in theNew-Object
case), and (c) only if there is no space between the command name and the opening(
; e.g.:& { Set-Strictmode -version 2; foo('a', 'b') }
Note that PSv5+ does offer a method-based way to construct objects, via the static new()
method exposed on type-information objects, in which case all arguments are parsed in expression mode:
# PowerShell version 5 and above; you can use the ::new() method on types.
$f = [IO.StreamWriter]::new("a.txt", $false, [Text.Encoding]::UTF8)
A note on when [System.Text.Encoding]::UTF8
is needed:
Unlike Window PowerShell, .NET defaults to UTF-8 (which PowerShell [Core] (v6+) now does too).
-
When you read data, you therefore normally don't need to request UTF-8 encoding explicitly.
-
When you write data, passing
[System.Text.Encoding]::UTF8
results in a UTF-8 file with a BOM, whereas relying on the default UTF-8 encoding creates a file without a BOM (which is better for cross-platform interoperabiliy); to request a BOM-less encoding explicitly, use[System.Text.Utf8Encoding]::new()
.