Batch script to install fonts not setting fext variable - batch-file

This is really driving me insane. The following is part of an 8,000 line batch script. This section of code is at line 3380 approx and not working for me, as the fext variable is not getting set.
Apologies for the long post.
Rem *********************************************************************************************************************************************
Rem * Copy and register fonts *
Rem *********************************************************************************************************************************************
:FONTS
IF EXIST "%SrcPath%\Fonts\" (
Echo Installing Custom Fonts
Echo. >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
Echo %time% === Installing Custom Fonts === >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
FOR %%a in ("%SrcPath%\Fonts\*.?tf") do call :Installfont "%%~nxa"
Echo %time% Fonts will not be available until the system has been restarted >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
Echo %time% === Finished Installing Custom Fonts === >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
)
Goto SYMLNK
:Installfont
Set font=
Set fext=
set ftype=
Set "font=%~1"
IF EXIST "%LOCALAPPDATA%\Microsoft\Windows\Fonts\%font%" echo Font "%font%" already installed. >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
IF EXIST "%WINDIR%\Fonts\%font%" echo Font "%font%" already installed. >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
IF NOT EXIST "%LOCALAPPDATA%\Microsoft\Windows\Fonts\%font%" IF NOT EXIST "%WINDIR%\Fonts\%font%" (
Set "fext=%font:~-3%"
IF "%fext%"=="" Echo null extension for font "%font%"
IF /i "%fext%"=="ttf" Set ftype=Truetype
IF /i "%fext%"=="otf" Set ftype=Opentype
Echo %time% Installing "%font%" to "%LOCALAPPDATA%\Microsoft\Windows\Fonts" >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
Echo %time% Processing font "%font%" Extension: %fext% Type: "(%ftype%)" >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
echo "%font%" "%fext%" "%ftype%"
Pause
copy "%SrcPath%\Fonts\%font%" "%LOCALAPPDATA%\Microsoft\Windows\Fonts" /y >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
REG ADD "HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Fonts" /v "%font% (%ftype%)" /d "%LOCALAPPDATA%\Microsoft\Windows\Fonts\%font%" /t REG_SZ /f >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
)
exit /b
:SYMLNK
Echo Finished
Endlocal
The srcpath and src variables are set at the top of the script...
#Echo Off
powershell -command "&{$w=(get-host).ui.rawui;$w.buffersize=#{width=132;height=2500};$w.windowsize=#{width=128;height=70};}"
setlocal
Rem What drive are we on? Save the source drive
SET SRC=%CD:~0,2%
Rem Shorten the source path
set SrcPath=%SRC%\PostInstall\%USERNAME%
If I don't clear the variables at the start of the subroutine the effect is cumulative. First time neither fext nor ftype are set. 2nd time fext is set, but ftype is not, third time both are set, so I added the sets at the start of the subroutine to clear them.
What I end up with is the registry entry name (eg). airstrike.ttf (). The value data is correct. What should be in the brackets is either truetype or opentype.
The copy command doesn't work at all. Just says 0 files copied.
Some fonts have spaces in the name such as Spirit & Ghost.ttf, which is why I've quoted everything.
I must admit I don't really have a good grasp of delayedexpansion but attempting to use it totally bricks the script.
The entire script contains a considerable amount of personal info, so sharing it is problematic. But if it's definitely needed I can go through and mask out the personal stuff, but then it's not an accurate copy of the real script.
You will see that the script writes to a log file. This is what's in the log file...
16:11:41.91 === Installing Custom Fonts ===
16:11:41.94 Installing "airstrike.ttf" to "C:\Users\Phillip\AppData\Local\Microsoft\Windows\Fonts"
16:11:41.94 Processing font "airstrike.ttf" Extension: Type: "()"
0 file(s) copied.
The operation completed successfully.
Would greatly appreciate some help with why this doesn't work.
thank you.

