Batch get relative path from absolute path - windows

How can I convert an absolute path to a relative path in batch? I have an absolute path to a directory A and a reference directory B, and I need the path to A relative to B. As example, the following batch script should print ..\other\somedir\.
setlocal enabledelayedexpansion
set referencePath=C:\Users\xyz\project\
set absolutePath=C:\Users\xyz\other\somedir\
set relativePath=...
echo %relativePath%
I tried relativePath=!absolutePath:%referencePath%=!, but this yields the absolute path C:\Users\xyz\other\somedir\.
I need something similar to the python function os.path.relpath:
>>> os.path.relpath("C:\\Users\\xyz\\other\\somedir", "C:\\Users\\xyz\\project\\")
"..\\other\\somedir"
I need this because I have a batch file with command line arguments similar to the above file names. This batch file creates another batch file startup.bat which sets some environment variables and starts an application. The startup.bat may be called over network, so I have to use relative paths. With absolute paths, the environment variables would point to the files on the wrong machine.

Here is a quick'n'dirty hack:
#echo off
setlocal enabledelayedexpansion
set referencePath=C:\Users\xyz\project\
set absolutePath=C:\Users\xyz\other\somedir\
set relativePath=
:LOOP
for /F "tokens=1 delims=\" %%a in ("%referencePath%") do (set ref=%%a)
for /F "tokens=1 delims=\" %%a in ("%absolutePath%") do (set rel=%%a)
if /i !ref!==!rel! (
set referencePath=!referencePath:%ref%\=!
set absolutePath=!absolutePath:%rel%\=!
goto LOOP
)
:RELLOOP
for /F "tokens=1 delims=\" %%a in ("%absolutePath%") do (
set absolutePath=!absolutePath:%%a\=!
set relativePath=!relativePath!..\
)
if not "%absolutePath%"=="" goto RELLOOP
set complRelPath=%relativePath%%referencePath%
echo !complRelPath!
This won't give you propper output if the folders are on different drives so you'll have to handle this special case yourself.
EDIT (comment): Well, this can't be that hard that you couldn't figure it out yourself. If / and \ are mixed (which is a bad idea - we are on Windows! Windows means \ in paths, UNIX etc. means / in paths) you should replace / by :
SET referencePath=%referencePath:/=\%
SET absolutePath=%absolutePath:/=\%
If the paths are equal, you have nothing to do so:
IF %referencePath%==%absolutePath% (
SET complRelPath=.\
GOTO WHATEVER
)

