2

I use this batch script written by Mofi to move folders into other folders.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
%SystemRoot%\System32\tree.com
for /F "eol=| delims=" %%I in ('dir /AD /B 2^>nul') do (
    set "FolderName=%%I"
    setlocal EnableDelayedExpansion
    set "TargetFolder=!FolderName:~0,1!"
    if not "!TargetFolder!" == "!FolderName!" (
        md "!TargetFolder!" 2>nul
        move /-Y "!FolderName!" "!TargetFolder!\"
    )
    endlocal
)
%SystemRoot%\System32\tree.com
endlocal

It moves folders with two or more characters in current folder into a subfolder with name being the first character of the folder to move with automatic creation of the destination folder if not existing already.

But the batch script doesn't work if a folder name contains one or more Unicode characters.

Is there a workaround with PowerShell?

For example, it doesn't move a folder with first character being Ш (Cyrillic capital letter SHA) into a folder with name Ш.

Mofi
  • 46,139
  • 17
  • 80
  • 143
Jack Rock
  • 157
  • 6

1 Answers1

4

Yes, PowerShell handles Unicode characters in file paths (and in general) correctly.

The equivalent of your batch-file code in PowerShell is:

Get-ChildItem -Directory -Path ??* |
  Move-Item -Destination { 
    # Determine the target dir - the input dir's first letter -
    # ensure that it exists, and output it.
    New-Item -Type Directory -Force (Join-Path $_.Parent.FullName $_.Name[0])
  } -WhatIf

Note: The -WhatIf common parameter in the command above previews the move operation. Remove -WhatIf once you're sure the operation will do what you want. However, even with
-WhatIf the target directories are created right away.

  • Get-ChildItem -Directory -Path ??* gets all directories whose name is made up of at least 2 characters.

    • Compatibility note: As Mofi, the original author of the batch-file code in the question, points out, the -Directory switch requires PowerShell version 3 or higher, and to make the code work in v2 as well,
      Get-ChildItem -Path ??* | Where-Object { $_.PSIsContainer } must be used.
  • The Move-Item command uses a delay-bind script block to determine the destination (target) directory for each input directory:

    • New-Item -Type Directory either creates the target directory or - thanks to -Force returns an existing one. In both cases, a System.IO.DirectoryInfo instance is output, which tells Move-Item where to move the input dir. at hand.

    • Join-Path determines the full target path from the input directory's own (parent) directory, $_.Parent.FullName[1], and the first-letter-of-the-input-dir-name target name, $_.Name[0])

      • Note: Constructing the full target path isn't strictly necessary if you're running the command in the current directory, so in this case you could simplify to
        New-Item -Type Directory -Force $_.Name[0]

[1] You can also use $_.PSParentPath, but, due to a bug in Join-Path in PowerShell [Core] as of 7.1, that wouldn't work on Unix-like platforms - see GitHub issue #14538.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 2
    The PowerShell script works fine with PowerShell 3.0 or newer version. It works also fine even on a FAT32 drive with PowerShell 2.0 installed by default on Windows 7 with the first line modified to `Get-ChildItem -Path ??* | ?{ $_.PSIsContainer } |` according to [How do I get only directories using Get-ChildItem?](https://stackoverflow.com/a/3085524/3074564) Thank you for rewriting my batch code as PowerShell script code. – Mofi Jan 04 '21 at 17:39
  • 2
    Thanks for confirming, @Mofi. I've added the v2 workaround to the answer (fortunately, v2 installations are increasingly rare). I've also added a note to indicate that it was you wrote the batch-file code, which the OP should have done in the question. (I've already given your original answer with the batch-file code a +1) – mklement0 Jan 04 '21 at 18:03
  • I appreciate it, @JackRock. – mklement0 Jan 05 '21 at 15:56