I am trying to configure a PowerShell script that will restart itself after reboot.
In order to do so, I have the PowerShell script check and write a bat-script in the Windows Startup folder "C:\Users<User>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" with the following code:
# Creates a startup script to restart the process after a reboot.
$Location = "$Env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\Restart.bat"
$Script = "
@echo off
REM Verifies if the script is running as admin.
net session >nul 2>&1
if %errorLevel% == 0 (
goto Continue
) else (
REM Restarts the script as admin.
powershell -command `"Start-Process %~dpnx0 -Verb runas`"
)
powershell.exe -NoLogo -NoExit -NoProfile -ExecutionPolicy Bypass -File $PSCommandPath
"
if(!(Test-Path $Location)){
$Script | Out-File -FilePath $Location -Force
Everything seems to be working fine, but when attempting to run it I get the following error: '■@' is not recognized as an internal or external command, operable program or batch file.
Opening the bat-file in Notepad doesn't show the character in question.
Would anyone know what might be causing this?
tl;dr
Use ... | Set-Content -Encoding Oem ...
to write batch files from PowerShell (Out-File -Encoding Oem
works too).
However, if the content to write contains Unicode characters that cannot be represented in the system's active OEM character encoding, they are lossily transcribed to verbatim ?
characters.
SomethingDark has provided the crucial pointer:
Your problem is one of character encoding:
In Windows PowerShell (the legacy, ships-with-Windows edition whose latest and last version if 5.1.) Out-File
(as well as its virtual alias, redirection operator >
) produces "Unicode" files by default, i.e. UTF-16LE files, which cmd.exe
does not understand - it expects batch files to use the character encoding implied by the system's active legacy OEM code page by default, such as CP437 on US-English systems, as reflected in the output of the chcp.com
utility.
By contrast, in PowerShell (Core) 7, Out-File
(as well as >
and Set-Content
) creates (BOM-less) UTF-8 files, given that said encoding is the consistent default there.
However, unless UTF-8 has been configured to be used system-wide - which has far-reaching consequences, see this answer - cmd.exe
will only interpret such files correctly if they're composed of ASCII-range characters only.
See the bottom section of this answer for how to make cmd.exe
sessions as well as PowerShell sessions use (BOM-less) UTF-8 as the OEM code page via autorun commands / profile files, as a less drastic alternative to the aforementioned system-wide change.
As for the specific symptom, '■@' is not recognized ...
:
■
is the (mis)interpretation of the second byte in the two-byte sequence that comprises the UTF-16LE BOM (Byte Order Mark), 0xFF
and 0xFE
, at the start of the file, as an OEM character. That is, byte 0xFE
is interpreted as the following Unicode character: ■
(BLACK SQUARE, U+25A0
)
The first byte is (mis)interpreted as Unicode character
(NO-BREAK SPACE, U+00A0
); however, because cmd.exe
seemingly applies whitespace-trimming to the source-code line it quotes in the error message, this character doesn't actually print.
@
is the start of the @echo off
line. The reason that the rest of the line doesn't print is that in UTF-16LE the @
is encoded as byte sequence 0x40
, 0x00
.
0x40
in isolation, due to falling into the ASCII range that OEM and Unicode share, is interpreted as @
(COMMERCIAL AT, U+0040
).0x00
is interpreted as NULL, U+0000
, which cmd.exe
seemingly interprets as a string terminator (as in C/C++) and therefore considers this character the end of the string, so the rest of the line is missing.A few asides:
The error message implies that your batch file does not start with a newline (empty line), which is what the code in your question implies.
If it did, the misinterpreted newline would in effect have reduced your error message to '■' is not recognized ...
Either way, if there were no character-encoding problem, such a leading newline (as well as an extra trailing one) would be benign; still, if you want to avoid it:
Either: Start the string content on the same line as the opening "
(and similarly place the closing "
at the end of the last line of content):
$Script = "@echo off
REM Verifies if the script is running as admin.
..."
Or: Use the here-string variant of an expandable (interpolating), double-quoted string ("..."
):
$Script = @"
@echo off
REM Verifies if the script is running as admin.
...
"@
Note that multiline string literals in PowerShell source code use the newline format of the enclosing script file, so that if your script is saved with Unix-format LF-only newlines, a file created from a string literal in that script will have the same newlines, and analogously for scripts with Window-format CRLF newlines (curiously, multiline string literals at the PowerShell prompt always use LF-only newlines, even on Windows).
However, this aspect is irrelevant when creating batch files, because cmd.exe
accepts LF-only and CRLF newlines interchangeably in them (even when mixed in a single file, which can easily happen, because Out-File
/ >
and Set-Content
by default append a platform-native newline to the content being written).