IF EXIST "%LOCALAPPDATA%\Microsoft\Windows\Fonts\%font%" exit /b
IF EXIST "%WINDIR%\Fonts\%font%" exit /b
Set "fext=%font:~-3%"
IF "%fext%"=="" Echo null extension for font "%font%"
IF /i "%fext%"=="ttf" Set ftype=Truetype
IF /i "%fext%"=="otf" Set ftype=Opentype
Echo %time% Installing "%font%" to "%LOCALAPPDATA%\Microsoft\Windows\Fonts" >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
Echo %time% Processing font "%font%" Extension: %fext% Type: "(%ftype%)" >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
echo "%font%" "%fext%" "%ftype%"
Pause
copy "%SrcPath%\Fonts\%font%" "%LOCALAPPDATA%\Microsoft\Windows\Fonts" /y >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
REG ADD "HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Fonts" /v "%font% (%ftype%)" /d "%LOCALAPPDATA%\Microsoft\Windows\Fonts\%font%" /t REG_SZ /f >>C:\%USERDOMAIN%.PostInstall.Log 2>&1
exit /b
The issue is that within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion (outside of the block or more usually after the initial #echo off) and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
Note therefore the use of CALL ECHO %%var%% which displays the changed value of var. CALL ECHO %%errorlevel%% displays, but sadly then RESETS errorlevel.
In this case, simply exit /b if the block in question is NOT to be executed, then execute the individual statements of the block, so they will be processed as you expect, avoiding the characteristic of blocks - which is a commonly-encountered phenomenon with thousands of mentions in SO solutions.

Related

How to properly use rename (ren) in Batch

I am trying to rename every image in a directory to add the date that each file was created, however, I keep either getting "invalid syntax" or "A duplicate file name exists, or the file cannot be found"
I am running Windows 10, and accessing the images off a flash drive (hence the short file path). I tried having all the code in one for-loop, when that didn't work I tried using batch functions, no dice. I did see someone mention on another thread to use delayed expansion, I would be up for using this if someone could give a better explanation than the /? command.
#echo off
REM batch file is placed in top of F drive, same as "images 2017+"
cd "F:\images 2017+"
FOR /R "F:\images 2017+" %%F in (*.jpg) do call :renER "%%~nF" "%%~tF"
goto :eof
:renER
cd "F:\images 2017+"
pause
echo %1
echo %2
rename %1.jpg %1_%2.jpg
pause
goto :eof
:end
For every .jpg file in "images 2017+", the date which that file was created would be stuck onto the end after a space.
thisIsMyFile.jpg made at 5-13-2017, would become thisIsMyFile 5-13-2017.jpg
Current output
EDIT:
I am CDing into the same directory as the images are, then using the passed variables to locate the correct image (The date is one of the passed variables, and shows up in the echo command).
I notice that you only want the date, not the time so you can do that as follows using your existing Call to a label, There is also no need to use FOR /R in this case so I'll use a normal for loop:
#echo off
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
CALL :RenER "%%~fA" %%~tA
)
GOTO :eof
:RenER
PAUSE
ECHO %1
ECHO %2
SET "_tmp=%~2"
SET "_tmp=%tmp:/=-"
REN "%~1" "%~n1_%_tmp%%~x1"
PAUSE
GOTO :eof
Notice how above we are dropping the Time off immediately by not wrapping it in quotes since you don't want that to be part of the file name.
You can also forgo the call to a label entirely without needing delayed expansion by using a second loop, as a matter of preference I think this is quite a bit cleaner!
#echo off
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
FOR /F "Tokens=1-3 Delims=/ " %%a IN ('echo.%%~tA') DO (
PAUSE
ECHO.%%~fA
ECHO.%%~tA
REN "%%~fA" "%%~nA_%%a-%%b-%%c%%~xA"
PAUSE
)
)
this is nice and clean and with a minor edit we can paste it directly into the CMD Prompt which is nicer still This is because we are not using DelayedExpansion, Calling a Label, or using Temp variables so by changing the %%s to %s, we can then Paste this directly into the CMD Line which is often more convenient when doing these sorts of operations:
This Multi-line will do just fine to be pasted into CMD directly:
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
FOR /F "Tokens=1-3 Delims=/ " %a IN ('echo.%~tA') DO #(
PAUSE
ECHO.%~fA
ECHO.%~tA
REN "%~fA" "%~nA_%a-%b-%c%~xA"
PAUSE
)
)
or, as a single line to paste into CMD if you prefer:
FOR %A IN ("F:\images 2017+\*.jpg") DO #( FOR /F "Tokens=1-3 Delims=/ " %a IN ('echo.%~tA') DO #( PAUSE& ECHO.%~fA& ECHO.%~tA& REN "%~fA" "%~nA_%a-%b-%c%~xA"& PAUSE ) )
no need to cd anywhere. ren takes a full path/filename for source - just the destination must be a filename only. So ... do call :renER "%%~fF" "%%~tF" is fine (no need to snip the extension and add it again later). In the subroutine reformat the time to a valid string and reassemble the destination file name:
#echo off
FOR /R "F:\images 2017+" %%F in (*.jpg) do call :renER "%%~fF" "%%~tF"
goto :eof
:renER
pause
echo %1
echo %2
set "string=%~2"
set "string=%string::=-%"
set "string=%string:/=-"
ECHO rename "%~1" "%~n1_%string%%~x1"
pause
goto :eof
:end
NOTE: I disarmed the rename command. Remove the ECHO after troubleshooting, if it works as intended.
#Stephan's answer is probably the best approach. But if you want to change directories ...
The windows shell has a working drive/volume, and on each drive/volume a current working folder. cd changes the working folder on a disk; to change the working folder on a drive (which is not the working drive) and to make that drive the working drive, you need to use cd /d, in this case cd /d "F:\images 2017+".
(A plain cd in this instance changes the working folder on F:\, but if your working folder is on C: -- as I'm guessing is the case -- it will not be changed.)
Assuming command extensions are enabled, you should also be able to use pushd and popd. pushd behaves like cd /d but also saves your previous location; popd returns you to that previous location. (And IIRC pushd will accept UNC paths.)
So at the beginning of your script, pushd "F:\images 2017+", and at the end popd.
I tend to favor pushd/popd over cd because invocations can be nested. So you can do things like
(assume working directory is C:\Users\IoCalisto):
pushd "F:\images 2017+"
(working directory is now F:\images 2017+)
pushd "Z:\images 2015-2016"
(working directory is now Z:\images 2015-2016)
popd
(working directory is now F:\images 2017+)
popd
(working directory is now C:\Users\IoCalisto)
... with this approach, your scripts will have fewer "side effects" and be more modular, or at least modularizable.

Need a little tip on batch files

I'm very new to coding and iI'm having a problem that is probably trivial, but is making me pull out my hair.
I'm using a batch script to automate mounting a VHD, executing a file inside and then pause until the user presses any key, which makes the VHD get unmounted and the script exits.
This is the main batch file:
#echo off
set fileVHD=Gord
CD /D "%~dp0"
powershell -command "Start-Process mount.cmd '%~dp0%fileVHD%.vhd' -Verb runas"
timeout /t 1
for /f %%D in ('wmic volume get DriveLetter^, Label ^| find "%fileVHD%"') do set usb=%%D
CD /D %usb%
index.html
echo "!!!!!!!!!!!!!!!!!!!!Press any key to fully close this program.!!!!!!!!!!!!!!!!!!!!!!!!!"
pause
CD /D "%~dp0"
powershell -command "Start-Process unmount.cmd '%~dp0%fileVHD%.vhd' -Verb runas"
exit
This is the mount script (Not made by me):
#echo off
setlocal enabledelayedexpansion
if "%~1"=="" (
echo Usage: %~nx0 [vhd] [letter]
exit /b 1
)
set "vhdPath=%~dpnx1"
set "driveLetter=%2"
if "!driveLetter!"=="" (
echo Mounting "!vhdPath!"
) else (
echo Mounting "!vhdPath!" to "!driveLetter!":
)
REM
REM create diskpart script
REM
set "diskPartScript=%~nx0.diskpart"
echo select vdisk file="!vhdPath!">"!diskPartScript!"
echo attach vdisk>>"!diskPartScript!"
REM assign the drive letter if requested
if not "!driveLetter!"=="" (
echo select partition 1 >>"!diskPartScript!"
echo assign letter="!driveLetter!">>"!diskPartScript!"
)
REM Show script
echo.
echo Running diskpart script:
type "!diskPartScript!"
REM
REM diskpart
REM
diskpart /s "!diskPartScript!"
del /q "!diskPartScript!"
echo Done!
endlocal
When all the files are located in a system path that contains no spaces, everything works fine. But it breaks where there are spaces.
That means that somewhere in the code a path is badly defined by the lack of quotes, probably in the mount script. The trouble is that i don't fully grasp the mount script when it starts using all the "%~...." variable path names.
I had to mix in some powershell commands because for some reason the script wouldn't work unless executed as Administrator.
If someone could give some insight to a newbie, it would be greatly appreciated.
You need end quotes around your parameters when you change directory, i.e.
CD /D "%~dp0"
You can also see all of the %~ options by running 'help for' in a console window. In those scripts it's getting the path or filename from a variable.
Discovered the root of my problem.
The path from script 1 was not being passed faithfully to script 2, even using using quotes or multiquotes.
Thanks for all the input guys!

IF EXIST - Files Without an Extension

A working script that moves various files based on file name runs successfully. After the script is done it will check two directories for any lingering files using IF EXIST *.txt. This works great except I've noticed some files with no extension. These were not an issue before and since that cannot be helped due to processes out of my control, I need to amend my script.
My only idea is the following code. Bear with as there are two conditions:
:check1
PUSHD "\\UNC\path1" &&(
DIR /A-D *.
IF %errorlevel% NEQ 0 GOTO check2
) & POPD
:add1
ECHO Add note to the log file
:check2
PUSHD "\\UNC\path2" &&(
DIR /A-D *.
IF %errorlevel% NEQ 0 GOTO laststep
) & POPD
:add2
ECHO Add note to the log file
:laststep
Some other code before exiting
This should run DIR on the path and if files without extensions exist, it will have %errorlevel% zero and move on to the next check. If there are no files present, it will have %errorlevel% not zero (likely 1) and it will append some text to the log before the next check. Check two will do the same.
This seems to be awfully complicated and I am not able to find a "one-liner" solution that is as easy as IF EXIST. I realize I can use *. but that returns directories as well and may result in an incorrect %errorlevel%.
Updated Code
Where I normally set my variables, I also SET the two paths to run DIR against. This way they can be used more easily elsewhere and I bypass the UNC Path error I normally get - reasons for that are unknown to me. The updated file check, used only for files without an extension, is:
DIR %p1% /b /a-d|FIND /v "." && ECHO Found 1 >> %log%
DIR %p2% /b /a-d|FIND /v "." && ECHO Found 2 >> %log%
FINDSTR /I "Found" %log%
IF %errorlevel% EQU 0 GOTO stillthere
:nofiles
Some code
GOTO domore
:stillthere
Some code
:domore
Other code before exit
Thank you for the responses, I've learned from this.
Is this what you want to find?
dir /b /a-d |find /v "."
#ECHO OFF
SETLOCAL
:check1
PUSHD "u:\path1"
DIR /A-D *. >NUL 2>NUL
IF %errorlevel% EQU 0 ECHO Add note \path1 to the log file
POPD
PUSHD "u:\path2"
DIR /A-D *. >NUL 2>NUL
IF %errorlevel% EQU 0 ECHO Add note \path2 to the log file
POPD
:laststep
:: Some other code before exiting
GOTO :EOF
Your problems include:
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
and you are potentially jumping out of a PUSHD/POPD bracket which would mean your POPD won't necessarily restore your starting directory.
(Note that I used u:\ rather than a server to suit my system)
You are already using && to verify PUSHD worked. You can do the same with your DIR /-D. I find it makes life easier. Also, you probably want to hide any error message if *. is not found, especially since that is the expected condition. I also hid the display of any files that might be found, but you can certainly get rid of that redirection. You might also want to hide error message if PUSHD fails, but I didn't implement that.
PUSHD "\\UNC\path1" && (
DIR /A-D *. 1>nul 2>nul && ECHO Add note to the log file
POPD
)
PUSHD "\\UNC\path2" && (
DIR /A-D *. 1>nul 2>nul && ECHO Add note to the log file
POPD
)
ECHO Some other code before exiting

uTorrent Batch Script

I wrote myself a script based off another one that I found and I'm having trouble figuring out why it's not working.
How it is supposed to work is once a torrent has finished downloading, it runs the script and grabs the Label on the torrent. For testing, I was downloading a song with the label of Music.
When it gets to the point at :copyfile, it won't move it into the correct directory. Instead of moving into F:\Completed Torrents\Music, it just moves into F:\Completed Torrents.
Can someone please point out what I'm missing because I've looked through it thrice already and it's driving me crazy. The script is below.
#echo off
title Liam's torrent-file script
rem Parameter usage: fromdir torrent-name label kind [filename]
rem corresponds to uTorrents flags: %D %N %L %K %F
echo *********************************************
echo Run on %date% at %time%
set fromdir=%1
set name=%2
set label=%3
set kind=%4
set filename=%5
set savepartition="F:\Completed Torrents"
set winrar="C:\Program Files (x86)\WinRAR\WinRAR.exe"
set torrentlog="F:\Torrent Scripts\logs\torrentlog.txt"
set handledlog="F:\Torrent Scripts\logs\handled_torrents.txt"
set errorlog="F:\Torrent Scripts\logs\ErrorLog.txt"
set label_prefix=""
echo Input: %fromdir% %name% %label% %kind% %filename%
rem Check if the label has a sub label by searching for \
if x%label:\=%==x%label% goto skipsublabel
rem Has a sub label so split into prefix and suffix so we can process properly later
echo sub label
for /f "tokens=1,2 delims=\ " %%a in ("%label%") do set label_prefix=%%a&set label_suffix=%%b
rem add the removed quote mark
set label_prefix=%label_prefix%"
set label_suffix="%label_suffix%
echo.prefix : %label_prefix%
echo.suffix : %label_suffix%
goto:startprocess
:skipsublabel
echo Skipped Sub Label
goto:startprocess
:startprocess
echo %date% at %time%: Handling %label% torrent %name% >> %handledlog%
rem Process the label
if %label%=="Movies" goto known
if %label%=="Music" goto known
if %label_prefix%=="TV" goto TV
rem Last resort
rem Double underscores so the folders are easier to spot (listed on top in explorer)
echo Last Resort
set todir=%savepartition%\Unsorted\__%name%
if %kind%=="single" goto copyfile
if %kind%=="multi" goto copyall
GOTO:EOF
:known
echo **Known Download Type - %label%
set todir=%savepartition%\%label%\%name%
echo todir = %todir%
GOTO:process
:TV
echo **Known Download Type - %label%
set todir=%savepartition%\%label_prefix%\%label_suffix%
echo todir = %todir%
GOTO:process
:process
rem If there are rar files in the folder, extract them.
rem If there are mkvs, copy them. Check for rars first in case there is a sample.mkv, then we want the rars
if %kind%=="single" goto copyfile
if exist %fromdir%\*.rar goto extractrar
if exist %fromdir%\*.mkv goto copymkvs
if %kind%=="multi" goto copyall
echo Guess we didnt find anything
GOTO:EOF
:copyall
echo **Type unidentified so copying all
echo Copy all contents of %fromdir% to %todir%
xcopy %fromdir%\*.* %todir% /S /I /Y
GOTO:EOF
:copyfile
rem Copies single file from fromdir to todir
echo Single file so just copying
echo Copy %filename% from %fromdir% to %todir%
xcopy %fromdir%\%filename% %todir%\ /S /Y
GOTO:EOF
:copymkvs
echo Copy all mkvs from %fromdir% and subdirs to %todir%
xcopy %fromdir%\*.mkv %todir% /S /I /Y
GOTO:EOF
:extractrar
echo Extracts all rars in %fromdir% to %todir%.
rem Requires WinRar installed to c:\Program files
if not exist %todir% mkdir %todir%
IF EXIST %fromdir%\subs xcopy %fromdir%\subs %todir% /S /I /Y
IF EXIST %fromdir%\subtitles xcopy %fromdir%\subtitles %todir% /S /I /Y
call %winrar% x %fromdir%\*.rar *.* %todir% -IBCK -ilog"%todir%\RarErrors.log"
IF EXIST %fromdir%\*.nfo xcopy %fromdir%\*.nfo %todir% /S /I /Y
GOTO:EOF
EDIT
Also, for some reason, on line 39 nothing prints to the log. For those who wish to see the code with line numbers: http://hastebin.com/juqokefoxa.dos
A couple of bits for ya:
1) Likely, your script isn't moving the files. Preferences / Directories has an option to move downloads when completed. verify that these settings aren't doing the file moving.
2) uTorrent locks the files on completion so that seeding can continue. To change this behavior, go to Preferences / Advanced and set bt.read_only_on_complete to false
3) you will still be foiled because "Run this program when a torrent finishes" doesn't really do what it says. It runs the program as downloading reaches 100%, but while uTorrent is still either moving the file or seeding. See my bug report here.
A quick summary of the post, just in case that post gets deleted: you have to set the command in "Run this program when a torrent changes state:", add a %S parameter and check that %S == 11
4) Just a tip from my attempt at doing something very similar: when you set the variables from the arguments, add a tilde (%~1 instead of %1). This will strip the quotes off and let us more easily build command lines with the variables later.
You say that the log is not being written to. Try this as a test and see if it writes to the log.
If it doesn't there there is some other fundamental problem.
#echo off
title Liam's torrent-file script
rem Parameter usage: fromdir torrent-name label kind [filename]
rem corresponds to uTorrents flags: %D %N %L %K %F
echo *********************************************
echo Run on %date% at %time%
set "fromdir=%~1"
set "name=%~2"
set "label=%~3"
set "kind=%~4"
set "filename=%~5"
set "savepartition=F:\Completed Torrents"
set "winrar=C:\Program Files (x86)\WinRAR\WinRAR.exe"
set "torrentlog=F:\Torrent Scripts\logs\torrentlog.txt"
set "handledlog=F:\Torrent Scripts\logs\handled_torrents.txt"
set "errorlog=F:\Torrent Scripts\logs\ErrorLog.txt"
set "label_prefix="
set "handledlog=%userprofile%\desktop\handled_torrents.txt"
>> "%handledlog%" echo Input: "%fromdir%" "%name%" "%label%" "%kind%" "%filename%"
>> "%handledlog%" echo %date% at %time%: Handling "%label%" torrent "%name%"

