The KAGAMI mark КАГАМИ
kagami.bg/academy · lesson · machine-readable viewVERIFIED 2026-10-01 · UPDATED 2026-10-01
IDENTITY
module
GX10-A9 · The Cyrillic encoding trap (Windows PowerShell / ssh / Python)
series
GX10 (local AI server class: NVIDIA GB10, e.g. ASUS Ascent GX10 / DGX Spark)
level
Intermediate
duration
~30 min
prerequisites
A Windows computer with Windows PowerShell 5.1 (optionally PowerShell 7), the OpenSSH client, and shell access to a Linux server
trust_label
VERIFIED 2026-10-01 (against Microsoft Learn, OpenSSH manual pages, python.org docs and PEP 686) · UPDATED 2026-10-01 · demos actually run by the authors: Linux sandbox (Python 3.10) and Windows 11 with Windows PowerShell 5.1.26100; PowerShell 7 NOT installed, so its behaviour comes from documentation only · no GB10-class machine involved, so no TESTED label
language
human view: en · bulgarian edition: /academy/gx10/ (same file name)
previous / next
04-220 model versions / GX10 series index (last lesson so far)
PURPOSE

Stop non-ASCII (Cyrillic) paths from being corrupted when they travel between a Windows PowerShell session and a Linux host over ssh. A corrupted path is a valid string that points nowhere, so an existence check returns a false negative that looks like "file already deleted". Fix the capture and file-writing settings, and carry paths as base64 (pure ASCII) wherever raw text is risky.

KEY CONCEPTS
COMMANDS / PATHS
CHECKLIST
NEXT MODULE

GX10 series index: kagami.bg/en/academy/gx10/ (last lesson so far; previous: 04-215 PaddleOCR) · offer: Quick experiment (kagami.bg/stalbata/)

SOURCES
TAGS
gx10encodingutf-8cyrillicpowershellsshbase64python
VERIFIED · 01.10.2026 UPDATED · 01.10.2026

Cyrillic Paths in PowerShell and ssh: the Encoding Trap

You work from Windows to a Linux server (say, one of the NVIDIA GB10 class), check a file whose path contains Cyrillic, and get "does not exist". The file is right there — it is the path that broke on the way. Here you see why, how to catch it with three commands, and how to carry paths without any loss.

⏱ ~30 min Intermediate GX10 Windows PowerShell 5.1 and 7 · OpenSSH · Python Encoding · UTF-8 · base64
PowerShell · ssh (on your computer)🔒 local Python · bash (on the server)🔒 local
🔄
UPDATED · 01.10.2026 — what changed
The lesson was checked against the Microsoft, OpenSSH and Python documentation. We corrected: the old claim that Set-Content and Out-File "add a BOM or write ANSI" — in Windows PowerShell 5.1 Set-Content writes the ANSI code page while Out-File and > write UTF-16 (we measured both); the advice "save the script as UTF-8 and run it with -File" — for 5.1 a script with Cyrillic must be UTF-8 with BOM, otherwise it is read as ANSI; and the explanation that blamed only "capturing" — there are two directions: $OutputEncoding (towards the external program) and [Console]::OutputEncoding (from it). We added: the difference between PowerShell 5.1 and 7, a three-line check of your own setup, the capture fix ([Console]::OutputEncoding), a table for files, a note on chcp, how ssh handles the locale (by default nothing is sent), Python (PYTHONUTF8, PYTHONIOENCODING, UTF-8 mode as the default from Python 3.15) and an invented example instead of real folders. We removed internal hub names and real folders from the examples. base64 stays the most reliable way to carry a path — but it is not "the only cure": capturing has a simpler fix.
⚠️
What we have not run ourselves
We ran the demos for real in two places: a Linux sandbox (Python 3.10) and Windows 11 with Windows PowerShell 5.1.26100. PowerShell 7 is not installed on our side — for it we follow the documentation. We did not run a live ssh connection to a server: an external Windows program (cmd /c type) plays the part of ssh, because the capture mechanism is the same. We have not tried scp/rsync with Cyrillic, nor chcp 65001 as a fix. There was no GB10-class machine, so there is no "TESTED" label.

01What you'll learn

02Before you start

💡
Why it matters: bytes versus letters
File names on Linux are bytes, usually in UTF-8. One Cyrillic letter is 2 bytes. If Windows reads those bytes through a single-byte code page, it gets 2 odd characters instead of 1 letter — the string is different and the path no longer points anywhere.

