PowerShell doesn't delete file in $env:TEMP if account contains a cyrillic char · Issue #21070 · PowerShell/PowerShell · GitHub
Skip to content

PowerShell doesn't delete file in $env:TEMP if account contains a cyrillic char #21070

Open

Description

Prerequisites

Steps to reproduce

If a user account contains a Cyrillic char in its name, PowerShell is unable to remove any file in $env:TEMP folder with command Remove-Item "$env:TEMP\1.txt" -Force.

The issue cannot be reproduced with $env:LOCALAPPDATA and $env:APPDATA.

Added on 16.01.2024

It turned out, if a username has the first capital letter Тест (not тест), the bug cannot be reproduced!

Expected behavior

File should be removed.

Actual behavior

Remove-Item: An object at the specified path C:\Users\FF6D~1 does not exist.

Error details

PS C:\Users\тест> get-error

Exception             :
    Type        : System.Management.Automation.PSArgumentException
    ErrorRecord :
        Exception             :
            Type    :
System.Management.Automation.ParentContainsErrorRecordException
            Message : An object at the specified path C:\Users\FF6D~1 does
not exist.
            HResult : -2146233087
        CategoryInfo          : InvalidArgument: (:) [],
ParentContainsErrorRecordException
        FullyQualifiedErrorId : Argument
    Message     : An object at the specified path C:\Users\FF6D~1 does not
exist.
    ParamName   : path
    TargetSite  :
        Name          : NormalizeThePath
        DeclaringType : Microsoft.PowerShell.Commands.FileSystemProvider
        MemberType    : Method
        Module        : System.Management.Automation.dll
    Source      : System.Management.Automation
    HResult     : -2147024809
    StackTrace  :
   at