Temporarily set file associations

I have a portable development tool that I want to use on other PCs. I'd like to set a file association so that clicking a file opens the tool. Then, when I'm finished, I want to undo or reset the file association on that PC.
Is there a way to do this? Possibly from a batch file?
Well, you can use the ftype and assoc commands to create or delete file type associations:
ASSOC .foo=FooFile
FTYPE FooFile=X:\Foo\foo.exe %1 %*
You can delete them later with
FTYPE FooFile=
ASSOC .foo=
EDIT: I've got a try now with something that enables you to re-set the association back to its default. I put it in my Subversion repo. In its current stage it generates two new batch files: set.cmd and reset.cmd; one of which sets a new association, the other reverses it. Rolling the set.cmd into the actual batch shouldn't be too difficult but it would have made testing here a hell, so I'll leave that as an exercise.
Code follows, it should be commented enough, hopefully.
#echo off
setlocal enableextensions enabledelayedexpansion
rem Debug flag. Generates a little more output; un-set if undesired
set DEBUG=1
rem Parse arguments and help
if [%1]==[] goto Usage
if [%2]==[] goto Usage
rem Find out whether the association is taken
for /f "usebackq tokens=2* delims==" %%x in (`assoc .%1 2^>nul`) do set assoc_old=%%x
if defined DEBUG (
if defined assoc_old (echo Association already defined: [%assoc_old%]) else (echo Association not yet taken)
)
rem Find a new, unused association
rem Note that we assume that we find one, eventually. This isn't guaranteed, but we'll ignore that for the moment
rem Otherwise this loop might run forever
:loop
set assoc_new=My.%1.%RANDOM%
if defined DEBUG echo Trying new association (%assoc_new%)
assoc .%1 >nul 2>nul
if errorlevel 1 (
set assoc_new=
if defined DEBUG echo Didn't work out
) else (
if defined DEBUG echo Found one! \o/
)
if not defined assoc_new goto loop
if defined DEBUG echo Writing reset batch file
echo #echo off>reset.cmd
echo assoc .%1=%assoc_old%>>reset.cmd
echo ftype %assoc_new%=>>reset.cmd
if defined DEBUG echo Writing setting batch file
echo #echo off>set.cmd
echo assoc .%1=%assoc_new%>>set.cmd
echo ftype %assoc_new%=%2 %%1>>set.cmd
goto :eof
:Usage
echo.Usage
echo. %~nx0 type command
echo.
echo. type is the file type to override, such as docx or txt.
echo. No dot before it is necessary.
echo. command is the command to perform on that file.
echo. %%1 is automatically appended at the end.
echo. If the command includes spaces, surround it with quotes.
echo.
echo.Example
echo. %~nx0 txt notepad
exit /b 1

Resources