How to upload files that contain special characters via powershell?

Describe the problem/error/question

I want to upload files to a file host using powershell cause it’s faster. The problem is it can’t read/open files that contain special character or non-english letter. Any fix for this? I want to avoid using Read/Write Node method
cause it’s slower especially if I’m dealing with large files

What is the error message (if any)?

Please share your workflow

Share the output returned by the last node

Information on your n8n setup

  • n8n version: 2.19.5
  • Database (default: SQLite): default
  • n8n EXECUTIONS_PROCESS setting (default: own, main): default
  • Running n8n via (Docker, npm, n8n cloud, desktop app): npm
  • Operating system: Windows Server 2022

@Ruriko this is the same Windows OEM codepage issue from ur earlier tree-list question, just hitting a different surface. when n8n’s Execute Command node fires powershell on Windows Server, the command string travels through cmd.exe’s default codepage (usually 437 or 850 depending on system locale) which mangles UTF-8 paths before powershell ever sees them. so even tho the file exists on disk, powershell gets a garbled path string and can’t open it.

cleanest fix is to bypass cmd.exe’s parsing entirely using powershell’s -EncodedCommand flag. u base64-encode the full command as UTF-16LE first, then pass that to powershell.exe. since the encoded blob has no special chars, cmd.exe can’t mangle anything, and powershell decodes it natively into proper Unicode.

in n8n that looks like a Code node that builds the encoded command, then Execute Command that runs it:

{
  "code_node_javascript": "const cmd = `Get-Item -LiteralPath '${$json.filepath}' | <your upload logic>`; const encoded = Buffer.from(cmd, 'utf16le').toString('base64'); return [{json: {encoded}}];",
  "execute_command": "powershell.exe -NoProfile -EncodedCommand {{ $json.encoded }}"
}

two key details: use -LiteralPath instead of -Path in the powershell command (the literal version doesnt interpret wildcards or special chars), and the Buffer encoding must be utf16le (powershell expects UTF-16 LE, not UTF-8, for EncodedCommand).

alternative if u want to skip powershell entirely — use a Code node with node’s built-in fs.createReadStream + http upload. node handles UTF-8 paths natively on Windows since the runtime ignores Windows codepage. faster than spawning powershell too. happy to show that pattern if u want it.

Can you help me fix my code? I still get Failed to open/read local data from file

@Ruriko looked at ur workflow JSON and theres three things stacking on each other.

first issue — ur using {{ $json.filepath }} INSIDE the JavaScript template literal in the Code node. thats n8n expression syntax which only works in n8n’s parameter UI, not inside Code node JS. inside Code node u use ${$json.filepath} (JS template literal). right now the literal text “{{ $json.filepath }}” is getting baked into the command, so curl is looking for a file called literally “{{ $json.filepath }}”.

second issue — Get-Item -LiteralPath ‘’ | curl.exe -F … doesnt do what u think. Get-Item’s pipe output is a FileInfo object, not file contents. curl doesnt read the file from that pipe at all. ur essentially running Get-Item, dropping the output, then running curl with the broken @path arg.

third issue — even if u fix both of those, curl.exe on Windows uses fopen() which is bound to the system codepage even after chcp 65001, so it would still choke on the Japanese characters in @path regardless of how clean ur EncodedCommand encoding is.

cleanest fix is to skip curl entirely and use powershell’s native Invoke-RestMethod which handles UTF-8 paths through .NET (not the system codepage):

{
  "Code_node_javascript": "const filepath = $json.filepath; const cmd = `$form = @{ sess_id = 'xxxxx'; utype = 'prem'; to_folder = '28890'; file_0 = Get-Item -LiteralPath '${filepath.replace(/'/g, "''")}' }; Invoke-RestMethod -Uri 'https://d300.userdrive.org/cgi-bin/upload.cgi?upload_type=file&utype=anon' -Method POST -Form $form | ConvertTo-Json`; const encoded = Buffer.from(cmd, 'utf16le').toString('base64'); return [{json: {encoded}}];",
  "Execute_Command_parameter": "powershell.exe -NoProfile -EncodedCommand {{ $json.encoded }}"
}

what changed: swapped curl for Invoke-RestMethod -Form which handles multipart natively. Get-Item -LiteralPath now feeds a FileInfo object directly into -Form which becomes the file-upload part properly (so the file CONTENTS actually go through, not just the path string). escaped single quotes in case the filename has any (powershell’s escape rule is doubling: ’ becomes ‘’). added ConvertTo-Json so u get a parseable response back from the upload endpoint instead of formatted text.

then in the Execute Command node u keep what u already have: powershell.exe -NoProfile -EncodedCommand {{ $json.encoded }} — that {{ }} IS correct there since its in the n8n parameter UI, not JS code. the JS-vs-n8n-expression confusion is the trickiest part of mixing Code nodes and Execute Command nodes.

@Ruriko , can you try this?

I think it now can read file but now I got a different error that it can’t find the parameter:

Command failed: powershell.exe -NoProfile -EncodedCommand JABmAG8AcgBtACAAPQAgAEAAewAgAHMAZQBzAHMAXwBpAGQAIAA9ACAAJwB4AHgAeAB4AHgAJwA7ACAAdQB0AHkAcABlACAAPQAgACcAcAByAGUAbQAnADsAIAB0AG8AXwBmAG8AbABkAGUAcgAgAD0AIAAnADIAOAA4ADkAMAAnADsAIABmAGkAbABlAF8AMAAgAD0AIABHAGUAdAAtAEkAdABlAG0AIAAtAEwAaQB0AGUAcgBhAGwAUABhAHQAaAAgACcAQwA6AFwAVQBzAGUAcgBzAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBcAEQAZQBzAGsAdABvAHAAXABPAFMAVABcAEMAbwBtAHAAbABlAHQAZQBcAFsAMgAwADIANgAuADAANQAuADIANQBdACAAZlsSV6IwpDDJMOsw3jC5ML8w/DAgAB1SH2ZmWxJXIABSAGUAbQBpAHgAIABBAGwAYgB1AG0ADDBSAGUAQwBvAGwAbABlAGMAdABpAG8AbgANMHYAbwBsAC4AMQAgAFsARgBMAEEAQwAgADQAOABrAEgAegAP/zIANABiAGkAdABdAC4AegBpAHAAJwAgAH0AOwAgAEkAbgB2AG8AawBlAC0AUgBlAHMAdABNAGUAdABoAG8AZAAgAC0AVQByAGkAIAAnAGgAdAB0AHAAcwA6AC8ALwBkADMAMAAwAC4AdQBzAGUAcgBkAHIAaQB2AGUALgBvAHIAZwAvAGMAZwBpAC0AYgBpAG4ALwB1AHAAbABvAGEAZAAuAGMAZwBpAD8AdQBwAGwAbwBhAGQAXwB0AHkAcABlAD0AZgBpAGwAZQAmAHUAdAB5AHAAZQA9AGEAbgBvAG4AJwAgAC0ATQBlAHQAaABvAGQAIABQAE8AUwBUACAALQBGAG8AcgBtACAAJABmAG8AcgBtACAAfAAgAEMAbwBuAHYAZQByAHQAVABvAC0ASgBzAG8AbgA=" #< CLIXML System.Management.Automation.PSCustomObjectSystem.Object1Preparing modules for first use.0-1-1Completed-1 1Preparing modules for first use.0-1-1Completed-1 Invoke-RestMethod : A parameter cannot be found that matches parameter name 'Form'._x000D__x000A_At line:1 char:343_x000D__x000A_+ ... in/upload.cgi?upload_type=file&utype=anon' -Method POST -Form $form | ..._x000D__x000A_+ ~~~~~_x000D__x000A_ + CategoryInfo : InvalidArgument: (:) [Invoke-RestMethod], ParameterBindingException_x000D__x000A_ + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.InvokeRestMethodCommand_x000D__x000A_ _x000D__x000A_

What is the Code node with node’s built-in fs.createReadStream + http upload method? does it require importing the binary file into n8n cause I don’t want that

tried it still the same error

Windows Server 2022 ships with PowerShell 5.1 as the default powershell.exe — and Invoke-RestMethod -Form only exists in PowerShell 7+. thats why ur getting the parameter error.

quickest fix is installing PowerShell 7 (called pwsh.exe, installs alongside the old one with zero conflict — its in the Microsoft store or GitHub - PowerShell/PowerShell: PowerShell for every system! · GitHub releases) and changing ur Execute Command from powershell.exe to pwsh.exe. everything else stays the same.

alternative if u dont want another PS install — skip powershell entirely and do the upload from an n8n Code node using Node’s fetch + FormData. n8n bundles its own Node so works regardless of Windows version. want the code for that path?

What about this, @Ruriko ?

Yes pls show the code for n8n Code node using Node’s fetch + FormData

heres the Code node version — paste into a Code node set to “Run Once for All Items” mode, leave Execute Command unused since the upload runs directly from Node:

const fs = require('fs');
const path = require('path');

const filepath = $json.filepath;
const filename = path.basename(filepath);

const buffer = fs.readFileSync(filepath);
const form = new FormData();
form.append('sess_id', 'xxxxx');
form.append('utype', 'prem');
form.append('to_folder', '28890');
form.append('file_0', new Blob([buffer]), filename);

const response = await fetch('https://d300.userdrive.org/cgi-bin/upload.cgi?upload_type=file&utype=anon', {
  method: 'POST',
  body: form,
});

return [{ json: { status: response.status, response: await response.text() } }];

couple things worth knowing. fs.readFileSync loads the whole file into memory so for normal-sized files (under ~1GB) ur fine, but if ur FLAC album zip is bigger u’d want fs.createReadStream piped through the form-data package instead, lmk if u hit that case. fetch, FormData and Blob are all globals in n8n’s bundled Node so the only requires are fs and path. this also skips powershell entirely so the PS 5.1 / PS 7 issue just goes away — Node handles UTF-8 paths natively on Windows.

I get error:

Blob is not defined [line 12]
ReferenceError

and yes the files are larger than 1gb, I have some that are 10gb

It doesn’t give any error in n8n but it doesn’t look like it’s actually uploading as it was just instant completed when I execute it. So I tried testing it in powershell instead of n8n to see if there were errors which results this:

ParserError:
Line |
   2 |  … e&utype=anon' -Method Post -ContentType \"multipart/form-data; bounda …
     |                                              ~~~~~~~~~~~~~~~~~~~
     | Unexpected token 'multipart/form-data' in expression or statement.

Last try, running out of idea @Ruriko

ah right, the Code node sandbox doesnt expose Blob as a global even on Node 18+ — n8n whitelists globals strictly. and fs.readFileSync would OOM ur worker on 10gb anyway. use n8n’s bundled form-data npm package + raw https.request which streams without buffering anything into memory:

const fs = require('fs');
const path = require('path');
const https = require('https');
const FormData = require('form-data');

const filepath = $json.filepath;
const filename = path.basename(filepath);
const url = new URL('https://d300.userdrive.org/cgi-bin/upload.cgi?upload_type=file&utype=anon');

const form = new FormData();
form.append('sess_id', 'xxxxx');
form.append('utype', 'prem');
form.append('to_folder', '28890');
form.append('file_0', fs.createReadStream(filepath), {
  filename: filename,
  knownLength: fs.statSync(filepath).size,
});

const response = await new Promise((resolve, reject) => {
  const req = https.request({
    method: 'POST',
    hostname: url.hostname,
    path: url.pathname + url.search,
    headers: form.getHeaders(),
  }, (res) => {
    let body = '';
    res.on('data', c => body += c);
    res.on('end', () => resolve({ status: res.statusCode, body }));
  });
  req.on('error', reject);
  form.pipe(req);
});

return [{ json: response }];

if u hit “Cannot find module form-data” or similar require error, set NODE_FUNCTION_ALLOW_EXTERNAL=form-data in ur n8n environment variables and restart. that whitelists the form-data package for the Code node sandbox — n8n 2.x defaults to no external modules for security. with that env var ur 10gb upload becomes bandwidth-bound, no memory copy of the file at all.

Setting NODE_FUNCTION_ALLOW_EXTERNAL=form-data fixed the “Cannot find module form-data” but it now gives a different error

URL is not defined [line 8]
ReferenceError

URL isnt exposed as a sandbox global either, same family as the Blob thing earlier. two ways to fix — add const { URL } = require(‘url’); at the top of the Code node, OR just drop the URL parsing entirely and hardcode hostname + path since u know the values:

const req = https.request({
  method: 'POST',
  hostname: 'd300.userdrive.org',
  path: '/cgi-bin/upload.cgi?upload_type=file&utype=anon',
  headers: form.getHeaders(),
}, (res) => { ... });

second route is simpler — no extra require, less to fight with. rest of the code stays the same.

@Ruriko Happy it helped!