#ECHO OFF
setlocal enabledelayedexpansion
set referencePath=C:\Users\xyz\project\
set absolutePath=C:\Users\xyz\other\somedir\
FOR %%a IN ("%absolutepath%") DO FOR %%r IN ("%referencepath%.") DO (
SET "abspath=%%~pa"
SET "relativepath=!abspath:%%~pr=..\!"
)
echo %relativePath%
GOTO :EOF
It would be of assistance if you were to tell us what your desired output is. Telling us what the output of your current code is, and that implicitly that's not what you expect, and then how to obtain something using some other platform is not particularly helpful.
The problem is that you are attempting to replace a string containing a colon within a string contining a colon. cmd` gets confused as it doesn't know which colon of the three is which.
This solution is resticted, since it assumes that the part of the path to be removed is exactly the parent directory of referencepath. In the absence of more information, it's as far as I'm prepared to guess...

…and an example which leverages PowerShell:
#Echo Off
Set "referencePath=C:\Users\xyz\project"
Set "absolutePath=C:\Users\xyz\other\somedir"
Set "relativePath="
Set "_="
If /I Not "%CD%"=="%referencePath%" (Set "_=T"
PushD "%referencePath%" 2>Nul || Exit /B)
For /F "Delims=" %%A In ('
PowerShell -C "Resolve-Path -LiteralPath '%absolutePath%' -Relative"
') Do Set "relativePath=%%A"
If Defined _ PopD
If Defined relativePath Echo %relativePath%
Pause
This obviously only works with actual existing paths

Well, usually I strongly recommend not to do string manipulation on file or directory paths, because it is quite prone to failures. But for a task like this, which does not rely on existing paths, there appears no way around.
Anyway, for doing so in a reliable fashion, the following issues must be considered:
Windows uses case-insensitive paths, so do all path comparisons in such manner as well!
Avoid sub-string substitution, because it is troublesome with =-signs and a few other characters!
Ensure to properly resolve the provided paths, using the ~f-modifier of for-loop meta-variables! This ensures that the input paths are really absolute, they do not contain doubled separators (\\), and there are no sequences with . and .. (like abc\..\.\def, which is equivalent to def), that make comparison difficult.
Regard that paths may be provided in an ugly way, like with trailing \ or \., bad quotation (like abc\"def ghi"\jkl), or using wrong path separators (/ instead of \, which is the Windows standard).
Alright, so let us turn to a script that I wrote for deriving the common path and the relative path between two absolute paths, regarding all of the said items (see the rem comments):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "referencePath=C:\Users\"xyz"\dummy\..\.\project"
set "absolutePath=C:\Users\"xyz"\other\\somedir\"
rem /* At first resolve input paths, including correction of bad quotes and wrong separators,
rem and avoidance of trailing separators (`\`) and also other unwanted suffixes (`\.`): */
set "referencePath=%referencePath:"=%"
set "absolutePath=%absolutePath:"=%"
for %%P in ("%referencePath:/=\%") do for %%Q in ("%%~fP.") do set "referencePath=%%~fQ"
for %%P in ("%absolutePath:/=\%") do for %%Q in ("%%~fP.") do set "absolutePath=%%~fQ"
rem // Initially clean up (pseudo-)array variables:
for /F "delims==" %%V in ('2^> nul ^(set "$ref[" ^& set "$abs["^)') do set "%%V="
rem // Split paths into their elements and store them in arrays:
set /A "#ref=0" & for %%J in ("%referencePath:\=" "%") do if not "%%~J"=="" (
set /A "#ref+=1" & setlocal EnableDelayedExpansion
for %%I in (!#ref!) do endlocal & set "$ref[%%I]=%%~J"
)
set /A "#abs=0" & for %%J in ("%absolutePath:\=" "%") do if not "%%~J"=="" (
set /A "#abs+=1" & setlocal EnableDelayedExpansion
for %%I in (!#abs!) do endlocal & set "$abs[%%I]=%%~J"
)
rem /* Determine the common root path by comparing and rejoining the array elements;
rem also build the relative path herein: */
set "commonPath=\" & set "relativePath=." & set "flag=#" & set /A "#cmn=#ref+1"
for /L %%I in (1,1,%#abs%) do (
if defined flag (
set "flag=" & if defined $ref[%%I] (
setlocal EnableDelayedExpansion
if /I "!$abs[%%I]!"=="!$ref[%%I]!" for %%J in ("!commonPath!\!$ref[%%I]!") do (
endlocal & set "commonPath=%%~J" & set "flag=#" & set /A "#cmn=%%I+1"
)
)
)
if not defined flag (
setlocal EnableDelayedExpansion & for %%J in ("!relativePath!\!$abs[%%I]!") do (
endlocal & set "relativePath=%%~J"
)
)
)
rem // Complete the relative path by preceding enough level-up (`..`) items:
for /L %%I in (%#cmn%,1,%#ref%) do (
setlocal EnableDelayedExpansion & for %%J in (".\.!relativePath!") do (
endlocal & set "relativePath=%%~J"
)
)
set "relativePath=%relativePath:*\=%" & if "%commonPath:~-1%"==":" set "commonPath=%commonPath%\"
set "commonPath=%commonPath:~2%" & if not defined commonPath set "relativePath=%absolutePath%"
rem // Eventually return results:
set "referencePath"
set "absolutePath"
set "commonPath" 2> nul || echo commonPath=
set "relativePath"
endlocal
exit /B
Note that this approach does not support UNC paths.

Though I understand exactly what you want to do, I do not like this method.
Instead, why not use your environment path, where we will search for the relevant file in path.
#echo off
for %%g in ("bin\startup-batch.cmd") do #set "pathTobat=%%~$PATH:g"
echo "%pathTobat%"

The solution of #MichaelS is good, but has problems:
If subdirectories have same or similar names as their parents, it doesn't work.
setlocal without endlocal
Same-path condition not in script
No command-line-interface (CLI)
Improved version follows: (save as setComRelPath.bat) (you need strlen.cmd from https://ss64.com/nt/syntax-strlen.html)
#echo off
rem Usage: call setCompRelPath.bat C:\A\B C:\i\am\here
rem echo "%compRelPath%"
rem
rem Alternernatively,
rem set position_target=D:\44_Projekte\imagine\
rem set position_now=%CD%\
rem call setCompRelPath.bat
rem echo "%compRelPath%"
if not [%1]==[] set position_target=%1
if not [%2]==[] set position_now=%2
set __absolutePath=%position_now%
set __referencePath=%position_target%
if %__referencePath%==%__absolutePath% (
set complRelPath=.\
exit /b
)
rem echo __referencePath=%__referencePath%
rem echo __absolutePath=%__absolutePath%
set relativePath=
:LOOP
for /F "tokens=1 delims=\" %%a in ("%__referencePath%") do (set ref=%%a)
for /F "tokens=1 delims=\" %%a in ("%__absolutePath%") do (set rel=%%a)
if /i not %ref%==%rel% goto RELLOOP
call strlen.cmd "x%ref%" _strlen
call set __referencePath=%%__referencePath:~%_strlen%%%
call set __absolutePath=%%__absolutePath:~%_strlen%%%
rem echo abs^> %__absolutePath%
goto LOOP
:RELLOOP
for /F "tokens=1 delims=\" %%a in ("%__absolutePath%") do call :SUB_relpath %%a
if not "%__absolutePath%"=="" goto RELLOOP
goto FIN
:SUB_relpath
set ARG=%1
call strlen.cmd "x%ARG%" _strlen
rem echo abs: %__absolutePath% // ARG=%ARG% // rel: %relativePath%
call set __absolutePath=%%__absolutePath:~%_strlen%%%
set relativePath=%relativePath%..\
exit /b
:FIN
set compRelPath=%relativePath%%__referencePath%
If you want to see it functioning, uncomment the echo lines

Related

Batch: Create folders from filename (substring)

i have loads of files which i want to organize differently. The batch script should create folders with the substring on the left side of the date in the filename.
Files are now named like this:
This_is_my_file_21.01.29_22-00_abc_115.avi
This_is_my_file_20.09.29_21-10_abc_15.avi
This_is_another_file_21.01.29_22-00_abc_55.avi
Pattern:
<Name with unknown number of underscores>_<YY.MM.DD>_<hh-mm>_<string with unknown length>_<number n from 1-999>.avi
Folders should be named like this:
This_is_my_file <- two files will go into this directory
This_is_another_file <- only one file.
The Problem is, how do I get the correct substring for my folder name?
This is what I have so far:
#echo off
setlocal
set "basename=."
for /F "tokens=1* delims=." %%a in ('dir *.avi /B /A-D ^| sort /R') do (
set "filename=%%a"
setlocal EnableDelayedExpansion
for /F "delims=" %%c in ("!basename!") do if "!filename:%%c=!" equ "!filename!" (
set "basename=!filename!"
md "!basename:~0,-23!"
)
move "!filename!.%%b" "!basename:~0,-23!"
for /F "delims=" %%c in ("!basename!") do (
endlocal
set "basename=%%c
)
)
#ECHO OFF
SETLOCAL
rem The following settings for the source directory, destination directory, target directory,
rem batch directory, filenames, output filename and temporary filename [if shown] are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files\t w o"
FOR /f "delims=" %%b IN ('dir /b /a-d "%sourcedir%\*.avi" ' ) DO (
SETLOCAL ENABLEDELAYEDEXPANSION
CALL :countus "%%b"
IF DEFINED subdir (
MD "!subdir!" 2>NUL
ECHO MOVE "%sourcedir%\%%b" "%sourcedir%\!subdir!\"
) ELSE (
ECHO Failed pattern check %%b
)
ENDLOCAL
)
GOTO :EOF
:: count number of underscores before pattern YY.MM.DD_hh-mm
:countus
SET /a ucount=0
:countusloop
SET /a ucount+=1
SET /a scount=ucount+1
FOR /f "tokens=%ucount%,%scount%delims=_" %%q IN ("%~1") DO SET "str1=%%q"&SET "str2=%%r"
IF NOT DEFINED str2 SET "subdir="&GOTO :EOF
:: is %str1%.%str2:-=.%. of form np.np.np.np.np where np is a number-pair?
SET "candidate=%str1%.%str2:-=.%."
FOR /L %%c IN (10,1,99) DO IF DEFINED candidate SET "candidate=!candidate:%%c.=!"&IF NOT DEFINED candidate GOTO success
FOR /L %%c IN (0,1,9) DO IF DEFINED candidate SET "candidate=!candidate:0%%c.=!"&IF NOT DEFINED candidate GOTO success
GOTO countusloop
:success
SET "subdir=%~1"
FOR /f "delims=:" %%e IN ("!subdir:_%str1%_%str2%=:!") DO SET "subdir=%%e"
GOTO :eof
The "move" command is merely echoed for verification. Remove the echo from echo move to actually move the files.
This possible solution uses the fact that your filenames have a known number of underscores if you work backwards. All I do is replace those underscores with backslashes, which obviously cannot already be contained in the filename. I can then use the relative paths to step up the filename, as if it were a directory tree, until all I have left is the part ahead of the date sequence, which I then replace the backslashes with underscores again. I use the result of that with robocopy, which has a move option, and will create the destination directory automatically, if it does not already exist. At the outset, I perform the directory search, in the same directory as the batch-file, using where.exe, (you can change that, on line three, from "%~dp0." to ".", if you want to use the current directory instead, or "any other path" as necessary). where.exe not only treats the ? wildcard as exactly one character, (unlike the dir command which is zero or one), but also ignores 8.3 naming. It therefore treats the .avi extension exactly as written, (and not 'beginning with' .avi, which dir, or a standard for loop, would).
Anyhow, feel free to give it a try:
#Echo Off & SetLocal EnableExtensions DisableDelayedExpansion
Set "}=" & For /F Delims^= %%G In ('(Set PATHEXT^=^) ^& %__AppDir__%where.exe
"%~dp0.":"?*_??.??.??_??-??_?*.avi" 2^> NUL') Do (Set "}=%%~nG"
SetLocal EnableDelayedExpansion & For %%H In ("\!}:_=\!") Do (
EndLocal & For %%I In ("%%~pH..\..") Do (Set "}=%%~pI"
SetLocal EnableDelayedExpansion & Set "}=!}:~1,-1!"
For %%J In ("!}:\=_!") Do (EndLocal & %__AppDir__%robocopy.exe ^
"%%~dpG." "%%~dpG%%~J" "%%~nxG" /Mov 1> NUL))))
If you want even further robustness, and do not wish to use a more suitable scripting technology, the following, extremely complex looking, version, is the same code, except that it uses findstr to validate the date and time sequence. It filters those avi files containing the following pattern, _yy.MM.dd_hh-mm_ in the avi filenames, using all dates from the beginning of 1970 up until the end of 2021:
#Echo Off & SetLocal EnableExtensions DisableDelayedExpansion
Set "}=" & For /F Delims^= %%G In ('(Set PATHEXT^=^) ^& %__AppDir__%where.exe
"%~dp0.":"?*_??.??.??_??-??_?*.avi" 2^> NUL ^| %__AppDir__%findstr.exe
/RC:"_[789][0123456789].0[123456789].0[123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_[789][0123456789].0[123456789].0[123456789]_2[0123]-[012345][0123456789]_"
/C:"_[789][0123456789].0[123456789].[12][0123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_[789][0123456789].0[123456789].[12][0123456789]_2[0123]-[012345][0123456789]_"
/C:"_[789][0123456789].0[123456789].3[01]_[01][0123456789]-[012345][0123456789]_"
/C:"_[789][0123456789].0[123456789].3[01]_2[0123]-[012345][0123456789]_"
/C:"_[789][0123456789].1[012].0[123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_[789][0123456789].1[012].0[123456789]_2[0123]-[012345][0123456789]_"
/C:"_[789][0123456789].1[012].[12][0123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_[789][0123456789].1[012].[12][0123456789]_2[0123]-[012345][0123456789]_"
/C:"_[789][0123456789].1[012].3[01]_[01][0123456789]-[012345][0123456789]_"
/C:"_[789][0123456789].1[012].3[01]_2[0123]-[012345][0123456789]_"
/C:"_[01][0123456789].0[123456789].0[123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_[01][0123456789].0[123456789].0[123456789]_2[0123]-[012345][0123456789]_"
/C:"_[01][0123456789].0[123456789].[12][0123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_[01][0123456789].0[123456789].[12][0123456789]_2[0123]-[012345][0123456789]_"
/C:"_[01][0123456789].0[123456789].3[01]_[01][0123456789]-[012345][0123456789]_"
/C:"_[01][0123456789].0[123456789].3[01]_2[0123]-[012345][0123456789]_"
/C:"_[01][0123456789].1[012].0[123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_[01][0123456789].1[012].0[123456789]_2[0123]-[012345][0123456789]_"
/C:"_[01][0123456789].1[012].[12][0123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_[01][0123456789].1[012].[12][0123456789]_2[0123]-[012345][0123456789]_"
/C:"_[01][0123456789].1[012].3[01]_[01][0123456789]-[012345][0123456789]_"
/C:"_[01][0123456789].1[012].3[01]_2[0123]-[012345][0123456789]_"
/C:"_2[01].0[123456789].0[123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_2[01].0[123456789].0[123456789]_2[0123]-[012345][0123456789]_"
/C:"_2[01].0[123456789].[12][0123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_2[01].0[123456789].[12][0123456789]_2[0123]-[012345][0123456789]_"
/C:"_2[01].0[123456789].3[01]_[01][0123456789]-[012345][0123456789]_"
/C:"_2[01].0[123456789].3[01]_2[0123]-[012345][0123456789]_"
/C:"_2[01].1[012].0[123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_2[01].1[012].0[123456789]_2[0123]-[012345][0123456789]_"
/C:"_2[01].1[012].[12][0123456789]_[01][0123456789]-[012345][0123456789]_"
/C:"_2[01].1[012].[12][0123456789]_2[0123]-[012345][0123456789]_"
/C:"_2[01].1[012].3[01]_[01][0123456789]-[012345][0123456789]_"
/C:"_2[01].1[012].3[01]_2[0123]-[012345][0123456789]_"') Do (Set "}=%%~nG"
SetLocal EnableDelayedExpansion & For %%H In ("\!}:_=\!") Do (
EndLocal & For %%I In ("%%~pH..\..") Do (Set "}=%%~pI"
SetLocal EnableDelayedExpansion & Set "}=!}:~1,-1!"
For %%J In ("!}:\=_!") Do (EndLocal & %__AppDir__%robocopy.exe ^
"%%~dpG." "%%~dpG%%~J" "%%~nxG" /Mov 1> NUL))))

Second latest folder in a Directory

I want a batch file which will find out which is the second latest folder created/modified in a directory.
I found this article but no matter how much i tried i could not understand how it works
#echo off
set "root_dir=c:\somewhere"
pushd "%root_dir%"
set "bl1="
set "bl2="
setlocal enableDelayedExpansion
for /f "tokens=* delims=" %%# in ('dir /b /a:-d /o:d') do (
set "bl2=!bl1!"
set "bl1=%%#"
)
echo %bl2%
endlocal
If i use it as it is then i can get the second latest folder but this script is supposedly able to get which ever latest folder you need , be it 1st or nth.
Could someone please tell me what modifications need to be done to the script to accomplish that. Also how exactly this script works
In your approach, the latest folder is already available in variable bl1; add echo %bl1% at the end before endlocal to display it. Retrieving the nth folder is simply not possible in a flexible way with that script as you would need to define another variable (say bl3, bl4,..., bln) within the loop.
However, you could reverse the sort order of the output of the dir command by changing the /O option, so it returns the latest (most recent) item first. Then let an index number count the iterations of the loop, and if that index equals the predefined number n, store the currently iterated item:
#echo off
setlocal EnableDelayedExpansion
rem // Define N here to get Nth-latest folder:
set /A LATEST=2
set /A INDEX=0
for /F "eol=| delims=" %%# in ('dir /B /A:D /O:-D "C:\somewhere"') do (
set /A INDEX+=1
if !INDEX! EQU !LATEST! (
set "ITEM=%%#"
)
)
if defined ITEM echo %LATEST%th-latest folder: %ITEM%
endlocal
exit /B
Update
Here is a modified script with the following improvements:
Exclamation marks ! in folder names are no longer lost due to toggling delayed expansion;
the target directory can be provided as the first command line argument; if omitted, the current directory is used;
the number n can be given as the second command line argument; if omitted, the user is prompted for it (this addresses elzooilogico's comment); n defaults to 1 for empty input;
the display output is improved to avoid something weird like 1th-latest, 2th-latest and 3th-latest; instead, The latest, 2nd-latest and 3rd-latest is returned, respectively;
So this is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem /* define location path and folder pattern as 1st command line argument;
rem /* Define number N as 2nd command line argument to get Nth-latest folder. */
set "LATEST=%~2"
set /A LATEST+=0
if %LATEST% LEQ 0 (set /P LATEST="Enter N [1]: " & set /A LATEST+=0)
if %LATEST% LEQ 0 set /A LATEST=1
set /A INDEX=0
for /F "eol=| delims=" %%# in ('dir /B /A:D /O:-D "%~1"') do (
set /A INDEX+=1
setlocal EnableDelayedExpansion
if !INDEX! EQU !LATEST! (
endlocal
set "ITEM=%%#"
goto :SKIP & rem // break loop after having retrieved Nth-latest folder;
) else endlocal
)
:SKIP
setlocal EnableDelayedExpansion
if defined ITEM (
if %LATEST% EQU 1 (echo The latest file: !ITEM!) else (
if %LATEST% EQU 2 (echo 2nd-latest file: !ITEM!) else (
if %LATEST% EQU 3 (echo 3rd-latest file: !ITEM!) else (
echo %LATEST%th-latest file: !ITEM!)))
)
endlocal
endlocal
exit /B
To achieve a similar result as with the simple script on top of this answer, you need to call this script by the following command line, supposing it has been saved as Nth-latest.bat:
Nth-latest.bat "C:\somewhere" 2

Windows batch - nested variables and delayed expansion

newbie here who would appreciate some help. I'm trying to generate to generate random file names (i.e. for each file in the source directory, a different random alphanumeric figure should be generated)
The function that generates the random alphanumeric name works but when the value is returned to the main routine, it remains the same across all the files. Not sure where the problem is but I suspect it has something to do with delayed expansion.
#Echo Off
Setlocal EnableDelayedExpansion
dir "D:/Source" /b > List.txt
FOR /F %%i in (List.txt) DO (
CALL:Alpha Numb
echo !Numb!
md D:\Output\%%~ni
rar a -v50M -hpabc123 -m0 -ep "D:\Output\%%~ni\!Numb!.rar" "D:\Source\%%i"
)
goto:eof
:Alpha
Setlocal EnableDelayedExpansion
Set _RNDLength=40
Set _Alphanumeric=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
Set _Str=%_Alphanumeric%987654321
:_LenLoop
IF NOT "%_Str:~18%"=="" SET _Str=%_Str:~9%& SET /A _Len+=9& GOTO :_LenLoop
SET _tmp=%_Str:~9,1%
SET /A _Len=_Len+_tmp
Set _count=0
SET _RndAlphaNum=
:_loop
Set /a _count+=1
SET _RND=%Random%
Set /A _RND=_RND%%%_Len%
SET _RndAlphaNum=!_RndAlphaNum!!_Alphanumeric:~%_RND%,1!
If !_count! lss %_RNDLength% goto _loop
REM Echo %_RndAlphaNum%
ENDLOCAL & SET "%~1=%_RndAlphaNum%"
Exit /b

How do I specify the file directory for it to run in task scheduler?

I added this batch file into the task scheduler but it couldn't be load and it keeps looping and most probably it is because of the file directory that I have wrote. But if I was to run this batch file from the local directory instead, this batch file can be run. So, how do I change the file directory in the if statement to allow it to run it in the task scheduler? What path do I need to use?
#ECHO Off
SETLOCAL
:CheckForFile
if exist "MATS\NX_EXTR.txt" %AND% if exist "MTA\NX_EXTR.txt" goto FileExists
timeout 1
goto :CheckForFile
:FileExists
FOR /f "tokens=1*delims=" %%a IN (MATS\NX_EXTR.txt) DO SET "L2a=%%a"&SET "L1a="&GOTO nextstep
:nextstep
(
FOR /f "delims=" %%a IN (MTA\NX_EXTR.txt) DO (
IF DEFINED L1a (ECHO(%%a) ELSE (SET "L1a=%%a"&CALL :sumlines)
)
FOR /f "skip=1delims=" %%a IN (MATS\NX_EXTR.txt) DO (ECHO(%%a)
) >C.txt
TYPE C.txt
GOTO :EOF
:sumlines
SET /a L2a=1%L1a:~10%+1%L2a:~10%
ECHO(%L1a:~0,10%%L2a:~1%
GOTO :eof
Seems familar somehow...
if exist "MATS\NX_EXTR.txt" %AND% if exist "MTA\NX_EXTR.txt" goto FileExists
First, %AND% is not required. if exist filename if exist filename2 something parameters
will execute something (even if it's a goto) when both files exist.
This will probably work as it stands because and is undefined and thus %and% is resolved to an empty string.
In all probability, the problem is that MATS\NX_EXTR.txt is a relative pathname to a file - it's short for .\MATS\NX_EXTR.txt and hence finding the file depends on the current directory.
Your fix is to either
change to he appropriate directory within the batch by executing
cd "d:\parent\of"
where d:\parent of" is the parent directoru ofMATS\NX_EXTR.txt(ie. the **absolute filepath** isd:\parent of\MATS\NX_EXTR.txt`
OR
replace MATS\NX_EXTR.txt with the absolute filepath (ie. d:\parent of\MATS\NX_EXTR.txt)
Naturally, you'd needto repeat this for each of the filenames you use, including C.txt
In order to keep from having to maintain this batch, you could also use
set "file1=d:\parent of\MATS\NX_EXTR.txt"
at the start of the program, and then use %file1% to refer to the file. Obviously, repeat for all files - and possibly even change the symbolic name file1 to something that may carry some meaning (newdatafile) for instance)
Revision example (I've no idea of your actual absolute filenames)
#ECHO Off
SETLOCAL
set "file1=c:\wherever\MATS\NX_EXTR.txt"
set "file2=c:\wherever\MTA\NX_EXTR.txt"
set "file3=c:\wherever\C.txt"
:CheckForFile
if exist "%file1%" if exist "%file2%" goto FileExists
timeout 1
goto :CheckForFile
:FileExists
FOR /f "tokens=1*delims=" %%a IN (%file1%) DO SET "L2a=%%a"&SET "L1a="&GOTO nextstep
:nextstep
(
FOR /f "delims=" %%a IN (%file2%) DO (
IF DEFINED L1a (ECHO(%%a) ELSE (SET "L1a=%%a"&CALL :sumlines)
)
FOR /f "skip=1delims=" %%a IN (%file1%) DO (ECHO(%%a)
) >%file3%
TYPE %file3%
GOTO :EOF
:sumlines
SET /a L2a=1%L1a:~10%+1%L2a:~10%
ECHO(%L1a:~0,10%%L2a:~1%
GOTO :eof
Note that if the value assigned to %filen% contains separators such as Space then %filen% must be quoted ("%%file3%").
If this results in the format
for /f ... in ("...") ...
then you need to add the usebackq option in for /f
eg.
FOR /f "USEBACKQdelims=" %%a IN ("%file2%") DO (
(caps used for emphasis; usebackq can be in any case)
If for /f ... is applied to "a string" without the usebackq option, then the "quoted string" is parsed, not the data in "the\file whose name \is in\the\quoted string"

Batch File to list folders and allow user selection

I have searched and searched to find a solution to (what feels like) a unique problem. Many
answers here have been quite helpful and have gotten me a long way, but the last bit has me stumped.
My batch file needs to open an executable in a folder with a variable name
This folder may be in one of two directories
This is what I originally had and it works
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET DIR1="%USERPROFILE%\AppData\Local\Application Workspace\Profiles"
SET DIR2=C:\Application\SRW\Profiles
IF NOT EXIST %DIR1% (
GOTO PROFILE_2
) ELSE (
GOTO PROFILE_1
)
:PROFILE_1
for /f "delims=" %%A in ('dir %DIR1% /ad /b') do (
set foldername=%%~nxA
)
SET DIR1=%DIR1:"=%
SET DIR=%DIR1%\%foldername%\app.exe
GOTO START_APP
:PROFILE_2
for /f "delims=" %%A in ('dir %DIR2% /ad /b') do (
set foldername=%%~nxA
)
SET DIR=%DIR2%\%foldername%\app.exe
GOTO START_APP
:START_APP
START "" /b "%DIR%"
:EOF
ENDLOCAL
EXIT
Then I was thrown a curveball when I discovered that some users may have multiple profiles in the variable profile folder and they need to be able to select which one to use for that given task. Now I have a variable profile folder and either one or multiple variable profiles within that folder.
I have found code to list the folder names and display them in the command window for selection.
#echo off
cls
setlocal EnableDelayedExpansion
set /a count=0
for /d %%d in (*) do (
set /a count+=1
#echo !count!. %%d
)
setlocal DisableDelayedExpansion
set /P selection="select folder number:"
I also found this routine which allows the user to select a file from a list then it is supposed to translate that file name into a variable.
Batch Script Programming -- How to allow a user to select a file by number from a list of files in a folder?
Unfortunately, I cannot get the example in the link to work as is and I have no idea how to make it work with folder names as the folder name example and the file name example are close but not close enough for me to understand what to do. And even if it somehow does manage to work, how then can I make such a routine work within the original code posted above?
In addition, I really don't want the user to be forced to make a folder selection if there is only one folder. If only one folder exists, it should be placed into the folder name variable automatically and nothing displays to the user at all.
Is what I'm trying to do even possible at all?
Any help is most appreciated.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
::
:: Set count-of-targets and application name
::
SET /a targets=0
SET appname=app.exe
SET "dots=..."
::
:: clear all "$" variables
::
FOR /f "delims==" %%i IN ('set $ 2^>nul') DO SET "%%i="
::
:: look for app.exe in (1) userprofile... (2) \application\srw...
::
SET dir1="%USERPROFILE%\AppData\Local\Application Workspace\Profiles"
SET dir2="C:\Application\SRW\Profiles"
:: My testing - my directory names
SET dir1="c:\sourcedir"
SET dir2="C:\destdir\test dir"
FOR %%s IN (%dir1% %dir2%) DO (
FOR /f "delims=" %%i IN ('dir /s /b "%%~s\%appname%"') DO ( CALL :process "%%~dpi"
)
)
::
:: Now have TARGETS=#hits; $_n=full pathname; $n=directoryname
::
IF %targets%==1 SET mychoice=1&GOTO chosen
::
:: repeat a menu
::
:again
CLS
FOR /l %%i IN (1,1,%targets%) DO (
IF %%i lss 10 (ECHO(%%i%dots%%dots:~0,1%!$%%i!) ELSE (ECHO(%%i%dots%!$%%i!)
)
SET mychoice=
SET /p mychoice="Please choose 1..%targets% : "
IF NOT DEFINED $_%mychoice% GOTO again
:chosen
CALL SET application="%%$_%mychoice%%%%appname%"
ECHO START "" /b %application%
GOTO :EOF
::
:: Parameter is quoted-full-pathname
::
:process
SET /a targets+=1
SET $_%targets%=%~1
SET $=%~1
FOR %%n IN ("%$:~0,-1%") DO SET $%targets%=%%~nxn
GOTO :eof
From what you've said, this should work.
First steps are to set up. Set the number of targets found and the real application name and clear out any variablenames that start "$".
Next step is to specify the directories to use. I simply copied yours, then overrode those settings with settings to suit my machine - you'd need to delete those overriding lines, of course. Note that the names should be set quoted...
Next, scan from each of the directories for the application. Each time an instance is found, call :process passing the full -terminated pathname.
:process increments the count of targets found and sets the variable $_n to the full pathname passed (dequoted) It then sets $ to the dequoted-full-pathname and uses a syntax-trick to have FOR find the application's immediate directory. %$:~0,-1% is the full pathname minus the last character - the \ so the results looks like a filename. That "filename" is then assigned to $n
Once the directory-scans are finished, %targets% will contain the er, number of targets. If that's 1, then we have all we need - we can only select target number 1, so that's chosen for us.
Otherwise, clear the screen and display a number of lines - %targets% to be precise. The format isn...immediatedirname` and an extra dot is added if the number of targets is less than 10 so that a long list will line up nicely.
Then ask for a line number.
At this point, the only $_ variables that exist are $_1..$_%targets% so if $_%mychoice% exists, it must be a valid choice. If it's not defined, then repeat the question...
CALLing SET application="%%$_%mychoice%%%%appname%" first parses the command, then parses it again. After the first parse, the line becomes (if mychoice=3) SET application="%$_3%app.exe" so on the second occasion,application` is properly set to the full fulename of the application - and quoted.
There wasn't much point in my starting the app, since it doesn't exist, so I just echoed it.
Given the extended requirement:
You's probably be starting this from a shortcut. Suppose you run that shortcut minimised and insert after the SETLOCAL
SET "poweruser=%1"
Add, after
IF %targets%==1 SET mychoice=1&GOTO chosen
the line
IF NOT DEFINED poweruser START "%username% - poweruser" "%~dpnx0" poweruser&GOTO :EOF
And change the
GOTO :EOF
just prior to the label :process to
EXIT
So that if it wasn't run in poweruser mode (therefore poweruser is NOT defined) AND %targets% is NOT 1 (I'm presuming it won't be 0) Then this user hasn't been set as a poweruser in the shortcut, but does have more than one target, so the job is restarted in poweruser mode (ie. maximised.)
Note that it doesn't actually matter what the string is after the "~dpnx0" - so long as it's a string. I just used poweruser What it does is tell the batch that it's being run in a normal window, not minimised.
For powerusrs, you could if you like set the shortcut to normal window or maximised AND put a parameter in the command line after the batchname, which will marginally quicken the procedure. Leaving it without parameters and minimised would suit ALL users as it will auto-adjust if more than 1 target is found.
I have tried this. It worked for me.
I answered your question in two steps. In the first one I translated your original code into a more readable one. Here it is:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET "DIR1=%USERPROFILE%\AppData\Local\Application Workspace\Profiles"
SET DIR2=C:\Application\SRW\Profiles
IF NOT EXIST "%DIR1%" (
for /f "delims=" %%A in ('dir %DIR2% /ad /b') do (
SET DIR=%DIR2%\%%~nxA\app.exe
)
) ELSE (
for /f "delims=" %%A in ('dir %DIR1% /ad /b') do (
SET DIR=%DIR1%\%%~nxA\app.exe
)
)
START "" /b "%DIR%"
ENDLOCAL
EXIT
You should check first that previous code is equivalent to your original one.
In the second step I added the profile selection to the previous code:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET "DIR1=%USERPROFILE%\AppData\Local\Application Workspace\Profiles"
SET DIR2=C:\Application\SRW\Profiles
IF NOT EXIST "%DIR1%" (
for /f "delims=" %%A in ('dir %DIR2% /ad /b') do (
SET DIR=%DIR2%\%%~nxA\app.exe
)
) ELSE (
call :SelectProfile
)
START "" /b "%DIR%"
ENDLOCAL
EXIT
:SelectProfile
rem Get a list of folders in User Profile dir
set count=0
for /f "delims=" %%A in ('dir %DIR1% /ad /b') do (
set /A count+=1
SET DIR[!count!]=%DIR1%\%%~nxA
)
rem Initialize the selected profile
set select=1
if %count% equ 1 goto endSelection
rem Show the profiles menu
cls
echo Available profiles:
echo/
for /L %%i in (1,1,%count%) do echo %%i- !DIR[%%i]!
echo/
rem Let's the user to select the profile
:select
set select=1
set /P "select=Enter number of desired profile [first one]: "
if not defined DIR[!select!] goto select
:endSelection
SET DIR=!DIR[%select%]!\app.exe
exit /B
Please note that previous code fail if the user enter spaces in the answer. This detail may be fixed, if needed.

Resources