How can I convert a Windows short name path into long names within a batch script
Asked Answered
S

4

16

I am writing a Windows batch script, and I have an argument or variable containing a path that uses short 8.3 names. The path might represent a file or folder.

How can I convert the 8.3 path into a long-name path? At a minimum I would like to be able to simply print out the full long-name path. But ideally I would like to safely get the long-name path into a new variable.

For example, given the path C:\PROGRA~1\AVASTS~1\avast\BROWSE~1.INI, I would like to return C:\Program Files\AVAST Software\avast\BrowserCleanup.ini.

As a batch enthusiast, I am most interested in a pure batch solution that uses only native Windows commands. But hybird use of other native scripting tools like PowerShell and JScript is also acceptable.

Note: I am posting my own answer for this question. I searched the web, and was surprised to find precious little on this subject. I developed multiple working strategies, and thought others might be interested in what I found.

Sheela answered 26/12, 2015 at 18:15 Comment(0)
S
20

First I will demonstrate how to convert a batch file argument %1 and print the result to the screen.

PowerShell

The simplest solution is to use PowerShell. I found the following code on a MSDN blog by Sergey Babkin

$long_path = (Get-Item -LiteralPath $path).FullName

Putting that code in a batch script and printing the result is trivial:

@echo off
powershell "(Get-Item -LiteralPath '%~1').FullName"

However, I try to avoid using PowerShell within batch for two reasons

  • PowerShell is not native to XP
  • The start up time for PowerShell is considerable, so it makes the batch hybrid relatively slow


CSCRIPT (JScript or VBS)

I found this VBS snippet at Computer Hope forum that uses a dummy shortcut to convert from short to long form.

set oArgs = Wscript.Arguments
wscript.echo LongName(oArgs(0))
Function LongName(strFName)
Const ScFSO = "Scripting.FileSystemObject"
Const WScSh = "WScript.Shell"
   With WScript.CreateObject(WScSh).CreateShortcut("dummy.lnk")
     .TargetPath = CreateObject(ScFSO).GetFile(strFName)
     LongName = .TargetPath
   End With
End Function

I found similar code at a Microsoft newsgroup archive and an old vbscript forum.

The code only supports file paths, and it is a bit easier to embed JScript within batch. After converting to JScript and adding an exception handler to get a folder if a file fails, I get the following hybrid code:

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment

::----------- Batch Code-----------------
@echo off
cscript //E:JScript //nologo "%~f0" %1
exit /b

------------ JScript Code---------------*/
var shortcut = WScript.CreateObject("WScript.Shell").CreateShortcut("dummy.lnk");
var fso = new ActiveXObject("Scripting.FileSystemObject");
var folder='';
try {
  shortcut.TargetPath = fso.GetFile(WScript.Arguments(0));
}
catch(e) {
  try {
    shortcut.TargetPath = fso.GetFolder(WScript.Arguments(0));
    folder='\\'
  }
  catch(e) {
    WScript.StdErr.WriteLine(e.message);
    WScript.Quit(1);
  }
}
WScript.StdOut.WriteLine(shortcut.TargetPath+folder);


Pure Batch

Surprisingly, my web search failed to find a pure batch solution. So I was on my own.

If you know that the path represents a file, then it is a simple matter to convert the 8.3 file name into a long name using dir /b "yourFilePath". However, that does not resolve the names of the parent folder(s).

The situation is even worse if the path represents a folder. There is no way to list a specific folder using only the DIR command - it always lists the contents of the folder instead of the folder name itself.

I tried a number of strategies to handle the folder paths, and none of them worked:

  • CD or PUSHD to the path and then look at the prompt - it preserves the short folder names
  • XCOPY with /L and /F options - it also preserves the short folder names
  • Argument or FOR variable modifier %~f1 or %%~fA - preserves the short names
  • FORFILES - doesn't appear to support short names.

The only solution I was able to come up with was to use DIR to iteratively convert each folder within the path, one at a time. This requires that I use DIR /X /B /AD to list all folders in the parent folder, including their 8.3 names, and then use FINDSTR to locate the correct short folder name. I rely on the fact that the short file name always appears in the exact same place after the <DIR> text. Once I locate the correct line, I can use variable substring or find/replace operations, or FOR /F to parse out the long folder name. I opted to use FOR /F.

One other stumbling block I had was to determine if the original path represents a file or a folder. The frequently used approach of appending a backslash and using IF EXIST "yourPath\" echo FOLDER improperly reports a file as a folder if the path involves a symbolic link or junction, which is common in company network environments.

I opted to use IF EXIST "yourPath\*", found at https://mcmap.net/q/74457/-how-to-test-if-a-file-is-a-directory-in-a-batch-script.

But it is also possible to use the FOR variable %%~aF attribute modifier to look for the d (directory) attribute, found at https://mcmap.net/q/74457/-how-to-test-if-a-file-is-a-directory-in-a-batch-script, and https://mcmap.net/q/75648/-how-to-test-if-a-path-is-a-file-or-directory-in-windows-batch-file.

So here is a fully working pure batch solution

@echo off
setlocal disableDelayedExpansion

:: Validate path
set "test=%~1"
if "%test:**=%" neq "%test%" goto :err
if "%test:?=%"  neq "%test%" goto :err
if not exist "%test%"  goto :err

:: Initialize
set "returnPath="
set "sourcePath=%~f1"

:: Resolve file name, if present
if not exist "%~1\*" (
  for /f "eol=: delims=" %%F in ('dir /b "%~1"') do set "returnPath=%%~nxF"
  set "sourcePath=%~f1\.."
)

:resolvePath :: one folder at a time
for %%F in ("%sourcePath%") do (
  if "%%~nxF" equ "" (
    for %%P in ("%%~fF%returnPath%") do echo %%~P
    exit /b 0
  )
  for %%P in ("%sourcePath%\..") do (
    for /f "delims=> tokens=2" %%A in (
      'dir /ad /x "%%~fP"^|findstr /c:">          %%~nxF "'
    ) do for /f "tokens=1*" %%B in ("%%A") do set "returnPath=%%C\%returnPath%"
  ) || set "returnPath=%%~nxF\%returnPath%"
  set "sourcePath=%%~dpF."
)
goto :resolvePath

:err
>&2 echo Path not found
exit /b 1

The GOTO used to iterate the individual folders will slow the operation down if there are many folders. If I really wanted to optimize for speed, I could use FOR /F to invoke another batch process, and resolve each folder in an infinite FOR /L %%N IN () DO... loop, and use EXIT to break out of the loop once I reach the root. But I did not bother.


Developing robust utilities that can return the result in a variable

There are a number of edge cases that can complicate development of a robust script given that ^, %, and ! are all legal characters in file/folder names.

  • CALL doubles quoted ^ characters. There is no good solution to this problem, other than to pass the value by reference using a variable instead of as a string literal. This is not an issue if the input path uses only short names. But it could be an issue if the path uses a mixture of short and long names.

  • Passing % literals within batch arguments can be tricky. It can get confusing as to how many times (if at all) it should be doubled. Again it might be easier to pass the value by reference within a variable.

  • The CALLer may call the utility from within a FOR loop. If a variable or argument contains %, then expansion of %var% or %1 within a loop in the utility can lead to inadvertent FOR variable expansion because the FOR variables are global in scope. The utility must not expand arguments within a FOR loop, and variables can only be safely expanded within a FOR loop if delayed expansion is used.

  • Expansion of FOR variables containing ! will be corrupted if delayed expansion is enabled.

  • The CALLing environment may have delayed expansion enabled or disabled. Passing values containing ! and ^ across the ENDLOCAL barrier to a delayed expansion environment requires that quoted ! be escaped as ^!. Also, quoted ^ must be escaped as ^^, but only if the line contains !. Of course those characters should not be escaped if the CALLing environment has delayed expansion disabled.

I have developed robust utility forms of both the JScript and pure batch solutions that take into account all of the edge cases above.

The utilities expect the path as a string literal by default, but accept a variable name that contains the path if the /V option is used.

By default the utilities simply print the result to stdout. But the result can be returned in a variable if you pass the name of the return variable as an extra argument. The correct value is guaranteed to be returned, regardless whether delayed expansion is enabled or disabled in your CALLing environment.

Full documentation is embedded within the utilities, and can be accessed using the /? option.

There are a few obscure limitations:

  • The return variable name must not contain ! or % characters
  • Likewise /V option input variable name must not contain ! or % characters.
  • The input path must not contain internal double quotes. It is OK for the path to be enclosed within one set of double quotes, but there should not be any additional quotes within.

I have not tested whether the utilities work with unicode in path names, or if they work with UNC paths.


jLongPath.bat - hybrid JScript / batch

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
:::
:::jLongPath  [/V]  SrcPath  [RtnVar]
:::jLongPath  /?
:::
:::  Determine the absolute long-name path of source path SrcPath
:::  and return the result in variable RtnVar.
:::
:::  If RtnVar is not specified, then print the result to stderr.
:::
:::  If option /V is specified, then SrcPath is a variable that
:::  contains the source path.
:::
:::  If the first argument is /?, then print this help to stdout.
:::
:::  The returned ERROLEVEL is 0 upon success, 1 if failure.
:::
:::  jLongPath.bat version 1.0 was written by Dave Benham
:::

::----------- Batch Code-----------------
@echo off
setlocal disableDelayedExpansion
if /i "%~1" equ "/?" (
  for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
  exit /b 0
)
if /i "%~1" equ "/V" shift /1
(
  for /f "delims=* tokens=1,2" %%A in (
    'cscript //E:JScript //nologo "%~f0" %*'
  ) do if "%~2" equ "" (echo %%A) else (
    endlocal
    if "!!" equ "" (set "%~2=%%B" !) else set "%~2=%%A"
  )
) || exit /b 1
exit /b 0

------------ JScript Code---------------*/
try {
  var shortcut = WScript.CreateObject("WScript.Shell").CreateShortcut("dummy.lnk"),
      fso = new ActiveXObject("Scripting.FileSystemObject"),
      path=WScript.Arguments(0),
      folder='';
  if (path.toUpperCase()=='/V') {
    var env=WScript.CreateObject("WScript.Shell").Environment("Process");
    path=env(WScript.Arguments(1));
  }
  try {
    shortcut.TargetPath = fso.GetFile(path);
  }
  catch(e) {
    shortcut.TargetPath = fso.GetFolder(path);
    folder='\\'
  }
  var rtn = shortcut.TargetPath+folder+'*';
  WScript.StdOut.WriteLine( rtn + rtn.replace(/\^/g,'^^').replace(/!/g,'^!') );
}
catch(e) {
  WScript.StdErr.WriteLine(
    (e.number==-2146828283) ? 'Path not found' :
    (e.number==-2146828279) ? 'Missing path argument - Use jLongPath /? for help.' :
    e.message
  );
}


longPath.bat - Pure batch

:::
:::longPath  [/V]  SrcPath  [RtnVar]
:::longPath  /?
:::
:::  Determine the absolute long-name path of source path SrcPath
:::  and return the result in variable RtnVar.
:::
:::  If RtnVar is not specified, then print the result to stderr.
:::
:::  If option /V is specified, then SrcPath is a variable that
:::  contains the source path.
:::
:::  If the first argument is /?, then prints this help to stdout.
:::
:::  The returned ERROLEVEL is 0 upon success, 1 if failure.
:::
:::  longPath.bat version 1.0 was written by Dave Benham
:::
@echo off
setlocal disableDelayedExpansion

:: Load arguments
if "%~1" equ "" goto :noPath
if "%~1" equ "/?" (
  for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
  exit /b 0
)
if /i "%~1" equ "/V" (
  setlocal enableDelayedExpansion
  if "%~2" equ "" goto :noPath
  if not defined %~2!! goto :notFound
  for /f "eol=: delims=" %%F in ("!%~2!") do (
    endlocal
    set "sourcePath=%%~fF"
    set "test=%%F"
  )
  shift /1
) else (
  set "sourcePath=%~f1"
  set "test=%~1"
)

:: Validate path
if "%test:**=%" neq "%test%" goto :notFound
if "%test:?=%"  neq "%test%" goto :notFound
if not exist "%test%" goto :notFound

:: Resolve file name, if present
set "returnPath="
if not exist "%sourcePath%\*" (
  for /f "eol=: delims=" %%F in ('dir /b "%sourcePath%"') do set "returnPath=%%~nxF"
  set "sourcePath=%sourcePath%\.."
)

:resolvePath :: one folder at a time
for /f "delims=* tokens=1,2" %%R in (^""%returnPath%"*"%sourcePath%"^") do (
  if "%%~nxS" equ "" for %%P in ("%%~fS%%~R") do (
    if "%~2" equ "" (
      echo %%~P
      exit /b 0
    )
    set "returnPath=%%~P"
    goto :return
  )
  for %%P in ("%%~S\..") do (
    for /f "delims=> tokens=2" %%A in (
      'dir /ad /x "%%~fP"^|findstr /c:">          %%~nxS "'
    ) do for /f "tokens=1*" %%B in ("%%A") do set "returnPath=%%C\%%~R"
  ) || set "returnPath=%%~nxS\%%~R"
  set "sourcePath=%%~dpS."
)
goto :resolvePath

:return
set "delayedPath=%returnPath:^=^^%"
set "delayedPath=%delayedPath:!=^!%"
for /f "delims=* tokens=1,2" %%A in ("%delayedPath%*%returnPath%") do (
  endlocal
  if "!!" equ "" (set "%~2=%%A" !) else set "%~2=%%B"
  exit /b 0
)