03Steps

  1. See the trap with your own eyes

    The script below is pure ASCII (the word is built from character codes) so that it depends on nothing. cmd /c type is an external program that returns UTF-8 bytes — exactly like ssh when the server sends a name with Cyrillic.

    powershell · on your computer
    # the word "Документи" ("Documents") built from character codes only
    $c = [string]::new([char[]](0x414,0x43E,0x43A,0x443,0x43C,0x435,0x43D,0x442,0x438))
    $t = Join-Path $env:TEMP 'enc_test.txt'
    # UTF-8 bytes, the way Linux sends them
    [IO.File]::WriteAllBytes($t, [Text.Encoding]::UTF8.GetBytes($c))
    $x = cmd /c type $t          # external program, its output is captured
    "before: $($c.Length) chars · captured: $($x.Length) chars · equal: $($x -eq $c)"
    Remove-Item $t

    On our machine (Windows 11, Windows PowerShell 5.1, code page 437) the result was 9 → 18 characters, "equal: False". Every letter doubled, because its two bytes were read as two characters. The console may still be able to "draw" them — the internal string, though, is already different.

  2. Check what is set on your machine

    powershell
    $PSVersionTable.PSVersion
    $OutputEncoding.WebName
    [Console]::OutputEncoding.WebName
    [Console]::InputEncoding.WebName
    chcp

    On ours: 5.1.26100.…, us-ascii, IBM437, IBM437 and "Active code page: 437". Yours may differ — it depends on the language and settings of Windows. What each line means:

    SettingWhat it controlsDefault (per the documentation)
    $OutputEncodingHow PowerShell sends text to an external program (pipe, ssh)Windows PowerShell 5.1: ASCII · PowerShell 7: UTF-8
    [Console]::OutputEncodingHow the console translates the output of external programsThe console code page, from the system locale (.NET). ⚠️ We have not checked the PowerShell 7 behaviour — run the command and look
    chcpThe active console code pageDepends on the system (437, 850, 866…); 65001 is UTF-8

    ASCII in $OutputEncoding means every Cyrillic letter sent to an external program from 5.1 turns into ? — irreversibly. Capturing from the program is the other end of the same trap.

  3. Fix the output capture

    Tell the console that external programs speak UTF-8. This is for the current session (it does not change Windows):

    powershell
    [Console]::InputEncoding  = [Text.UTF8Encoding]::new($false)
    [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
    $OutputEncoding = [Console]::OutputEncoding

    Repeat the test from step 1: on our machine, after this setting the captured text became equal to the original ("equal: True"). The PowerShell documentation advises that $OutputEncoding should match [Console]::InputEncoding, so we set them together. If you want this for every session, put the lines in your profile — but then it applies to everything you launch from there.

    ⚠️
    What about chcp 65001?
    It is often recommended as the matching switch on the console itself. Microsoft's chcp page does not include it in its table of code pages, and we have not tested it — so we offer it as an idea to try, not as a fix.
  4. Write and read files with an explicit encoding

    This is where the two versions differ most. We measured on 5.1; for 7 we follow the documentation.

    CommandWindows PowerShell 5.1PowerShell 7
    Set-Content without -EncodingThe system's ANSI code page — on ours Cyrillic became 3F (?)UTF-8 without BOM
    Out-File and >UTF-16 with BOM (measured: first bytes FF FE)UTF-8 without BOM
    -Encoding asciievery non-ASCII character → ?the same
    -Encoding utf8UTF-8 with BOM (measured: EF BB BF)UTF-8 without BOM; utf8BOM is a separate value
    powershell
    # write and read with an explicit encoding — works in both versions
    Set-Content -Path .\notes.txt -Value $text -Encoding utf8
    Get-Content -Path .\notes.txt -Encoding utf8
    
    # optional: UTF-8 by default for every cmdlet in the session
    $PSDefaultParameterValues['*:Encoding'] = 'utf8'

    Microsoft's documentation also reminds us about the BOM: for best compatibility UTF-8 files should be without a BOM, because Unix tools (cat, sed, awk) do not understand it. So: a file that Linux programs will read, write in PowerShell 7 with utf8, or in 5.1 with [IO.File]::WriteAllText($path, $text, [Text.UTF8Encoding]::new($false)).

  5. A script with Cyrillic: UTF-8 with BOM for 5.1

    Windows PowerShell 5.1 reads a script without a BOM as ANSI. So a script with Cyrillic written in an editor that keeps UTF-8 without BOM (as VS Code does) breaks its strings or fails to parse (Unexpected token, string is missing the terminator). According to Microsoft, if the script has non-ASCII characters, save it as UTF-8 with BOM. Do not pass long scripts as a single string through -Command — save them to a file and use -File, or encode them in base64 (step 6).

  6. Carry the path as base64

    Why: base64 is pure ASCII and goes through any pipe, code page and file unchanged. You encode on the UTF-8 side, carry ASCII, and decode to a real Unicode string right before use. The path in the example is invented.

    bash · on the server
    python3 - <<'EOF'
    import base64
    p = "Документи/Отчети/годишен-отчет.txt"
    print(base64.b64encode(p.encode("utf-8")).decode("ascii"))
    EOF
    # 0JTQvtC60YPQvNC10L3RgtC4L9Ce0YLRh9C10YLQuC/Qs9C+0LTQuNGI0LXQvS3QvtGC0YfQtdGCLnR4dA==
    powershell · on your computer
    function ConvertFrom-B64([string]$b) {
      [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b))
    }
    $path = ConvertFrom-B64 '0JTQvtC60YPQvNC10L3RgtC4L9Ce0YLRh9C10YLQuC/Qs9C+0LTQuNGI0LXQvS3QvtGC0YfQtdGCLnR4dA=='
    $path                      # a correct Unicode string, in memory
    # for a local file: always -LiteralPath
    # Get-Item -LiteralPath $path

    In the other direction — a script for the server with Cyrillic inside — you encode the script to base64 on your computer and pass it so that only ASCII crosses the pipe:

    powershell · on your computer
    $b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($script))
    ssh <user>@<server-address> "echo $b64 | base64 -d | bash"

    We checked the round trip (Linux → base64 → decoding in PowerShell 5.1) on an invented path: the result is the same string. The ssh connection itself was not run (see "What we have not run ourselves").

  7. Set up the locale and Python on the server

    Two things to remember. First, OpenSSH sends no environment variables by default (SendEnv) and the server accepts none (AcceptEnv) — your locale does not travel by itself. ⚠️ Distributions often change this in their configuration files; it is not in the official OpenSSH documentation, so check your own machine. The simplest approach is to set the locale in the command itself.

    bash · on the server
    locale -a | grep -i utf         # is C.UTF-8 there?
    LC_ALL=C.UTF-8 python3 -c "import sys; print(sys.stdout.encoding)"

    Second, Python has its own UTF-8 mode. According to the documentation it switches on automatically when the locale is C or POSIX. We checked in the sandbox:

    bash
    LC_ALL=C python3 -c "import sys; print(sys.stdout.encoding, sys.flags.utf8_mode)"
    # utf-8 1
    LC_ALL=C PYTHONUTF8=0 python3 -c "import sys; print(sys.stdout.encoding, sys.flags.utf8_mode)"
    # ascii 0
    VariableWhat it does (docs.python.org)
    PYTHONUTF8=1Turns UTF-8 mode on; 0 turns it off. The mode makes open() and paths use UTF-8
    PYTHONIOENCODINGSets the encoding of stdin/stdout/stderr. On Windows it is ignored for an interactive console unless PYTHONLEGACYWINDOWSSTDIO is set; redirected files and pipes are not affected
    Python 3.15+UTF-8 mode is the default (PEP 686, status "Final"). Before that, on Windows open() uses the system code page — set $env:PYTHONUTF8 = '1' before starting the interpreter

    For scp and rsync — file names are bytes, so the capture and transport rules above apply to whatever you call from PowerShell. ⚠️ We have not checked special settings for encoding their names themselves (rsync has an --iconv option — see man rsync, not verified here).

  8. The safety rule before deleting

    🛑
    "Does not exist" often means "broken string"
    Never delete on the strength of a "missing" check if the path went through a pipe raw with non-ASCII. First prove you are reading the right string (decode from base64 or run the test from step 1), check existence and size, and only then act. Do the heavy path work on Linux (Python/bash) — UTF-8 is native there, and let Windows be only a thin transport.
    Situation✅ Do❌ Don't
    A Cyrillic path between Windows and Linuxbase64 end to enda raw string through the pipe
    Writing a file-Encoding utf8 (or explicit UTF-8 without BOM for Linux)-Encoding ascii, or "the default" in 5.1
    A script with Cyrillic for 5.1UTF-8 with BOM, run with -Filea long string through -Command
    Accessing the path-LiteralPath and a decoded stringan interpolated string with Cyrillic through a pipe
    Before deletinga check with a decoded pathtrusting "does not exist"

04Check

Quiz

1. What is the default value of $OutputEncoding in Windows PowerShell 5.1?

2. A file check returns "does not exist", but the path has Cyrillic and went through ssh and PowerShell. The most sensible first move?

3. Which encoding do Out-File and the > operator use by default in Windows PowerShell 5.1?

4. A script with Cyrillic saved without a BOM is run in Windows PowerShell 5.1. What happens?

05What's next

06Sources

  1. about_Character_Encoding — default encodings and the BOM in 5.1 and 7, scripts without a BOM.
  2. about_Preference_Variables — $OutputEncoding (ASCII in 5.1, UTF-8 in 7).
  3. Console.OutputEncoding (.NET) · chcp — the console code page.
  4. Python: command line and environment — PYTHONUTF8, PYTHONIOENCODING · UTF-8 mode · PEP 686 — the default mode from 3.15.
  5. ssh_config (SendEnv) · sshd_config (AcceptEnv) — environment variables over ssh.