Microsoft.PowerShell.Commands.FileSystemProvider.NormalizeThePath(String
basepath, Stack`1 tokenizedPathStack)
   at Microsoft.PowerShell.Commands.FileSystemProvider.NormalizeRelativePath
Helper(String path, String basePath)
CategoryInfo          : InvalidArgument: (:) [Remove-Item],
PSArgumentException
FullyQualifiedErrorId :
Argument,Microsoft.PowerShell.Commands.RemoveItemCommand
InvocationInfo        :
    MyCommand        : Remove-Item
    ScriptLineNumber : 1
    OffsetInLine     : 1
    HistoryId        : 1
    Line             : Remove-Item "$env:TEMP\1.txt" -Force
    Statement        : Remove-Item "$env:TEMP\1.txt" -Force
    PositionMessage  : At line:1 char:1
                       + Remove-Item "$env:TEMP\1.txt" -Force
                       + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    InvocationName   : Remove-Item
    CommandOrigin    : Internal
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1

Environment data

Name                           Value
----                           -----
PSVersion                      7.4.1
PSEdition                      Core
GitCommitId                    7.4.1
OS                             Microsoft Windows 10.0.22631
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

изображение

Workaround

Get-ChildItem -Path "$env:TEMP\1.txt" | Remove-Item

Activity

  1. changed the title [-]PowerShell doesn't delete file in $env:TEMP in account contains cyrillic chars[/-] [+]PowerShell doesn't delete file in $env:TEMP if account contains cyrillic chars[/+] on Jan 14, 2024
  2. changed the title [-]PowerShell doesn't delete file in $env:TEMP if account contains cyrillic chars[/-] [+]PowerShell doesn't delete file in $env:TEMP if account contains a cyrillic char[/+] on Jan 14, 2024
  3. michael-o commented on Jan 14, 2024

    @michael-o

    After more than 30 years Windows still cannot get the 8.3 limitation and Unicode straight...

  4. mklement0 commented on Jan 14, 2024

    @mklement0
    Contributor

    The bug also affects Windows PowerShell, and switching from the (positionally implied) -Path parameter to -LiteralPath doesn't make a difference.

    It's not a fundamental problem, however, given the following workaround:

    Get-Item $env:TEMP\1.txt | Remove-Item
  5. farag2 commented on Jan 14, 2024

    @farag2
    Author

    The bug also affects Windows PowerShell, and switching from the (positionally implied) -Path parameter to -LiteralPath doesn't make a difference.

    It's not a fundamental problem, however, given the following workaround:

    Get-Item $env:TEMP\1.txt | Remove-Item

    Use the same workaround in my module.

  6. 237dmitry commented on Jan 14, 2024

    @237dmitry

    Intersting, does this work?

    del temp:\1.txt
    

    PS. Use utf-8 as system wide locale, 2024 already.

  7. farag2 commented on Jan 14, 2024

    @farag2
    Author

    Intersting, does this work?

    del temp:\1.txt
    

    PS. Use utf-8 as system wide locale, 2024 already.

    It works in CMD.

  8. 237dmitry commented on Jan 14, 2024

    @237dmitry

    It works in CMD.

    The filename, directory name, or volume label syntax is incorrect.
    

    I am about pwsh

  9. farag2 commented on Jan 14, 2024

    @farag2
    Author

    It works in CMD.

    The filename, directory name, or volume label syntax is incorrect.
    

    I am about pwsh

    PS C:\Users\тест> del temp\1.txt
    Remove-Item: Cannot find path 'C:\Users\тест\temp\1.txt' because it does not exist.
    

    тест means test in Russian. It's a VM in Hyper-V.

  10. 237dmitry commented on Jan 14, 2024

    @237dmitry

    Ok, I thought it might be possible using PSDrive temp: this would work

    not temp\1.txt but temp:\1.txt

     PS > Get-PSDrive -Name Temp
    
    Name           Used (GB)     Free (GB) Provider      Root
    ----           ---------     --------- --------      ----
    Temp               40,20        197,30 FileSystem    C:\Users\<user>\AppData\Local\Temp\
    
  11. farag2 commented on Jan 14, 2024

    @farag2
    Author

    Ok, I thought it might be possible using PSDrive temp: this would work

    I will until the bug will be fixed properly. I'm OK to use now Get-Item $env:TEMP\1.txt | Remove-Item in the code.

  12. mklement0 commented on Jan 14, 2024

    @mklement0
    Contributor

    Dmitry Nefedov (@farag2), a tip for future issues:

    • If you already know of a workaround, please mention it as part of the initial post.
    • An "I already knew that" comment in response to somebody else mentioning a workaround doesn't meaningfully contribute to the conversation.

    Given the comments above, the bug seems to come down to this:

    • Use of Remove-Item ...
    • ... in combination with the 8.3 form of a path, as reflected in the TEMP environment variable.
      • By contrast, cmd.exe's internal del command does not have this problem
      • Similarly, using the PowerShell-only Temp: drive works fine
      • Ditto for Remove-Item $env:USERPROFILE\AppData\Local\Temp\1.txt, given that $env:USERPROFILE does not use an 8.3 path
  13. farag2 commented on Jan 16, 2024

    @farag2
    Author

    It turned out, if a username has the first capital letter Тест (not тест), the bug cannot be reproduced!

  14. farag2 commented on Jan 23, 2024

    @farag2
    Author

    изображение

  15. jascott1717 commented on Feb 16, 2024

    @jascott1717

    I encountered a similar issue with a coworker. His name is JT, two consecutive capital letters, and he was encountering the exact same issue trying to delete a file in the temp directory. We applied the workaround described above and were able to get past it.

  16. farag2 commented on Feb 23, 2024

    @farag2
    Author

    изображение

  17. farag2 commented on May 25, 2024

    @farag2
    Author

    изображение

  18. farag2 commented on Oct 31, 2024

    @farag2
    Author

    изображение

  19. farag2 commented on Jan 16, 2025

    @farag2
    Author

    Image

  20. farag2 commented on Jan 25, 2025

    @farag2
    Author

    Image

  21. farag2 commented on May 17, 2025

    @farag2
    Author

    Image

  22. farag2 commented on Oct 21, 2025

    @farag2
    Author
    Image Image
  23. farag2 commented on May 10, 2026

    @farag2
    Author

    The issue still persists in PowerShell 7.7.0-preview.1.

  24. haigou-web commented on Oct 6, 2026

    @haigou-web

    Root cause located — the character set is a red herring

    I reproduced this on Windows 11 (build 26200) + PowerShell 7.6.6 and traced it to a single
    length heuristic in FileSystemProvider.NormalizeThePath. The trigger is not Cyrillic
    characters, and not non-ASCII characters at all. It is 8.3 short names that expand to a
    shorter long name
    .

    Minimal reproduction — pure ASCII, no special account name required

    # long name is 1 char, short name is 3 chars
    $d = "$env:TEMP\a"
    New-Item -ItemType Directory -Force -Path $d | Out-Null
    fsutil file setshortname "$d" "A~1"
    
    Set-Content "$env:TEMP\A~1\p.txt" -Value x
    Remove-Item "$env:TEMP\A~1\p.txt" -Force
    # Remove-Item: An object at the specified path C:\Users\<user>\AppData\Local\Temp\A~1 does not exist.
    
    Test-Path "$env:TEMP\A~1\p.txt"   # True — the file is still there

    The same thing with a non-ASCII name (the shape from the original report):

    $d = "$env:TEMP\测试目录"                  # long: 4 chars
    New-Item -ItemType Directory -Force -Path $d | Out-Null
    fsutil file setshortname "$d" "CESHI~1"    # short: 7 chars
    
    Set-Content "$env:TEMP\CESHI~1\p.txt" -Value x
    Remove-Item "$env:TEMP\CESHI~1\p.txt" -Force
    # Remove-Item: An object at the specified path ...\Temp\CESHI~1 does not exist.

    The exact condition

    long name short name resolved FullName shorter than input? Remove-Item
    a A~1 a (1) yes (3 → 1) ❌ fails
    测试目录 CESHI~1 测试目录 (4) yes (7 → 4) ❌ fails
    abcdefghij ABCDEF~1 abcdefghij (10) no (8 → 10) ✅ works
    asciiProbe ASCIPR~1 asciiProbe (10) no (8 → 10) ✅ works

    It fails iff len(resolved long name) < len(short name). Non-ASCII account names are the
    common real-world trigger only because a 4-character UTF-16 name like тест almost always gets
    a longer ASCII short name (FF6D~1). That also explains the "capitalized Тест does not
    reproduce" observation in the thread — what matters is the resulting short/long name lengths,
    not the casing.

    Only Remove-Item is affected

    Path component is a short name longer than its long name (CESHI~1 → 测试目录):

    call result
    Test-Path ✅
    Get-Item ✅
    Remove-Item <path> ❌ "does not exist"
    Remove-Item -LiteralPath <path> ❌ "does not exist"
    Remove-Item 'p.txt' after Set-Location <short> ❌ "does not exist"
    Get-Item <path> | Remove-Item ✅
    [System.IO.File]::Delete(<path>) ✅
    cmd /c del <path> ✅

    The filesystem, the .NET BCL, cmd.exe and PowerShell's own Get-Item all resolve the short
    name correctly. Only Remove-Item's path normalization fails.

    Root cause

    src/System.Management.Automation/namespaces/FileSystemProvider.cs,
    NormalizeThePath(string basepath, Stack<string> tokenizedPathStack) — the guard at L5688:

    currentPath = MakePath(currentPath, childName);
    
    var fsinfo = GetFileSystemInfo(currentPath, out bool _);
    
    if (fsinfo != null)
    {
        // This might happen if you've passed a child name of two or more dots,
        // which the .NET APIs treat as the parent directory
        if (fsinfo.FullName.Length < currentPath.Length)          // ← L5688
        {
            throw PSTraceSource.NewArgumentException(
                "path", FileSystemProviderStrings.ItemDoesNotExist, currentPath);
        }
    
        // Expand the short file name
        if (fsinfo.Name.Length >= childName.Length)
        {
            childName = fsinfo.Name;
        }
    }

    The guard is a heuristic meant to catch a child token such as .... that the .NET APIs treated
    as a parent directory reference — in that case fsinfo.FullName points at an ancestor and is
    therefore "shorter". It assumes a shorter FullName can only mean an ancestor.

    An 8.3 short name breaks that assumption. GetFileSystemInfo returns a DirectoryInfo, whose
    FullName is the expanded long name. When the long name is shorter in characters than the
    short name, the guard fires and reports ItemDoesNotExist for an item that exists and is
    exactly the one requested:

    input    : C:\Users\胡竞初\AppData\Local\Temp\CESHI~1      (len 39)
    FullName : C:\Users\胡竞初\AppData\Local\Temp\测试目录      (len 36)
    36 < 39  →  throw ItemDoesNotExist(currentPath = "...\CESHI~1")
    

    Note that the next statement in the same block already performs short-name expansion
    correctly and correctly declines to expand when the long name is shorter — the L5688 guard runs
    first and never gives it the chance.

    Suggested fix

    The guard needs to distinguish "this is an ancestor due to over-normalization" from "this is the
    same item, spelled with a short name". A short-name expansion keeps the same number of path
    components
    ; an over-normalized path has fewer. So comparing component counts (or comparing
    against the expanded currentPath) instead of raw character lengths would be immune to this.
    Assigning currentPath = fsinfo.FullName before the comparison would also make it meaningful,
    since fsinfo.FullName is already the correctly resolved path.

    A regression test can be built with the a / A~1 pair above — no non-ASCII characters, no
    locale or account-name dependence.

    Environment
    • Windows 11 build 26200, NTFS with 8.3 short-name generation enabled
    • PowerShell 7.6.6 (stable)
    • Verified with fsutil file setshortname on directories under $env:TEMP
  25. gitnol commented on Oct 8, 2026

    @gitnol

    Confirming the root cause analysis above (len(long name) < len(short name)) with a real-world, pure-ASCII trigger and
    two additional observations that may help scope the fix.

    Environment: PowerShell 7.6.6, Windows 11 (build 26200), NTFS with 8.3 names enabled.

    Real-world trigger without any special characters: a domain account of the form f.lastname (a dot in the name).
    Windows gives the profile folder an 8.3 alias like F12AB~1.XYZ and sets TEMP/TMP to the short form
    (C:\Users\F12AB~1.XYZ\AppData\Local\Temp). The long name (f.lastname, 10 chars) is shorter than the short name
    (F12AB~1.XYZ, 11 chars), so every path under $env:TEMP hits the bug.

    1. Move-Item is affected as well, not only Remove-Item:

    call (path under the 8.3 profile alias) result
    Test-Path -LiteralPath ✅
    Get-Item -LiteralPath ✅
    Copy-Item -LiteralPath ✅
    Move-Item -LiteralPath ❌ An object at the specified path C:\Users\F12AB~1.XYZ does not exist.
    Remove-Item -LiteralPath ❌ same error, FullyQualifiedErrorId: Argument,Microsoft.PowerShell.Commands.RemoveItemCommand

    2. It depends on the current location's drive: with the current location on the same drive as the target
    (Set-Location C:\), Move-Item/Remove-Item fail; with the current location on another drive
    (Set-Location D:\), the identical calls succeed. This presumably steers which branch of the path normalization runs.

    Repro (uses GetShortPathName instead of fsutil file setshortname, so it needs no admin rights; it reproduces
    whenever $env:TEMP itself is in the problematic 8.3 form):

    $long = Join-Path ([IO.Path]::GetTempPath()) ('Repro Long Folder Name ' + (Get-Random))
    [void][IO.Directory]::CreateDirectory($long)
    Add-Type -Namespace Repro -Name Native -MemberDefinition @'
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    public static extern uint GetShortPathName(string lpszLongPath, System.Text.StringBuilder lpszShortPath, uint cchBuffer);
    '@
    $sb = [Text.StringBuilder]::new(1024); [void][Repro.Native]::GetShortPathName($long, $sb, 1024)
    $file = Join-Path $sb.ToString() 'a.txt'
    [IO.File]::WriteAllText($file, 'x')
    
    Set-Location ($file.Substring(0, 3))          # same drive  -> Move-Item/Remove-Item fail
    # Set-Location D:\                            # other drive -> both succeed
    Copy-Item   -LiteralPath $file -Destination "$file.copy"           # ok
    Move-Item   -LiteralPath "$file.copy" -Destination "$file.moved"   # fails
    Remove-Item -LiteralPath $file                                     # fails
    [IO.Directory]::Delete($long, $true)

    Workarounds we use: expand the path first — [IO.Path]::GetFullPath($p) already returns the long form on
    .NET 8, also for not-yet-existing files — or call [IO.File]::Move/Delete directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-FileSystem-Providerspecific to the FileSystem providerIn-PRIndicates that a PR is out for the issueIssue-BugIssue has been identified as a bug in the productNeeds-TriageThe issue is new and needs to be triaged by a work group.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions