Question

The following command in a batch file does not work as expected/hoped:

    echo %~nxs1


Here is a sample display of what I’m trying to get:

C:\>type test.bat
@dir /b %1
@echo %~nxs1

C:\>test "C:\Documents and Settings\All Users\ntuser.dat"
ntuser.dat
NTUSER.DAT

C:\>test "C:\Documents and Settings\All Users\ntuser.data"
ntuser.data
NTUSER~1.DA~

C:\>test "C:\Documents and Settings\All Users\ntuser.dat.baz"
ntuser.dat.baz
NTUSER~1.BAZ

C:\>test "C:\Documents and Settings\All Users\foo.bar.baz"
File Not Found
foo.bar.baz (or FOO~1.BAZ or whatever, doesn’t really matter since
             file does not exist, though latter would be nice)

Instead what I get is something like the following (depending on assigned short-filenames):

C:\>type test.bat
@dir /b %1
@echo %~nxs1

C:\>test "C:\Documents and Settings\All Users\ntuser.dat"
ntuser.dat
s\ntuser.dat

C:\>test "C:\Documents and Settings\All Users\ntuser.data"
ntuser.data
s\ntuser.data

C:\>test "C:\Documents and Settings\All Users\ntuser.dat.baz"
ntuser.dat.baz
z

C:\>test "C:\Documents and Settings\All Users\foo.bar.baz"
File Not Found
s\foo.bar.baz



Basically, I need to pass a filename to a BAT file and have the script get (eg display) it as a short-filename, but only the filename and extension, no drive or path.

The help info for FOR gives %~fsI as an example, but that has the whole path as a short-filename, not just the file. Does anyone know how to combine the S parameter from %~ without getting the whole path?


Thanks a lot.



Updates

  1. I am not looking for a solution in a different language, I need the BAT command to work.

  2. It seems that it works for others, so I am checking to see if it is some kind of alternate configuration issue. I’m currently testing to see if Command Processor Extensions could be the cause.

  3. It won’t work at all if Extensions are disabled (obviously), so I’m going on the hypothesis that it is a bug that was fixed in a subsequent service pack (the system I tested on is XP SP1). I’m testing SP2 and SP3 today…

Was it helpful?

Solution

Well I just confirmed it. I tested the script with CMD.EXE from XP SP1, SP2, and SP3 as well as an SP2 VM installation. It gave the aforementioned erroneous results with the SP1 version, but worked correctly in the SP2 and SP3 versions. So it is indeed a bug that was fixed. For anyone else who runs into this, the CMD.EXE file from SP2+ can be dropped into an SP1 installation without issue (assuming that an update is not feasible).

OTHER TIPS

Look at this forum post. The code looks like:

%~snx
s ... short 
n ... name
x ... extension

I don't have a problem executing your batch. Hope someone can help you soon. But while you are at it, here's an alternative done with Vbscript, which i think you should get familiar with.

Set objArgs = WScript.Arguments
strFile = objArgs(0)
WScript.Echo CreateObject("Scripting.FileSystemObject").GetFile(strFile).ShortName

on the command line (or your batch), call it like this

C:\test>cscript //nologo getshortname.vbs "C:\Documents and Settings\All Users\Desktop\shortcut.lnk"
shortcut.lnk

1- Save the code in ShortFileName.Vbs

2- Drag n Drop any folder or file to this script

    Set fso=CreateObject("Scripting.FileSystemObject")
    ' Is object a file or folder?

    If fso.FolderExists(WScript.Arguments(0)) Then
       'The dropped stuff is a folder
       Set objFolder = fso.GetFolder(WScript.Arguments(0))
       rtrn = InputBox("Short path is :", "SHORT PATH", objFolder.ShortPath)
    End If

    If fso.FileExists(WScript.Arguments(0)) Then
       'The dropped stuff is a file
       Set objFile = fso.GetFile(WScript.Arguments(0))
       rtrn = InputBox("Short path is :", "SHORT PATH", objFile.ShortPath)
    End If

There is another way of doing this, compile this code in VS:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;

namespace ConvFN
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 3)
            {
                if ((args[2].Length > 1) && System.IO.File.Exists(args[2]))
                {
                    if (args[1].Equals("-l")) Console.WriteLine(ShortLongFName.GetLongPathName(args[2]));
                    if (args[1].Equals("-s")) Console.WriteLine(ShortLongFName.ToShortPathName(args[2]));
                }
            }
        }
    }

    public class ShortLongFName
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern uint GetShortPathName(
           [MarshalAs(UnmanagedType.LPTStr)]
   string lpszLongPath,
           [MarshalAs(UnmanagedType.LPTStr)]
   StringBuilder lpszShortPath,
           uint cchBuffer);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.U4)]
        private static extern int GetLongPathName(
            [MarshalAs(UnmanagedType.LPTStr)]
        string lpszShortPath,
            [MarshalAs(UnmanagedType.LPTStr)]
        StringBuilder lpszLongPath,
            [MarshalAs(UnmanagedType.U4)]
        int cchBuffer);

        /// <summary>
        /// Converts a short path to a long path.
        /// </summary>
        /// <param name="shortPath">A path that may contain short path elements (~1).</param>
        /// <returns>The long path.  Null or empty if the input is null or empty.</returns>
        internal static string GetLongPathName(string shortPath)
        {
            if (String.IsNullOrEmpty(shortPath))
            {
                return shortPath;
            }

            StringBuilder builder = new StringBuilder(255);
            int result = GetLongPathName(shortPath, builder, builder.Capacity);
            if (result &gt; 0 && result &lt; builder.Capacity)
            {
                return builder.ToString(0, result);
            }
            else
            {
                if (result &gt; 0)
                {
                    builder = new StringBuilder(result);
                    result = GetLongPathName(shortPath, builder, builder.Capacity);
                    return builder.ToString(0, result);
                }
                else
                {
                    throw new FileNotFoundException(
                        string.Format(
                        CultureInfo.CurrentCulture,
                        "{0} Not Found",
                        shortPath),
                        shortPath);
                }
            }
        }
        /// <summary>
        /// The ToLongPathNameToShortPathName function retrieves the short path form of a specified long input path
        /// </summary>
        /// <param name="longName">The long name path</param>
        /// <returns>A short name path string</returns>
        public static string ToShortPathName(string longName)
        {
            uint bufferSize = 256;

            // don´t allocate stringbuilder here but outside of the function for fast access
            StringBuilder shortNameBuffer = new StringBuilder((int)bufferSize);

            uint result = GetShortPathName(longName, shortNameBuffer, bufferSize);

            return shortNameBuffer.ToString();
        }
    }
}

Add this into the Console C# project called ConvFN and build it. Then invoke ConvFN -s %1 from the batch file where the %1 parameter is a long file name and it will output the short filename equivalent...like wise the inverse, ConvFN -l %1 where %1 is the short filename and it will output the long filename equivalent.

This code was taken from pinvoke.net.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top