Search code examples
powershellpowershell-3.0

Code executed through Invoke-Command throws error


When I run

Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Format-Table –AutoSize

directly from PowerShell, the code works just fine.

However, when I placed this exact code into file script.txt and then run

$stdout = Invoke-Command -ScriptBlock ([ScriptBlock]::Create((Get-Content 'script.txt'))) *>&1 

I received the following error:

Exception calling "Create" with "1" argument(s): "At line:1 char:163
+ ... me, DisplayVersion, Publisher, InstallDate | Format-Table –AutoSize
+                                                                 ~~~~~~~~~
The string is missing the terminator: "."

What causes this behavior? How do I have to adjust the code to be able to execute the script from the external txt file?


Solution

  • tl;dr

    The implication is that you're using Windows PowerShell and that there is a character-encoding problem.
    (The problem would not arise in PowerShell (Core) 7+, whose consistent default encoding is now (BOM-less) UTF-8.)

    You have two solution options:

    • Either: Re-save your script.txt file as UTF-8 with BOM.

    • Or: Use -Encoding utf8 with your Get-Content call (note the bug alert below):

    # Note the addition of -Encoding utf8.
    # -Raw is added for robustness; it would be needed for *multiline* scripts.
    Invoke-Command -ScriptBlock (
       [ScriptBlock]::Create(
         (Get-Content -Encoding utf8 -Raw 'script.txt')
       )
     ) *>&1 
    

    A simpler alternative is to use Invoke-Expression (whose use is generally best avoided, however):

    Get-Content -Encoding utf8 -Raw 'script.txt' | Invoke-Expression *>&1
    

    Bug alert:

    When combining the above commends with redirection *>&1, error output, specifically, disappears, i.e. it unexpectedly isn't merged into the success output stream (1).

    This applies to both Windows PowerShell (where it won't be fixed) and PowerShell (Core) up to at least v7.3.6; the relevant bug report is GitHub issue #10476.

    A workaround is to enclose the Invoke-Command call or the Invoke-Expression pipeline in (...), the grouping operator before applying *>&1, e.g.:

    # Note the (...), which has side effects, however.
    (Get-Content -Encoding utf8 -Raw 'script.txt' | Invoke-Expression) *>&1
    

    The workaround has a side effect: Using (...) means that all output from the enclosed command is collected in memory first, in full, before the redirection is applied and output is produced.


    Background information:
    • It looks like your script.txt is UTF-8-encoded but lacks a BOM, which causes Windows PowerShell to misinterpret your file as ANSI-encoded (i.e., using the single-byte ANSI code page of your system's legacy locale).

    • It isn't obvious but your file contains a non-ASCII-range character:

      • The in –AutoSize is the EN DASH, U+2013 character, not the usual ASCII-range -, - (HYPHEN-MINUS, U+002D) character.

      • When the 3-byte UTF-8 encoding of is misinterpreted as ANSI, each byte becomes a character in its own right, specifically: –

    It is the (LEFT DOUBLE QUOTATION MARK, U+201C) - which PowerShell accepts as an alternative to the usual ASCII-range double quote, QUOTATION MARK, U+0022 - that causes the syntax error, because a closing double quote is missing.

    Similarly, PowerShell accepts - (EN DASH) in lieu of the ASCII-range -.

    For the complete list of interchangeable quotation and punctuation characters with syntactic function supported by PowerShell, see this answer.

    However, for robustness it is generally better to use the ASCII-range characters.

    The remedies in the top section avoid the problem.