Batch script to check whether the required NPM package is installed or not doesn't work reliably - batch-file

I have come up with a batch script, through some Googling and analysis on batch scripts, for checking if the package I mention in user prompt exists in system and get its version if it does. Here it is in its most possible final version:
#echo off && setlocal EnableDelayedExpansion
set "output_cnt=0"
set /p "pkg=Package? "
for /f "delims=" %%a in ('npm -v 2^>nul') do #set "npmV=%%a"
if defined npmV (echo NPM Version: %npmV%) else (echo NPM isn't installed)
for /f "delims=" %%b in ('node -v 2^>nul') do #set "nodeV=%%b"
if defined nodeV (echo Node Version: %nodeV%) else (echo Node isn't installed)
for /f "delims=" %%c in ('npm list -g %pkg% 2^>nul') do (
set /a output_cnt+=1
set "pkgV[!output_cnt!]=%%c"
)
if defined pkgV (for /l %%n in (1 1 !output_cnt!) do echo !pkg! Version: !pkgV[%%n]!) else (echo %pkg% isn't installed)
setlocal DisableDelayedExpansion && endlocal && pause
Now when I run this script, it does check properly whether Node and NPM are installed and gets their version accurately, but when I pass any package name to the user prompt (e.g. cowsay, and this package is installed, of that I am sure), it always says <package> isn't installed(as in cowsay isn't installed).
As you can see I am trying to capture full output of npm list -g <package> with multiple lines through array, but it clearly screws up.
Can anyone assist in figuring out what's wrong and get what I want?

Welcome to batch; arrays aren't real!*
Instead, you've got a collection of variables that all simply happen to start with pkgV[ that you're able to iterate over. So while %pkgV[1]%, %pkgV[2]%, etc. are all defined, %pkgV% by itself isn't because that particular variable was never set.
However, since you'll always have a %pkgV[1]% if any valid packages have been provided, you can check that pkgV[1] is defined and go from there. (Alternately, you can simply check that %output_cnt% is greater than 0.)
if defined pkgV[1] (for /l %%n in (1 1 !output_cnt!) do echo !pkg! Version: !pkgV[%%n]!) else (echo %pkg% isn't installed)
* - At least, not in the way that other languages do arrays.

Why do you use a (pseudo-)array after all? Why not simply doing all in a single loop?
I see two possible code portions to replace the for /f %%c loop and the subsequent if defined pkgV condition (which actually is the faulty code fragment as explained in SomethingDark's answer) with:
Use a flag-style variable that indicates whether npm list returned any items:
#echo off & setlocal EnableExtensions DisableDelayedExpansion
set "output_cnt=0"
set /p "pkg=Package? "
for /f "delims=" %%a in ('npm -v 2^>nul') do #set "npmV=%%a"
if defined npmV (echo NPM Version: %npmV%) else (echo NPM isn't installed)
for /f "delims=" %%b in ('node -v 2^>nul') do #set "nodeV=%%b"
if defined nodeV (echo Node Version: %nodeV%) else (echo Node isn't installed)
set "FLAG="
for /f "delims=" %%c in ('npm list -g %pkg% 2^>nul') do (
set "FLAG=#" & echo !pkg! Version: %%c
)
if not defined FLAG echo %pkg% isn't installed
pause
Detect the exit code of the for /f %%c loop by conditional execution, which is set to 1 when the loop doesn't iterate:
#echo off & setlocal EnableExtensions DisableDelayedExpansion
set "output_cnt=0"
set /p "pkg=Package? "
for /f "delims=" %%a in ('npm -v 2^>nul') do #set "npmV=%%a"
if defined npmV (echo NPM Version: %npmV%) else (echo NPM isn't installed)
for /f "delims=" %%b in ('node -v 2^>nul') do #set "nodeV=%%b"
if defined nodeV (echo Node Version: %nodeV%) else (echo Node isn't installed)
(
for /f "delims=" %%c in ('npm list -g %pkg% 2^>nul') do (
echo !pkg! Version: %%c
)
) || echo %pkg% isn't installed
pause
N. B.:
By the way, the command sequence setlocal DisableDelayedExpansion && endlocal at the end doesn't make any sense at all, since the state afterwards is just the same as without it.
If the intention was to surely have delayed expansion disabled at the pause command, you should have replaced your initial line with #echo off & setlocal EnableExtensions DisableDelayedExpansion and have inserted the line setlocal EnableDelayedExpansion before the for /f %%c loop in your original code.

If there will only be one installed version of any input package, then perhaps this would work for you:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "nodeV="
For %%G In ("node.js") Do For /F %%H In (
'"%%~$PATH:%%G" -v') Do Set "nodeV=%%H"
If Defined nodeV (Echo Node Version: %nodeV%
) Else Echo node.js is not in your %%PATH%%
Set "npmV="
For %%G In ("npm.cmd") Do For /F %%H In (
'"%%~$PATH:%%G" -v') Do Set "npmV=%%H"
If Defined npmV (Echo NPM Version: %npmV%
) Else (Echo npm.cmd is not in your %%PATH%%
Echo Press any key to exit ...
Pause 1>NUL
Exit /B)
:GetPkg
Set "pkg="
Set /P "pkg=Package? "
If Not Defined pkg GoTo GetPkg
Set "pkgV="
For /F "Tokens=2 Delims=#" %%G In (
'npm.cmd list "%pkg%" -g 2^>NUL
^| %SystemRoot%\System32\find.exe "#"'
) Do Set "pkgV=%%G"
If Defined pkgV (Echo %pkg% Version: %pkgV%
) Else Echo %pkg% is not globally installed
Pause
EndLocal
GoTo :EOF
However, if there could be multiple installed versions of your input package, as implied by your code, then this modification may prove more useful:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "nodeV="
For %%G In ("node.js") Do For /F %%H In (
'"%%~$PATH:%%G" -v') Do Set "nodeV=%%H"
If Defined nodeV (Echo Node Version: %nodeV%
) Else Echo node.js is not in your %%PATH%%
Set "npmV="
For %%G In ("npm.cmd") Do For /F %%H In (
'"%%~$PATH:%%G" -v') Do Set "npmV=%%H"
If Defined npmV (Echo NPM Version: %npmV%
) Else (Echo npm.cmd is not in your %%PATH%%
Echo Press any key to exit ...
Pause 1>NUL
Exit /B)
:GetPkg
Set "pkg="
Set /P "pkg=Package? "
If Not Defined pkg GoTo GetPkg
For /F "Delims==" %%G In ('"(Set pkgV[) 2>NUL"'
) Do Set "%%G="
Set "output_cnt=0"
For /F "Tokens=2 Delims=#" %%G In (
'npm.cmd list %pkg% -g 2^>NUL
^| %SystemRoot%\System32\find.exe "#"'
) Do (Set /A output_cnt += 1
SetLocal EnableDelayedExpansion
For %%H In (!output_cnt!) Do (EndLocal
Set "pkgV[%%H]=%%G"))
If %output_cnt GEq 1 (
For /F "Tokens=2,* Delims=[]=" %%G In (
'"(Set pkgV[) 2>NUL"'
) Do Echo %pkg% version %%G: %%H
) Else Echo %pkg% is not globally installed
Pause
EndLocal
GoTo :EOF
Please note, that none of the above code has been tested at all, if there are obvious typos, or errors, please let me know, and I'll take a look at it later.

Related

what environment issue cause batch file function not working properly

I have one script and one configure file.
in configure file mainly have
SNFold_1="ORKI App 1.0.0.0219"
SNFile_1="8520010008_852001.T3A.pkg"
SNPrm_1="8520010008_852001.P3A.txt"
SNXML_1="852001.xml"
SNFold_2="iConnect REST Server 1.8.0.0017"
SNFile_2="8296831817_829683.P3A.pkg"
SNPrm_2="8296831817_829683.P3A.txt"
SNXML_2="829683.xml"
In the main script file
I have below code:
#echo off
set SNFold_1=%SNFold_1:"=%
set SNFile_1=%SNFile_1:"=%
set SNPrm_1=%SNPrm_1:"=%
set SNXML_1=%SNXML_1:"=%
set SNFold_2=%SNFold_2:"=%
set SNFile_2=%SNFile_2:"=%
set SNPrm_2=%SNPrm_2:"=%
set SNXML_2=%SNXML_2:"=%
call:signFunc "%SNFold_1%" %SNFile_1% %SNPrm_1% %SNXML_1%
call:signFunc "%SNFold_2%" %SNFile_2% %SNPrm_2% %SNXML_2%
call:end
EXIT /B %ERRORLEVEL%
:signFunc - here starts Sign function
::::::::::::::::::::::::
:: prepare sign xml file
::::::::::::::::::::::::
cd %NEW_PACKAGE_BASE_PATH%\%NEW_PACKAGE_NAME%\%New_PACK_PTN%\Sign\xml
copy %NEW_PACKAGE_BASE_PATH%\%NEW_PACKAGE_NAME%\%New_PACK_PTN%\Sign\Temp\sample.xml .
Set "search=sign folder"
Set "replace=%~1"
Set "textfile=sample.xml"
Set "newfile=indesnew.txt"
(
For /F "Tokens=1* Delims=:" %%A In ('FindStr /N "^" "%textfile%"') Do (
If "%%B"=="" (
Echo=
) Else (
Set "line=%%B"
SetLocal EnableDelayedExpansion
Set "line=!line:%search%=%replace%!"
Echo=!line!
EndLocal
)
)
)>"%newfile%"
Del "%textfile%"
Ren "%newfile%" "%textfile%"
Set "search=sign binary file"
Set "replace=%~2"
Set "textfile=sample.xml"
Set "newfile=indesnew.txt"
(
For /F "Tokens=1* Delims=:" %%A In ('FindStr /N "^" "%textfile%"') Do (
If "%%B"=="" (
Echo=
) Else (
Set "line=%%B"
SetLocal EnableDelayedExpansion
Set "line=!line:%search%=%replace%!"
Echo=!line!
EndLocal
)
)
)>"%newfile%"
Del "%textfile%"
Ren "%newfile%" "%textfile%"
Set "search=sign parameter"
Set "replace=%~3"
Set "textfile=sample.xml"
Set "newfile=indesnew.txt"
(
For /F "Tokens=1* Delims=:" %%A In ('FindStr /N "^" "%textfile%"') Do (
If "%%B"=="" (
Echo=
) Else (
Set "line=%%B"
SetLocal EnableDelayedExpansion
Set "line=!line:%search%=%replace%!"
Echo=!line!
EndLocal
)
)
)>"%newfile%"
Del "%textfile%"
Ren "%newfile%" "%textfile%"
rename sample.xml %~4
cd "%SignKit_Path%"
call antCmdLine.bat %NEW_PACKAGE_BASE_PATH%\%NEW_PACKAGE_NAME%\%New_PACK_PTN%\Sign\xml\%~4 mockup
EXIT /B 0
:end
move "%SignKit_Path%\sign_file.log" "%NEW_PACKAGE_BASE_PATH%\%NEW_PACKAGE_NAME%\%New_PACK_PTN%\Sign\Temp"
#RD /S /Q "%NEW_PACKAGE_BASE_PATH%\%NEW_PACKAGE_NAME%\%New_PACK_PTN%\Sign\xml"
now the issue was this.
I ran it in my computer it ran smoothly from
call:signFunc "%SNFold_1%" %SNFile_1% %SNPrm_1% %SNXML_1%
then second
call:signFunc "%SNFold_2%" %SNFile_2% %SNPrm_2% %SNXML_2%
But when it ran in my colleague computer it came across this issue
call:signFunc "%SNFold_1%" %SNFile_1% %SNPrm_1% %SNXML_1%
ran fine
but just after that I got a pause to ask Press any key to continue...
After hit any key, then the second loop was running like #echo on and even hanging...
Both my colleague and I are using Windows 10
Is any thing different on my colleague's computer cause this happen?
Based upon your input, I just modified two things and make that works easily
1) Changed the format of configure file. In this file.cfg, I list
parameters as below:
"ORKI App 1.0.0.0219" 8295650401_CAVDEV.P3A.pkg 8295650401_CAVDEV.P3A.jsat 829565.xml
"iConnect REST Server 1.8.0.0017" 8296831817_829683.P3A.pkg 8296831817_829683.P3A.txt 829683.xml
2)Then In the main script file
for /f "tokens=1-4 delims=," %%A in (file.cfg) do call :signFunc %%A %%B %%C %%D
:signFunc - here starts Sign function
And it works OK.

How can we split string using windows bat

How can we split string using windows bat script?
for below .bat code snippet
#echo off & setlocal EnableDelayedExpansion
set j=0
for /f "delims=""" %%i in (config.ini) do (
set /a j+=1
set con!j!=%%i
call set a=%%con!j!%%
echo !a!
(echo !a!|findstr "^#">nul 2>nul && (
rem mkdir !a!
) || (
echo +)
rem for /f "tokens=2" %%k in(config.ini) do echo %%k
)
)
pause
below config file
Q
What's wrong when I del rem at the begin of rem for /f "tokens=2" %%k in(config.ini) do echo %%k
How can I get the /path/to/case and value as a pair?
for /f xxxx in (testconfig.ini) do (set a=/path/to/case1 set b=vaule1)
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q43407067.txt"
set j=0
for /f "delims=""" %%i in (%filename1%) do (
set /a j+=1
set con!j!=%%i
call set a=%%con!j!%%
echo !a! SHOULD BE EQUAL TO %%i
(echo !a!|findstr "^#">nul 2>nul && (
echo mkdir !a!
) || (
echo +)
for /f "tokens=2" %%k IN ("%%i") do echo "%%k"
for /f "tokens=1,2" %%j IN ("%%i") do echo "%%j" and "%%k"
)
)
ECHO ----------------------------
SET con
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
I used a file named q43407067.txt containing your data for my testing.
(These are setting that suit my system)
SO - to address your problems:
because the ) on that line closes the ( on the previous. The ) on that line closes the ( on the one prior. (I changed the rem to an echo so that the code would produce something visible) The first ( on the (echo !a! line is closed by the ) on the line following the (now) two for /f commands. and the ( on the for..%%i..do( is closed by the final ) before the echo -----
You can't delete that ) because it's participating in a parenthesis-pair.
You need a space between the in and the (.
I've shown a way. See for /?|more from the prompt for documentation (or many articles here on SO)
In your code, !a! is the same as %%i - so I've no idea why you are conducting all the gymnastics - doubtless to present a minimal example showing the problem.
Note that since the default delimiters include Space then if any line contains a space in the /path/to/case or value then you'll have to re-engineer the approach.
I' not sure if I understand what exactly it is you need, so what follows may not suit your needs:
#Echo Off
SetLocal EnableDelayedExpansion
Set "n=0"
For /F "Delims=" %%A In (testConfig.ini) Do (Set "_=%%A"
If "!_:~,1!"=="#" (Set/A "n+=1", "i=0"
Echo=MD %%A
Set "con[!n!]!i!=%%A") Else (For /F "Tokens=1-2" %%B In ('Echo=%%A'
) Do (Set/A "i+=1"
Set "con[!n!]!i!=%%B"&&Set/A "i+=1"&&Set "con[!n!]!i!=%%C")))
Set con[
Timeout -1
GoTo :EOF
remove Echo= on line 6 if you are happy with the output and really want to create those directories

FOR statement returning blank variable for command output

The third FOR statement in the code below is supposed to return '18066' from the command output from 'java full version "1.8.0_66-b18" but it is returning blank, which causes the comparison that follows to fail. Can anyone offer some help?
#echo off
set MSIFileName=jre1.8.0_66.msi
set KeyName="HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe"
for /f tokens^=2-5^ delims^=e._m^" %%a in ("%MSIFilename%") do set VersionToInstall=%%a%%b%%c%%d
for /f "tokens=2*" %%h in ('reg query %KeyName% /v Path') do set JavaHome=%%i
PATH "%PATH%;%JavaHome%"
for /f tokens^=2-5^ delims^=.-_^" %%j in ('java -fullversion 2^>^&1') do set InstalledVersion=%%j%%k%%l%%m
if "%InstalledVersion%."=="." goto INSTALL
if "%InstalledVersion%" GEQ "%VersionToInstall%" goto END
TIA for the replies.
Note: I borrowed some code from user npocmaka found here.
-EDIT-
I still don't have an answer for why the original code didn't work but I found a workaround that has my script working. Rather than adding JavaHome to system PATH so the script could find java.exe I used 'cd' instead. In hope that this helps someone else here's the updated code...
#echo off
set MSIFileName=jre1.8.0_66.msi
set KeyName="HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe"
set JavaHome=0
set IsNumber=0
for /f tokens^=2-5^ delims^=e._^" %%a in ("%MSIFilename%") do set VersionToInstall=%%a%%b%%c%%d
for /f "tokens=2*" %%e in ('reg query %KeyName% /v Path') do set JavaHome="%%f"
if %JavaHome% EQU 0 goto INSTALL
cd %JavaHome%
for /f tokens^=2-5^ delims^=._-^" %%g in ('java -fullversion 2^>^&1') do set InstalledVersion=%%g%%h%%i%%j
for /f "tokens=* delims=0123456789" %%k in ("%InstalledVersion%") do (
if "[%%k]" EQU "[]" set IsNumber=1
)
if %IsNumber% EQU 0 goto INSTALL
if "%InstalledVersion%" GEQ "%VersionToInstall%" goto END
for /f "tokens=4 delims= " %%j in ('java -fullversion 2^>^&1') do set InstalledVersion=%%~j
echo %InstalledVersion%
works happily for me.
1.8.0_73-b02
from my version
java full version "1.8.0_73-b02"

Batch file conditions - Execute next statement only if FOR loop is true

I built/adapted a script to uninstall any previous version of Java before it install a new one. The script goes as follows;
1 - Uninstall any previous version of JAVA
SET regVar32=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WinDOws\CurrentVersion\Uninstall
SET regVar64=HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\WinDOws\CurrentVersion\Uninstall\
SET myCMD=REG QUERY %regVar32% /s /f *java*
FOR /f " usebackq delims={} tokens=2" %%i IN (`%myCMD%`) DO ( msiexec /x {%%i} /qn /norestart )
SET myCMD=REG QUERY %regVar64% /s /f *java*
FOR /f " usebackq delims={} tokens=2" %%i IN (`%myCMD%`) DO ( msiexec /x {%%i} /qn /norestart )
2 - Clean registry
3 - Clean files and folders
4 - install new version of JAVA
The problem is that the script doesn't have any condition and if the routine 1 doesn't find anything to uninstall it will continue to execute the others subroutines.
What I want to be able to do is that if routine number 1 above doesn't have anything to uninstall GOTO :INSTALL and install the new JAVA without running 2 and 3.
I hope I explained myself clear enough ;-) thank you in advance for any help.
(for /f .... do (msiexec .... )) || goto :install
If the for command does not find any line to process, it raises errorlevel. Using conditional execution you can detect it and directly jump to the required label.
In other words, you want to know if the two for commands in step 1-Uninstall processed any file. You may do that this way:
SET anyFileUninstalled=false
SET myCMD=REG QUERY %regVar32% /s /f *java*
FOR /f " usebackq delims={} tokens=2" %%i IN (`%myCMD%`) DO (
msiexec /x {%%i} /qn /norestart
SET anyFileUninstalled=true
)
SET myCMD=REG QUERY %regVar64% /s /f *java*
FOR /f " usebackq delims={} tokens=2" %%i IN (`%myCMD%`) DO (
msiexec /x {%%i} /qn /norestart
SET anyFileUninstalled=true
)
if %anyFileUninstalled% neq true goto install
So I finally was able to get it right with some help from you guys here. Here is how it ended up;
ECHO -------------------------------------------------------
ECHO UNISTALL ANY PREVIOUS VERSIONS OF JAVA 32 Bit
ECHO -------------------------------------------------------
SET uinstallState=false
SET jver="Java 7"
SET regVar32=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
SET myCMD=REG QUERY %regVar32% /s /f %jver%
FOR /f " usebackq delims={} tokens=2" %%i IN (`%myCMD%`) DO (
SET uinstallState=true
ECHO Uninstall Key: {%%i}
ECHO Condition: %uinstallState%
)
ECHO -------------------------------------------------------
ECHO UNISTALL ANY PREVIOUS VERSIONS OF JAVA 64 Bit
ECHO -------------------------------------------------------
SET uinstallState=false
SET jver="Java 7"
SET regVar64=HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\
SET myCMD=REG QUERY %regVar64% /s /f %jver%
FOR /f " usebackq delims={} tokens=2" %%i IN (`%myCMD%`) DO (
SET uinstallState=true
ECHO Uninstall Key: {%%i}
ECHO Condition: %uinstallState%
)
IF %uinstallState% NEQ true GOTO INSTALL
So now it will Skip the subroutine 2 and 3 and go straight to INSTALL if there is nothing to uninstall. :-)
Thank you all for the help.

Get full path and long file name from short file name

I have an nice console file manager (eXtreme by Senh Liu), it passes short path/filenames as variables to a menu.bat.
How can I generate a full folder name + long file name?
Example:
input variable = "P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL"
target variable = "P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal"
I have tried the following:
SET my_file=%~2:
echo %my_file% produces: "P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL"
FOR /F "tokens=* USEBACKQ" %%F IN (`dir /B %2`) DO SET my_file=%%~fF:
echo %my_file% produces: "P:\MYPROG~1\SHELLS\zBackup\RefsToMyData.bal"
FOR /F "tokens=* USEBACKQ" %%F IN (`dir /B %2`) DO SET my_file=%%~dpnxF:
echo %my_file% produces: "P:\MYPROG~1\SHELLS\zBackup\RefsToMyData.bal"
Simple solution: use PowerShell.
PS C:\> (Get-Item 'P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL').FullName
P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal
You can incorporate a PowerShell call in a batch file like this:
#echo off
setlocal
for /f "usebackq delims=" %%f in (
`powershell.exe -Command "(Get-Item '%~1').FullName"`
) do #set "var=%%~f"
echo %var%
Output:
C:\> test.cmd P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL
P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal
PowerShell is available for all supported Windows versions:
Windows XP SP3 and Server 2003 SP2: PowerShell v2 available
Windows Vista and Server 2008: ship with PowerShell v1 (not installed by default), PowerShell v2 available
Windows 7 and Server 2008 R2: PowerShell v2 preinstalled, PowerShell v3 available (batteries not included)
Windows 8 and Server 2012: PowerShell v3 preinstalled
If PowerShell can't be used for some reason (e.g. administrative restrictions), I'd use VBScript instead:
name = WScript.Arguments(0)
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(name) Then
Set f = fso.GetFile(name)
ElseIf fso.FolderExists(name) Then
Set f = fso.GetFolder(name)
If f.IsRootFolder Then
WScript.Echo f.Path
WScript.Quit 0
End If
Else
'path doesn't exist
WScript.Quit 1
End If
Set app = CreateObject("Shell.Application")
WScript.Echo app.NameSpace(f.ParentFolder.Path).ParseName(f.Name).Path
A VBScript like the one above can be used in a batch file like this:
#echo off & setlocal
for /f "delims=" %%f in ('cscript //NoLogo script.vbs "%~1"') do #set "var=%%~f"
echo %var%
This does require an additional script file, though.
The following should work with any valid path, as long as it is not a UNC path. The path may be absolute or relative. It may use short file names or long names (or a mixture). The path may refer to a folder or a file.
The result will end with \ if it is a folder, no \ at end if it is a file.
The :getLongPath routine expects an inputPath variable name as the 1st argument, and an optional return variable name as the 2nd argument. The inputPath variable should contain a valid path. If the return variable is not speciied, then the result is ECHOed to the screen (enclosed in quotes). If the return variable is specified, then the result is returned in the variable (without quotes).
The routine should only be called when delayed expansion is disabled if you are returning a variable. If called with delayed expansion enabled, then the result will be corrupted if it contains the ! character.
Test cases (for my machine only) are at the top of the script, the actual routine at the bottom.
#echo off
setlocal
for %%F in (
"D:\test\AB2761~1\AZCFE4~1.TXT"
"AB2761~1\AZCFE4~1.TXT"
"D:\test\AB2761~1\ZZCE57~1\"
"D:\test\a b\a z.txt"
"D:\test\a b\z z"
"."
"\"
"x%%&BAN~1\test"
"x%% & bang!\test"
) do (
echo(
echo resolving %%F
set "shortPath=%%~F"
call :getLongPath shortPath longPath
set longPath
)
echo(
echo(
set "shortPath=D:\test\AB2761~1\AZCFE4~1.TXT"
set shortPath
echo Calling :getLongPath with with no return variable
call :getLongPath shortPath
exit /b
:getLongPath path [rtnVar]
setlocal disableDelayedExpansion
setlocal enableDelayedExpansion
for %%F in ("!%~1!") do (
endlocal
set "sourcePath=%%~sF"
set "sourceFile=%%~nxF"
)
if not exist "%sourcePath%" (
>&2 echo ERROR: Invalid path
exit /b 1
)
set "rtn="
2>nul cd "%sourcePath%" || (
cd "%sourcePath%\.."
for /f "eol=: delims=" %%F in ('dir /b /a-d "%sourceFile%"') do set "rtn=%%F"
)
:resolveFolders
for %%F in ("%cd%") do (
cd ..
set "folder=%%~nxF"
)
if defined folder for /f "eol=: delims=" %%: in ('dir /b /ad') do (
if /i "%%~snx:" equ "%folder%" (
set "rtn=%%:\%rtn%"
goto :resolveFolders
)
)
set "rtn=%cd%%rtn%
( endlocal
if "%~2" equ "" (echo "%rtn%") else set "%~2=%rtn%"
)
=== OUTPUT ===
resolving "D:\test\AB2761~1\AZCFE4~1.TXT"
longPath=D:\test\a b\a z.txt
resolving "AB2761~1\AZCFE4~1.TXT"
longPath=D:\test\a b\a z.txt
resolving "D:\test\AB2761~1\ZZCE57~1\"
longPath=D:\test\a b\z z\
resolving "D:\test\a b\a z.txt"
longPath=D:\test\a b\a z.txt
resolving "D:\test\a b\z z"
longPath=D:\test\a b\z z\
resolving "."
longPath=D:\test\
resolving "\"
longPath=D:\
resolving "x%&BAN~1\test"
longPath=D:\test\x% & bang!\test\
resolving "x% & bang!\test"
longPath=D:\test\x% & bang!\test\
shortPath=D:\test\AB2761~1\AZCFE4~1.TXT
Calling :getLongPath with with no return variable
"D:\test\a b\a z.txt"
If you want to run the above code, then I suggest you completely delete all the test scenario code between #echo off and :getLongPath. Then you can simply call the script, passing any valid path as the first argument. The correct long path should be printed as a result.
I was amazed how difficult this was to solve using batch. I don't think it is much easier with JScript or VBS (Actually, Ansgar found a nice VBS solution). But I like Ansgar's simple PowerShell solution - so much easier.
Update
I found an obscure case where the above code fails if called from within a FOR loop, and the path happens to have the FOR variable within it. It also doesn't properly report a path with wild cards as an error, and it doesn't work with delayed expansion enabled when the path contains !.
So I created a modified version below. I'm pretty confident it should truly work in all situations, except for UNC paths and possibly not with unicode in the path. I packaged it up as an easy to call procedure, complete with built in documentation. It can be left as a stand-alone script, or incorporated into a larger script.
#echo off
:getLongPath
:::
:::getLongPath PathVar [RtnVar]
:::getLongPath /?
:::
::: Resolves the path contained in PathVar into the full long path.
::: If the path represents a folder then it will end with \
:::
::: The result is returned in variable RtnVar.
::: The result is echoed to the screen if RtnVar is not specified.
:::
::: Prints this documentation if the first argument is /?
if "%~1" equ "" (
>&2 echo ERROR: Insufficient arguments. Use getLongPath /? to get help.
exit /b 1
)
if "%~1" equ "/?" (
for /f "delims=" %%A in ('findstr "^:::" "%~f0"') do (
set "ln=%%A"
setlocal enableDelayedExpansion
echo(!ln:~3!
endlocal
)
exit /b 0
)
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
setlocal enableDelayedExpansion
for /f "eol=: delims=" %%F in ("!%~1!") do (
endlocal
set "sourcePath=%%~sF"
set "sourcePath2=%%F"
set "sourceFile=%%~nxF"
)
if not exist "%sourcePath%" (
>&2 echo ERROR: Invalid path
exit /b 1
)
set "sourcePath3=%sourcePath2:**=%"
set "sourcePath3=%sourcePath3:?=%"
if "%sourcePath3%" neq "%sourcePath2%" (
>&2 echo ERROR: Invalid path
exit /b 1
)
set "rtn="
2>nul cd "%sourcePath%" || (
cd "%sourcePath%\.."
for /f "eol=: delims=" %%F in ('dir /b /a-d "%sourceFile%"') do set "rtn=%%F"
)
:resolveFolders
for %%F in ("%cd%") do (
cd ..
set "folder=%%~nxF"
)
if defined folder for /f "delims=: tokens=1,2" %%A in ("%folder%:%rtn%") do for /f "eol=: delims=" %%F in ('dir /b /ad') do (
if /i "%%~snxF" equ "%%A" (
set "rtn=%%F\%%B"
goto :resolveFolders
)
)
set "rtn=%cd%%rtn%"
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
if not defined notDelayed (set "!=!==!") else set "!="
for %%A in ("%rtn%") do (
endlocal
endlocal
if "%~2" equ "" (echo %%~A%!%) else set "%~2=%%~A"!
)
I also adapted Ansgar's VBS into a hybrid JScript/batch script. It should provide the identical result as the pure batch script above, but the JScript is much simpler to follow.
#if (#X)==(#Y) #end /* harmless hybrid line that begins a JScrpt comment
#echo off
:getLongpath
:::
:::getLongPath PathVar [RtnVar]
:::getLongPath /?
:::
::: Resolves the path contained in PathVar into the full long path.
::: If the path represents a folder then it will end with \
:::
::: The result is returned in variable RtnVar.
::: The result is echoed to the screen if RtnVar is not specified.
:::
::: Prints this documentation if the first argument is /?
::************ Batch portion ***********
if "%~1" equ "" (
>&2 echo ERROR: Insufficient arguments. Use getLongPath /? to get help.
exit /b 1
)
if "%~1" equ "/?" (
for /f "delims=" %%A in ('findstr "^:::" "%~f0"') do (
set "ln=%%A"
setlocal enableDelayedExpansion
echo(!ln:~3!
endlocal
)
exit /b 0
)
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
set "rtn="
for /f "delims=" %%A in ('cscript //E:JScript //nologo "%~f0" %*') do set "rtn=%%A"
if not defined rtn exit /b 1
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
if not defined notDelayed (set "!=!==!") else set "!="
for %%A in ("%rtn%") do (
endlocal
endlocal
if "%~2" equ "" (echo %%~A%!%) else set "%~2=%%~A"!
)
exit /b 0
************ JScript portion ***********/
var env=WScript.CreateObject("WScript.Shell").Environment("Process");
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var app=WScript.CreateObject("Shell.Application");
var inPath=env(WScript.Arguments.Item(0));
var folder="";
var f;
if (fso.FileExists(inPath)) {
f=fso.GetFile(inPath);
}
else if (fso.FolderExists(inPath)) {
folder="\\"
f=fso.GetFolder(inPath);
if (f.IsRootFolder) {
WScript.StdOut.WriteLine(f.Path);
WScript.Quit(0);
}
}
else {
WScript.StdErr.WriteLine('ERROR: Invalid path');
WScript.Quit(1);
}
WScript.StdOut.WriteLine( app.NameSpace(f.ParentFolder.Path).ParseName(f.Name).Path + folder);
This returns the full long pathname, but depends on:
A) there not being too many files in the tree (due to time taken)
B) there is only one of the target (long) filename in the tree.
#echo off
for /f "delims=" %%a in (' dir /b "%~1" ') do set "file=%%a"
for /f "delims=~" %%a in ("%~dp1") do cd /d "%%a*"
for /f "delims=" %%a in ('dir /b /s /a-d "%file%" ') do set "var=%%a"
echo "%var%"
When called with mybat "d:\MYPROG~1\SHELLS\zBackup\REFSTO~1.BAL"
it returned this:
"d:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal"
And one unexpectedly simple solution:
echo lcd %some_path%|ftp
EDITED to show example: it isn't 100%
d:\>echo lcd C:\Files\Download\MYMUSI~1\iTunes\ALBUMA~1 |ftp
Local directory now C:\Files\Download\MYMUSI~1\iTunes\Album Artwork.
this is an ugly batch job and my code is not nice, but brut force :-)
#echo off &SETLOCAL
SET "short=P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL"
SET "shorty=%short:\= %"
FOR %%a IN (%short%) DO SET "shortname=%%~nxa"
FOR %%a IN (%shorty%) DO (
IF DEFINED flag (
CALL :doit "%%~a"
) ELSE (
SET "longpath=%%~a"
SET flag=true
SET "first=\"
)
)
ECHO "%longpath%"
goto:eof
:doit
SET "last=%~1"
IF "%last%" neq "%shortname%" (SET "isDir=/ad") ELSE SET "isDir=/a-d"
FOR /f "delims=" %%b IN ('dir %isdir% %longpath%%first%^|findstr /ri "\<%last%\>"') DO SET "X0=%%b"
FOR /f "delims=" %%b IN ('dir %isdir% /x %longpath%%first%^|findstr /ri "\<%last%\>"') DO SET "X1=%%b"
REM for European time format
IF "%X0: =%"=="%X1: =%" (SET /a token=3) ELSE SET /a token=4
REM for "AM/PM" time format
IF "%X0: =%"=="%X1: =%" (SET /a token=4) ELSE SET /a token=5
FOR /f "tokens=%token%*" %%b IN ('dir %isdir% /x %longpath%%first%^|findstr /ri "\<%last%\>"') DO SET "longname=%%~c"
SET "longpath=%longpath%\%longname%"
SET "first="
goto:eof
Please set your time format in the doit function (delete as applicable format).This might maybe fail with special characters in path or file names like !%=&^.
#echo off
setlocal
rem this need to be a short name to avoid collisions with dir command bellow
cd C:\BALBAL~1\BLBALB~1\
set "curr_dir=%cd%"
set "full_path="
:repeat
for /f "delims=" %%f in ('for %%d in ^(.^) do #dir /a:d /n /b "..\*%%~snd"') do (
set "full_path=%%f\%full_path%"
)
cd ..
if ":\" NEQ "%cd:~-2%" (
goto :repeat
) else (
set "full_path=%cd%%full_path%"
)
echo --%full_path%--
cd %curr_dir%
endlocal
The path is hardcoded at the beginning but you can change it or parameterizied it.As you can easy get the full name of a file here is only a solution for directories.
EDIT
now works for file and directory and a parameter can be passed:
#echo off
rem ---------------------------------------------
rem ---------------------- TESTS ----------------
rem ----------------------------------------------
md "c:\test\blablablablabl\bla bla bla\no no no no no no\yes yes yes" >nul 2>&1
md "c:\test\1 b1\1\" >nul 2>&1
for %%t in ("c:\test\blablablablabl\bla bla bla\no no no no no no\yes yes yes") do set t_dir=%%~st
for %%t in ("c:\test\1 b1\1\") do set t_dir2=%%~st
echo a>"%t_dir2%a"
echo a>"%t_dir2%a a.txt"
echo testing "%t_dir%\\"
call :get_full_name "%t_dir%\\"
echo(
echo testing "%t_dir2%a"
call :get_full_name "%t_dir2%a"
echo(
echo testing "%t_dir2%a a.txt" with return variable
call :get_full_name "%t_dir2%a a.txt" test_var
echo return variable : -- %test_var% --
goto :eof
rem -------------------------------------
:get_full_name [%1 - short path to a file or directory ; %2 - if set stores the result in variable with that name]
setlocal
if not exist "%~1" ( echo file/dir does not exist & exit /b 2 )
set "curr_dir=%cd%"
for /f "delims=" %%n in ('dir /b /n "%~dps1\%~snx1"') do set "name=%%n"
cd "%~dps1"
set "full_path="
:repeat
for /f "delims=" %%f in ('for %%d in ^(.^) do #dir /a:d /n /b "..\*%%~snd"') do (
set "full_path=%%~f\%full_path%"
)
cd ..
if ":\" NEQ "%cd:~-2%" (
goto :repeat
) else (
set "full_path=%cd%%full_path%"
)
echo %full_path%%name%
cd %curr_dir%
endlocal & if "%~2" NEQ "" set "%~2=%full_path%%name%"
and the test output:
testing "c:\test\BLABLA~1\BLABLA~1\NONONO~1\YESYES~1\\"
c:\test\blablablablabl\bla bla bla\no no no no no no\yes yes yes\
testing "c:\test\1B1~1\1\a"
c:\test\1 b1\1\a
testing "c:\test\1B1~1\1\a a.txt" with return variable
c:\test\1 b1\1\a a.txt
return variable : -- c:\test\1 b1\1\a a.txt --
And one attempt with WMIC and Win32_Directory.Probably is slower than using cd and dir , but the current directory is not changed:
#echo off
:get_full_name [%1 - short path to a file or directory ; %2 - if set stores the result in variable with that name]
setlocal
if not exist "%~1" ( echo file/dir does not exist & exit /b 2 )
for /f "delims=" %%n in ('dir /b /n "%~dps1\*%~snx1"') do set "name=%%n"
set "short_path=%~dps1"
set "short_path=%short_path:~0,-1%"
set "drive=%short_path:~0,2%"
set "full_name="
:repeat
set "short_path=%short_path:\=\\%"
set "short_path=%short_path:'=\'%"
FOR /F "usebackq skip=2 delims=" %%P in (`WMIC path win32_directory where name^='%short_path%' get Path^,FileName /Format:Textvaluelist.xsl`) do for /f "delims=" %%C in ("%%P") do (
set "_%%C"
)
set "_Path=%_Path:~0,-1%"
set full_name=%_FileName%\%full_name%
if "%_Path%" NEQ "" (
set "short_path=%drive%%_Path%"
goto :repeat
) else (
set full_name=%drive%\%_FileName%\%full_name%
)
echo %full_name%%name%
endlocal if "%~2" NEQ "" set "%~2=%full_path%%name%"
Not heavy tested yet....
Here is a batch script based on the answer by npocmaka, using the ftp command (together with its sub-command lcd). There you can see that only the last element of a path is expanded to the long name. My idea is now to apply the lcd sub-command for every element of the path individually, so we will get the full names of all elements in the final output.
This script works for directories only. It does not work for files, neither does it work for UNC paths.
So here we go:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ARGS=%*"
set FTP_CMD=lcd
set "TEMP_FILE=%TEMP%\%~n0_%RANDOM%.tmp"
setlocal EnableDelayedExpansion
for %%A in (!ARGS!) do (
endlocal
set "ARG=%%~fA" & set "SEP=\"
setlocal EnableDelayedExpansion
> "%TEMP_FILE%" (
for %%D in ("!ARG:\=" "!") do (
endlocal
if not "%%~D"=="" (
set "ITEM=%%~D"
setlocal EnableDelayedExpansion
echo(%FTP_CMD% "!ITEM!!SEP!"
endlocal
set "SEP="
)
setlocal EnableDelayedExpansion
)
)
set "PREFIX="
for /F "delims=" %%L in ('^< "%TEMP_FILE%" ftp') do (
endlocal
if not defined PREFIX set "PREFIX=%%L"
set "LONG_PATH=%%L"
setlocal EnableDelayedExpansion
)
set "PREFIX=!PREFIX::\.=!" & set "PREFIX=!PREFIX:~,-1!"
for /F "delims=" %%E in ("!PREFIX!") do (
set "LONG_PATH=!LONG_PATH:*%%E=!"
set "LONG_PATH=!LONG_PATH:~,-1!"
)
echo(!LONG_PATH!
)
endlocal
del /Q "%TEMP_FILE%"
endlocal
exit /B
Basically there is a for %%D loop that iterates through all elements of the given path (after it has been expanded to its full path by the outer-most for %%A loop). Each element is enclosed within "" and preceded with lcd (the sub-command of the ftp command to change the local working directory). For the first path element that constitutes a drive, a trailing \ is appended to refer to its root directory. Each of these built path strings is written to a temporary file.
Next the temporary file is redirected into the ftp command, so it changes its local working directory path element by path element. The output of ftp is captured by a for /F %%L loop. Actually the last line of the output is of interest only as this contains the full long path. However, the first line is also stored, where the root directory of the applicable drive is used. This is just needed to easily extract the prefix of the output lines in order to remove it from the output line containing the full path (the ftp command outputs something like Local directory now D:\. on English systems, but I want the script to be language-independent). Finally the said prefix is removed from the full long path and the result is returned on the console.
Here is an improved approach that can also handle paths of files, by handling the last path element in such a case separately by the sub-routine :LAST_ITEM, which does not rely on ftp but on the fact that for loops expand tthe last path element to long paths when wildcards are given:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ARGS=%*"
set FTP_CMD=lcd
set "TEMP_FILE=%TEMP%\%~n0_%RANDOM%.tmp"
setlocal EnableDelayedExpansion
for %%A in (!ARGS!) do (
endlocal
set "ARG=%%~fA" & set "SEP=\" & set "ITEM="
if exist "%%~fA" (
if exist "%%~fA\" (set "FLAG=") else set "FLAG=#"
setlocal EnableDelayedExpansion
> "%TEMP_FILE%" (
for %%D in ("!ARG:\=" "!") do (
endlocal
if not "%%~D"=="" (
set "ITEM=%%~D"
setlocal EnableDelayedExpansion
echo(!FTP_CMD! "!ITEM!!SEP!"
endlocal
set "SEP="
) else set "ITEM="
setlocal EnableDelayedExpansion
)
)
set "PREFIX="
for /F "delims=" %%L in ('^< "%TEMP_FILE%" 2^> nul ftp') do (
endlocal
if not defined PREFIX set "PREFIX=%%L"
set "LONG_PATH=%%L"
setlocal EnableDelayedExpansion
)
set "PREFIX=!PREFIX::\.=!" & set "PREFIX=!PREFIX:~,-1!"
for /F "delims=" %%E in ("!PREFIX!") do (
set "LONG_PATH=!LONG_PATH:*%%E=!"
set "LONG_PATH=!LONG_PATH:~,-1!"
)
if not "!LONG_PATH:~-2!"==":\" set "LONG_PATH=!LONG_PATH!\"
for /F "tokens=1,2 delims=|" %%S in ("!LONG_PATH!|!ITEM!") do (
endlocal
set "LONG_PATH=%%S" & set "ITEM=%%T"
if defined FLAG call :LAST_ITEM ITEM LONG_PATH
setlocal EnableDelayedExpansion
)
if defined FLAG (echo(!LONG_PATH!!ITEM!) else echo(!LONG_PATH!
) else setlocal EnableDelayedExpansion
)
endlocal
del /Q "%TEMP_FILE%"
endlocal
exit /B
:LAST_ITEM var_last_item var_long_path
setlocal EnableDelayedExpansion
for %%I in ("!%~2!!%~1!*") do (
endlocal
set "LONG=%%~nxI" & set "SHORT=%%~snxI"
setlocal EnableDelayedExpansion
if /I "!LONG!"=="!%~1!" (set "%~1=!LONG!"
) else if /I "!SHORT!"=="!%~1!" set "%~1=!LONG!"
)
for /F "delims=" %%T in ("!%~1!") do (
endlocal
set "%~1=%%T"
setlocal EnableDelayedExpansion
)
endlocal
exit /B
My solution:
set shortname=P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL
for /F %f in ('dir /b /s %shortname%') do where /R %~dpf %~nf%~xf
if you use it in a batch file:
for /F %%f in ('dir /b /s %shortname%') do where /R %%~dpf %%~nf%%~xf
Alright, here is a script I began some time ago, relying on the fact that dir /B returns long file or directory names when a wildcard is used. This is a recursive approach that walks up the directory hierarchy of the path given as command line argument and resolves each element. Note that it has problems with paths containing % and/or ^ due to the usage of call:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set ARGS=%*
set "COLL="
setlocal EnableDelayedExpansion
for %%A in (!ARGS!) do (
endlocal
set "ARG=%%~fA"
if exist "%%~fA" (
call :PROC_ITEM COLL "%%~fA" || set "COLL="
)
setlocal EnableDelayedExpansion
)
if defined COLL (echo(!COLL!) else exit /B 1
endlocal
endlocal
exit /B
:PROC_ITEM rtn_built_path val_source_path
setlocal DisableDelayedExpansion
set "FND="
if "%~pnx2"=="\" (
set "COLL=%~d2"
) else (
cd /D "%~dp2." & rem (this must be set before `for /F` in order for `%%~snxJ` to refer to it!)
for /F "delims= eol=|" %%J in ('dir /B /A "%~f2?"') do (
if /I "%%J"=="%~nx2" (
set "FND=\%%J" & rem (this assignment should be executed for long names)
) else (
if /I "%%~snxJ"=="%~nx2" set "FND=\%%J" & rem (and this for short ones)
)
)
if defined FND (
call :PROC_ITEM COLL "%~dp2."
) else (
exit /B 1 & rem (this intercept exceptions and should usually not happen)
)
)
endlocal & set "%~1=%COLL%%FND%"
exit /B

Resources