1

I'm trying to execute a command in Powershell 5.1, but it fails when the path contains Unicode characters.

For example:

(Get-Acl 'E:/test .txt').access

I'm running the command from Node.js:

let childProcess = require('child_process')
let testProcess = childProcess.spawn('powershell', [])
testProcess.stdin.setEncoding('utf-8')

testProcess.stdout.on('data', (data) => {
  console.log(data.toString())
})

testProcess.stdout.on('error', (error) => {
  console.log(error)
})

// This path is working, I get command output in the console:
// testProcess.stdin.write("(Get-Acl 'E:/test.txt').access\n");

// This path is not working. I get nothing in the console
testProcess.stdin.write("(Get-Acl 'E:/test .txt').access\n");

I cannot use Powershell 7 since I'm making a Node.js app that runs commands on the pre-installed Powershell

Update

This method seems to work:

childProcess.spawn(
  'powershell', 
  ['-Command', '(Get-Acl "E:/test .txt").access']
)
AlekseyHoffman
  • 2,438
  • 1
  • 8
  • 31
  • Make sure the script containing the emoji character literal is Unicode-encoded (UTF16LE), Windows PowerShell will choke on the UTF-8 representation otherwise. – Mathias R. Jessen May 17 '21 at 21:18
  • @MathiasR.Jessen, while saving PowerShell scripts as UTF-16LE definitely works, so does UTF-8 _with BOM_, and since scripts generally mostly contain ASCII-range characters, the latter is preferable in terms of file size. – mklement0 May 17 '21 at 21:23
  • How do I set encoding with an argument? I tried adding `-encoding utf8` and `-encoding UTF16LE` to the command, but it's not working either – AlekseyHoffman May 17 '21 at 21:29
  • @AlekseyHoffman `-Encoding Unicode` – Mathias R. Jessen May 17 '21 at 21:31
  • @MathiasR.Jessen not working either: `testProcess.stdin.write("(Get-Acl 'E:/test .txt').access -Encoding Unicode\n")` – AlekseyHoffman May 17 '21 at 21:32
  • Do you need to provide the commands _via stdin_? If not, try passing the command string via the `-c` (`-Command`) CLI parameter. – mklement0 May 17 '21 at 21:33
  • `-Encoding` would only apply to file-creation cmdlets such as `Set-Content`. The PowerShell CLI uses the console's active code page (by default, the system's active OEM code page) to read input from stdin. To get it to interpret the stdin input as UTF-8, you'd have to set the console's code page to `65001` _before_ invoking `powershell.exe`. – mklement0 May 17 '21 at 21:37
  • 2
    @mklement0 thank you for the solution. Passing the command as an array of arguments with `-Command` seems to work: `childProcess.spawn('powershell', ['-Command', '(Get-Acl "E:/test .txt").access'])` I get the command output in the console – AlekseyHoffman May 17 '21 at 21:41

1 Answers1

1

By using stdin input to provide your command to powershell.exe, the Windows PowerShell CLI, you're implicitly relying on the system's active OEM code page, because that is what the PowerShell CLI uses to decode input received via stdin.

By contrast, passing commands via the -c (-Command) CLI parameter fully supports Unicode, irrespective of the active OEM code page, so it is a simple alternative that bypasses your original problem; borrowing from your own update to your question:

childProcess.spawn(
  'powershell', 
  ['-NoProfile', '-Command', '(Get-Acl "E:/test .txt").access']
)

Note that I've added -NoProfile in order to make the invocation more predictable / speed it up, as this option suppresses loading of the profile files that are usually only relevant for interactive use.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Thank you for the explanation. Do you think there's a way to make it work with `stdin.write` (my initial metod)? Can I encode the file path either with Node.js before sending the command, or with powershell itself before processing the command? – AlekseyHoffman May 17 '21 at 22:09
  • @AlekseyHoffman, I don't know if you've seen my update re the pseudo-interactive behavior that stdin command input exhibits. You can _somewhat_ mitigate that by reading from stdin via `-Command -` instead, but the question is: why do you need stdin input? – mklement0 May 17 '21 at 22:11
  • @AlekseyHoffman, if you _do_ need stdin input - so that you can feed multiple commands over time to a single `powershell.exe` process as a kind of pseudo-REPL - can I suggest you create a _new_ question that is focused specifically on that? – mklement0 May 17 '21 at 22:15
  • 1
    I'm using stdin.write because it's faster for my use case. It takes about 500 ms for Node to spawn a child process. So I figured I could create a single process during app loading and then re-use it – AlekseyHoffman May 17 '21 at 22:17
  • Thanks for your additional suggestions. `-noProfile` makes sense with the 2nd approach. There's 1 thing I found interesting about the unicode conversion, I noticed that when I copy paste this command: `(Get-Acl "E:/test .txt").access` into the powershell manually, it converts the unicode character into `??` and works just fine. So I'm wondering, is there way to utilize this to pre-process paths using powershell capabilities? Also I'm not sure what do you mean by "mitigate that by reading from stdin via -Command"? – AlekseyHoffman May 17 '21 at 22:31
  • @AlekseyHoffman, `-Command -` also makes PowerShell read from stdin, but without also outputting a _prompt string_, which is what piping commands to `powershell.exe` by default or with `-File -` does. Character encoding issues aside, `-Command -` therefore is what you want, but beware of command strings that span multiple lines, as they require _two trailing newlines_ in order to be recognized - the linked GitHub issues detail all of this. – mklement0 May 17 '21 at 22:36
  • @AlekseyHoffman: As for copying into the terminal: the `??` are just a _display_ problem: the selected _font_ doesn't support the character, but that doesn't affect functionality. – mklement0 May 17 '21 at 22:37
  • @AlekseyHoffman, as for solving the character-encoding issues with stdin input, so you can get away with launching a _single_ `powershell.exe` process for processing multiple commands over time: it would indeed make sense to ask a new question focused on that; feel free to notify me here once you have done so, and I'd be happy to take a look. – mklement0 May 17 '21 at 22:39
  • P.S., @AlekseyHoffman: `-NoProfile` _generally_ makes sense (unless you explicitly _want_ profiles to be loaded), irrespective of whether you use stdin input or `-Command`, because - very unfortunately - PowerShell always loads profiles by default. – mklement0 May 17 '21 at 22:47