:noPath
>&2 echo Missing path argument - Use longPath /? for help.
exit /b 1

:notFound
>&2 echo Path not found
exit /b 1
Sheela answered 26/12, 2015 at 18:19 Comment(4)
Never had this problem but good to know that there is some solutions, Thanks !Predecessor
I guess line 31 of longPath.bat should read if not defined !%~2!, is it not?Ganesa
@Ganesa - No, the odd syntax is intentional. I need the IF statement to have valid syntax when %~2 is undefined. The !! is there when the line is parsed, so the IF condition is valid regardless. Then at execution the !! becomes nothing because delayed expansion is enabled.Sheela
So I am having issues converting my windows PATH variable which is completely made up of short paths into long path names using this script. I have come up with the command START "" /B CMD /E:ON /V:ON /Q /D /C "FOR %G IN (%PATH%) DO CALL C:\Windows\System32\LongPath.bat %G" to be run in cmd.exe which outputs 80% of the PATH shortnames correctly in their long name format as desired. However some file paths are left in the short name form and I can't figure out why. Anyone have any ideas?Consumedly
F
2

Facing the same problem, I found a simpler solution in pure batch using the ATTRIB command.

attrib.exe outputs the long file name, even when getting a short file name as argument.
But unfortunately, it does not expand the short names in the path either.
Like in the other solution, this requires looping on the whole pathname to get the full long pathname.

A further difficulty comes from the fact that when the pathname does not exist, attrib.exe outputs an error message on stdout ending with the pathname (just like the normal output), and does not return an errorlevel. This can be worked around by filtering the attrib.exe output to remove lines that contain a - before the drive name.

Test program for the :GetLongPathname expansion routine:

@echo off

:# Convert a short or long pathname to a full long pathname
:GetLongPathname %1=PATHNAME %2=Output variable name
setlocal EnableDelayedExpansion
set "FULL_SHORT=%~fs1"           &:# Make sure it really is short all the way through
set "FULL_SHORT=%FULL_SHORT:~3%" &:# Remove the drive and initial \
set "FULL_LONG=%~d1"             &:# Begin with just the drive
if defined FULL_SHORT for %%x in ("!FULL_SHORT:\=" "!") do ( :# Loop on all short components
  set "ATTRIB_OUTPUT=" &:# If the file does not exist, filter-out attrib.exe error message on stdout, with its - before the drive.
  for /f "delims=" %%l in ('attrib "!FULL_LONG!\%%~x" 2^>NUL ^| findstr /v /c:" - %~d1"') do set "ATTRIB_OUTPUT=%%l"
  if defined ATTRIB_OUTPUT ( :# Extract the long name from the attrib.exe output
    for %%f in ("!ATTRIB_OUTPUT:*\=\!") do set "LONG_NAME=%%~nxf"
  ) else (                   :# Use the short name (which does not exist)
    set "LONG_NAME=%%~x"
  )
  set "FULL_LONG=!FULL_LONG!\!LONG_NAME!"
) else set "FULL_LONG=%~d1\"
endlocal & if not "%~2"=="" (set "%~2=%FULL_LONG%") else echo %FULL_LONG%
exit /b

Sample output:

C:\JFL\Temp>test "C:\Progra~1\Window~1"
C:\Program Files\Windows Defender

C:\JFL\Temp>test "\Progra~1\Not There\Not There Either"
C:\Program Files\Not There\Not There Either

C:\JFL\Temp>

PS. I wish Microsoft had added a %~l modifier to the FOR command, doing the exact opposite of the %~s modifier. This would have avoided such acrobatics. In an ideal world...

Foresight answered 28/10, 2020 at 18:58 Comment(0)
T
0

longPathUsingPython.bat

@echo off
set SFN=%1
for /F %%i in ('python -c
  "import sys;from os.path import realpath;print(realpath(sys.argv[1]))"
    %SFN%') do set LFN=%%i
echo ShortFileName: %SFN%
echo LongFileName : %LFN%

Usage example:

(base) C:\VldTest1\cmd_exe>longPathUsingPython C:\PRAB99~1\OCTAVE~1.0\usr\bin\python.exe
ShortFileName: C:\PRAB99~1\OCTAVE~1.0\usr\bin\python.exe
LongFileName : C:\ProgramFilesVld_x64\Octave-8.1.0\usr\bin\python.exe
Thebaine answered 2/4, 2023 at 18:38 Comment(0)
M
-1

For Python, use:

from os import realname

long_name = realname(short_name)
Maser answered 30/11, 2022 at 10:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.