. | . | . | . | David McCracken |
Windows Scripting Examplesupdated:2018.05.13 |
rem File: d.bat if _%1 == _. goto cdDone if _%1 == _? goto help if /i _%1 NEQ _-H goto doCd :help echo d.bat and dSup.py 2016.04.23. Extended chdir. echo -C = clear traversal list. echo -B or nothing = backward (to previous dir). echo -F or - = forward (to next dir). echo -R = remove current from list and return to previous. echo -R dir = remove dir from list. echo -S = select from list. echo -H = help. echo . = make window title current directory. echo Anything else = target directory. goto done :doCd %~dp0dSup.py %* for /F "delims=" %%d in ( %HOME%\dSupList ) do ( pushd %%d rem pushd is used instead of cd because it changes drive. cd changes current rem directory of another drive but doesn't change drive. if errorlevel 1 ( %~dp0dSup.py -R "%%d" ) goto cdDone ) :cdDone title %CD% :done
See companion Python script dSup.py and equivalent Bash script cdx
The improved chdir for Windows is a relatively complicated script well beyond the capabilities of BAT. d.bat is very simple because it serves only as a shell for the main script dSup.py, written in Python. The Python interpreter executes in a child environment and cannot change the current directory of the parent. d.bat can invoke dSup.py and can change the directory but the only return value it can accept is a single integer ERRORLEVEL. Python can’t change anything in the parent environment so that avenue is also blocked. Thus, the only mechanism available for conveying the target directory to the BAT script is a real file.
The Python script could write just the target directory to a file, which the BAT script could subsequently read, but it also needs a means of storing the directory traversal list. Here too the environment offers no help because any changes to it persist only for the duration of the current invocation. Again, the only viable mechanism is a real file.
A single file can serve both requirements. Each directory path in the traversal
list is written as one line in the file with the target always the first.
for /F "delims=" %%d in ( %HOME%\dSupList ) do (
reads this into a local
variable, which can be dereferenced to change the directory.
d [directory | -C | -B | -F | -R | -S | -H]
. If
directory is . then this just assigns current directory
to the command window title, which is also done in all other command cases as
well.
dSup.py and d.bat are colocated, normally in C:\CmdTools. The directory traversal history file is %HOME%/dSupList. This doesn’t have to already exist but we have to be able to R/W it, i.e. %HOME% must exist. d.bat fails if invoked in an archive/backup directory for BAT files because dSup.py won’t be there. If in a command window in such a directory, either close the window or cd out of it.
d without arguments changes to the most recent directory. Doing this whenever a
command window is opened provides workplace persistence while reducing disk
wear, because dSupList is not rewritten when it doesn’t change, as in
this case. The cmd link target is
%SystemRoot%\system32\cmd.exe /K d.bat
.
The shortcut’s StartIn directory should be one that is rarely
used in a cmd window, e.g. C:\Windows, because it cannot persist, as d means to
go to previous.
The list of recently visited directories is stored in dSupList file, one per line. dSup.py always puts the target on the first line, enabling this file to serve as a pipe from dSup.py to d.bat. d.bat always assumes this and tries to cd to that first line. In some cases, this is just the current directory, essentially doing nothing, which is simpler than devising some magic handshake syntax.
dSup.py doesn’t try to determine whether a requested target directory exists or can be moved into but simply puts it into the list. If the resulting cd fails d.bat recalls dSup.py with the -R argument, asking the head of the list (the failed directory) to be removed. This is not the most efficient mechanism but it does afford the simplest code.
The first line of the directory list file is extracted with a
for /F
statement, which jumps out after the first iteration,
parsing out the first line. BAT doesn’t have another means of doing this.
The option delims=
says that there are no delimiters, telling
for
to not parse the line into separate arguments. Without this,
the line would be parsed on space, truncating directories containing space.
' renx.vbs ' Updated: 2013.08.17 ' Author: David McCracken ' Purpose: Windows vbscript extended file rename. This renames files in the ' current directory with more complete wildcarding than the standard ren ' command. For each file name that passes the filter pattern, the name ' fragments corresponding to *s in the filter replace corresponding *s in the ' replacement pattern. All fragments matching the filter pattern are replaced ' by corresponding fragments from the replacement pattern. ' ' .................... WSH ..................................... ' Both wscript and cscript work with this but wscript's presentation of ' optional show information is clumsy. The only way to implicitly force ' cscript to execute is to make cscript the default: ' wscript //H:cscript ' Only the script's UI is affected. GUI programs execute the same way ' whether invoked by wscript or cscript. ' To suppress cscripts annoying logo: ' cscript //S //Nologo ' ' ................... Use ...................................... ' renx filter replacement [show] Rename, using replacement, all files in ' current directory that match filter. If optional show is any digit, the ' name of each file and its replacment, if it passes the filter, are echoed. ' If show is 0 then the files are not actually renamed. This preview can help ' avoid accidentally losing useful name fragments. For files that pass the ' filter, the name fragments corresponding to the filter's * characters ' replace *s in the replacement pattern in the same order. If the original ' name has fewer of these keepers than the replacement has *s then the ' unmated *s are replaced by empty. If the original name has extra keepers, ' these are concatenated as the last replacement. This rule is arbitrary but ' often useful. ' ' ................. Design ...................................... ' The two basic approaches are to parse the three strings, filter, replacment, ' and the file under test individually and then build the replacement or to ' scan all three simultaneously while building the replacement. The latter ' could be more efficient due to less superfluous data movement but is a more ' complex program. Some intermediate approach might be a good compromise but ' I have taken the easy way, which is to parse the three strings as ' independently as feasible before merging. ' .................... filter and replacment fragment arrays .............. ' Split is used to make the filter fragment array aFilter and the replacment ' array aRep. The filter is not case-sensitive so the fragments are all ' upper-case and will be compared to upper-cased file names. The actual file ' name is used for keepers. To make the distinction clear, file.name is used ' for replacment while filterName, an upper-cased copy is used for filter ' testing. aFilter and aRep are created once and then used for all files ' under test. ' ........................ new name merge ......................... ' An array of keeper fragments is built while testing a file name against the ' filter fragments. The new name is subsequently constructed by interleaving ' replacement and keeper fragments. Which merge source goes first could depend ' on whether the first character of the replacement pattern is *. However, this ' question is side-stepped by the fact that if the first replacement character ' is *, Split produces an empty first element, which appends to the new name ' as nothing. Thus, the replacement can go first in all cases. ' .................................................................... const ShowNone = 0, ShowAll = -1, ShowFile = 1, ShowFilter = 2, ShowFail = 4 const ShowKeep = 8 Show = ShowNone ' This can be overridden by optional show argument. noRename = false ' May be overridden by show argument. set shell = CreateObject("WScript.Shell") set fso = CreateObject("Scripting.FileSystemObject") srcdir = shell.CurrentDirectory helpLines = Array ( _ "renx.vbs renames files in the current directory.", _ "renx filter replacement [show]", _ "File names that pass filter are renamed according to replacement.", _ "Optional argument show:", _ " 0 = show old/new names while renaming.", _ " 1 = show names but don't rename files.", _ " 2 = show analysis but don't rename files.", _ "Fragments of the old name corresponding to filter *s replace replacement *s", _ "in the same order. Extras are concatenated for the last replacement.", _ "Examples:", _ "renx 08493357-0*.PNG hap*.png renames 08493357-001.PNG hap01.png", _ " and skips 08493357-001.PNGX", _ "renx 0*93*7-0*.P* hap*ong renames 08493357-001.PNG hap843501NGong", _ " and 08493357-001.PNGX hap843501NGXong") if WScript.Arguments.Count < 2 then for each line in helpLines WSH.Echo line next WScript.quit end if if WScript.Arguments.Count > 2 then Show = ShowFile if WScript.Arguments(2) <> 0 then noRename = true if WScript.Arguments(2) > 1 then Show = ShowAll end if end if end if aFilter = Split( ucase( WScript.Arguments(0)), "*" ) aRep = Split( WScript.Arguments(1), "*" ) nRep = UBound( aRep ) if nRep < 1 and UBound(aFilter) > 0 then WSH.Echo "Replacement must have at least one * if filter has any." WScript.quit end if dim aKeep(20) dim nKeep for each file in fso.GetFolder( srcdir ).Files filterName = ucase( file.Name ) if Show and ShowFile then WSH.Echo "Checking " & file.name end if pass = true pos = 1 nKeep = 0 for idx = 0 to UBound( aFilter ) frag = aFilter( idx ) if frag <> "" then match = InStr( pos, filterName, frag ) if match < 1 then if Show and ShowFail then WSH.Echo "Fails on " & frag end if pass = false exit for end if if Show and ShowFilter then WSH.Echo "Passes " & frag end if if idx > 0 then keep Mid( file.name, pos, match - pos ) end if pos = match + Len( frag ) end if ' frag <> "" next ' for idx = 0 to UBound( aFilter ) if pass and pos <= Len( filterName ) then if aFilter( UBound( aFilter )) <> "" then if Show and ShowFail then WSH.Echo "Fails on trailing " & Mid( file.name, pos ) end if pass = false else keep Mid( file.name, pos ) end if ' aFilter( UBound( aFilter )) <> "" end if ' pass and pos <= Len( name ) if pass then idx = 0 newName = "" for each frag in aRep newName = newName & frag if idx < nKeep then newName = newName & aKeep( idx ) idx = idx + 1 end if next if Show and ShowFile then WSH.Echo "New name is " & newName end if if noRename = false then file.name = newName ' Rename the actual file object end if end if next ' file in fso.GetFolder( srcdir ).Files ' ------------------ keep ------------------------------- sub keep( frag ) dim idx ' If there are more keepers than slots for them in the replacement pattern ' then concatenate all extras into the last keeper, which will subsequently ' fill the last replacement slot. if nKeep >= nRep then idx = nKeep - 1 aKeep( idx ) = aKeep( idx ) & frag else idx = nKeep aKeep( nKeep ) = frag nKeep = nKeep + 1 end if if Show and ShowKeep then WSH.Echo "keep " & aKeep( idx ) end if end sub ' keep
Windows’ command line wild card capability affords only rudimentary file group
renaming. For example, patent office image files have long numeric names like
08493357-001.tif, which I would like to replace with shorter and more intuitive
names. Windows documentation implies that the command
ren 08493357-0*.tif hap*.tif
would rename the series hap01.tif, hap02.tif, etc. Instead, the new names
are hap93357-001.tif, etc. Similarly, ren 08493357-0*.tif *.tif
reproduces exactly the same name as the original. There is no way to remove
pieces of a name but only to replace them with other characters. A better rename
facility would replace each * in the new name pattern with the * phrase
in the original, retaining the literals in the new pattern while discarding
them in the original. For example:
ren 08493357-0*.tif hap*.tif
produces hap01.tif, etc.
ren 08493357-0* hap*
produces hap01.tif, etc.
ren 08493357-0* *
produces 01.tif, etc.
ren *93357-0* *
produces 08401.tif, etc.
Windows dir command interprets wild cards much better than ren. With the patent
image files, for example, dir *93357-0*
shows all of the expected
tif files as well as png files that also match this pattern.
dir *93357-0*.tif
shows only the matching tif files. Using dir to
filter the names and piping into a more capable string processor like AWK can
come close to the desired outcome. For example:
dir /b 08493357*.tif | awk "{name=$1; sub(/08493357-0/, \"hap\", name);
system(\"ren \" $1 \" \" name) }"
This achieves the desired goal but is too complicated to serve as a routine command.
An AWK script could be written to simplify usage but a bat file would still be needed to invoke the awk script unless Windows is configured to run awk scripts automatically. With the bat/awk pair we would have a choice between filtering and piping in the bat or using awk to filter. In either case, both the filter and replacement pattern have to be passed to the AWK script to tell how to replace fragments. Using WSH (Windows Scripting Host) functions, the same result can be achieved by a single vbscript or jscript. I elected to use vbscript for this because of its less complicated WSH interface.
Whether the rename script processes one file passed to it by an external filter, i.e. dir, or filters the directory itself, the core is the same. It has three input components, filter pattern (which is needed for replacement even if the script doesn’t do the filtering), replacement pattern, and one old file. From these, it produces a new name. The script really only has to do this and could pass the name on to some other process to do the actual renaming but it is convenient for the script to do this final step.
Conceptually, the new name is created by scanning the old name for the literal fragments of the filter pattern, replacing each * in the replacement pattern in order with the original intervening fragments. The filter pattern and old name are scanned without backtracking. To match the filter, the old name must have the same fragments in the same order. The old name’s intervening strings, which replace * in the filter pattern may be empty. This is the standard regular expression interpretation of *, “zero or more instances”. We could say that in this application there must be at least one character in the old name to replace each * but that probably would not be better and might be worse. Usage may clarify this. Changing the behavior would be trivial.
The fragments of the old name that match the filter are called match fragments
and the intervening ones the keep fragments or keepers. Generating the new name
by interleaving replacement and keeper fragments is conceptually trivial if the
old name contains the same number of keepers as there are *s in the
replacement. How best to handle mismatches requires some experience but it
seems that the replacement must always be fully processed. If the keeper list
is exhausted but the replacement contains more *s then there are empties,
concatenating the surrounding replacement fragments. If an old name (which
passes the filter) contains more keepers than there are *s in the replacement,
an arbitrary but useful rule is that all of the extras are concatenated to fill
the last replacement slot. This does not affect the filter. For example, the
original name 08493357-001.PNGX does not pass the filter in the command
renx 0*93*7-0*.PNG hap*.png
but it does pass the filter in the
command renx 0*93*7-0*.P* hap*.png
and is renamed
hap843501NGXYZ.png.
At some point the three strings are parsed. In each case parsing can occur separately or as part of a broader process. For example, we could parse the filter while parsing the old name. We could parse the replacement pattern while composing the new name. We might parse all three while generating the new name. The only part of this process where the three parses would not synchronize easily is in the keeper fragment determination because we don’t know the length of a keeper until we find the next filter fragment match. This requires delaying appending the keeper to the new name until the next match cycle. But one of the easiest ways to handle who appends first, replacement or keeper, when the replacement has a leading * is to split the replacement so that this case produces a blank, which appends to the new name as nothing, enabling the replacement to always go first. But if the keeper is delayed until the next match, the match must go first. This could be resolved by, at each iteration of the loop, first analyzing the next fragment of the filter and old name and then generating the new name fragment from the keeper start determined in the previous iteration and the keeper end determined in the current iteration.
The program would be less efficient (more unnecessary data movement) but simpler if the old name were parsed (using only the filter pattern) and the fragments saved (presumably in an array) and then the new name generated from the replacement pattern while stepping through the keeper array to replace each *. This also enables using an existing split function to parse both the filter and replacement patterns so that only the old name needs a unique parse process. A more efficient unified parse-generate process would not be especially difficult but simplicity is usually more important than performance for scripts in general and certainly in this case.
Verify that with a directory containing 08493357-001.PNG, etc. and 08493357-001.PNGX:
renx 08493357-0*.PNG hap*.png
produces hap01.png, etc, and skips 08493357-001.PNGXrenx 08493357-0* hap*
produces hap01.PNG, etc. and hap01.PNGXrenx 08493357-0* *
produces 01.PNG, etc. and 01.PNGXrenx *93357-0* *
produces 08401.PNG, etc. and 08401.PNGX.
renx *93357-0* *.png
produces 08401.PNG.png ... 08407.PNG.png and 08401.PNGX.png. This is the rule that all extra keepers are concatenated to fill the last replacement slot. This only involves old name fragments that don’t cause the name to fail the filter.renx 0*93*7-0*.PNG hap*.png
renames 08493357-001.PNG to hap843501.png but skips 08493357-001.PNGX.renx 0*93*7-0*.P* hap*.png
renames 08493357-001.PNG to hap843501NG.png and 08493357-001.PNGX to hap843501NGX.png.' File: tifToPng.vbs ' Updated: 2013.08.18 ' Purpose: Windows Script Host vbscript to make a png file copy of every tif ' file in the current directory, optionally stretching the image. ' ' ............................ Use .......................................... ' tifToPng [stretch] ' stretch is a percentage of original. 100 is the original size. The minimum ' is 1, which is 1%, i.e. a 99% reduction. ' Invoke this in the directory containing the files to be converted. It can ' be invoked directly or as an argument to WSH. Most conveniently, if located ' in the exe path, it can be invoked by root name tifToPng. ' ........................... Design ..................................... ' Mspaint is controlled by open-loop key stuffing to the shell, which can make ' a mess if an unexpected app activates at some point. It is best to close all ' apps before running this. A confirmation dialog pops up before overwriting a ' (PNG) file. The user must respond to this within one second to maintain sync. ' It is best to delete any such files before running the script. ' .......................... notes ................................. ' Source/Destination Directory ' This uses shell.CurrentDirectory directly. To take an optional path command ' line argument with default to CD use: ' if WScript.Arguments.Unnamed.Length < 1 then ' srcdir = shell.CurrentDirectory ' else ' srcdir = WScript.Arguments(0) ' end if ' Blocking/Unblocking SendKeys ' By default SendKeys blocks. For unblocking the optional Wait argument is ' false. In most instances we want unblocking so that the script can send ' keystrokes to the app. Telling mspaint to save the file is the one ' exception. We would like to interact with the overwrite confirmation ' dialog. Unfortunately, in this instance control returns immediately to the ' script regardless of the specified or default behavior. The script's one- ' second delay at this point is longer than needed for the write to complete ' in order to give the user an opportunity to respond to the query. The delay ' could be longer but that would delay every write unnecessarily. This is the ' dumbest solution but easy to implement. ' ............................................................................ helpLines = Array ( _ "tifToPng.vbs invokes mspaint to convert all tiff files in the current", _ "directory to png. The one optional argument causes the image to be stretched", _ "e.g. 50 is 50%, 100 is the original size, 150 is 150%.") stretch = 100 set fso = CreateObject("Scripting.FileSystemObject") set shell = CreateObject("WScript.Shell") if WScript.Arguments.Count > 0 then if IsNumeric( WScript.Arguments(0)) then if WScript.Arguments(0) > 0 then stretch = WScript.Arguments(0) else WSH.Echo "Stretch must be > 0." WScript.quit end if else for each line in helpLines WSH.Echo line next WScript.quit end if end if shell.Run "mspaint ", 1 WSH.Sleep 1000 for each file in fso.GetFolder(shell.CurrentDirectory).Files if ucase(fso.GetExtensionName(file.Name)) = "TIF" then ' Type Alt-F O for File > Open shell.SendKeys "%fo", false WSH.Sleep 500 ' Type source file name into name field. We don't need to move to the field ' because this is automatic when the dialog opens. shell.SendKeys file.Path, false ' Type Alt-O for Open button shell.SendKeys "%o", false WSH.Sleep 500 ' Now we are back in Paint's main window if stretch <> 100 then ' Type Alt-I, S for Image > Stretch. Hot key Ctrl-W doesn't work. shell.SendKeys "%IS", false WSH.Sleep 500 ' Automatically in Horizontal stretch edit. shell.SendKeys stretch shell.SendKeys "{TAB}" ' TAB to Vertical stretch edit WSH.Sleep 100 shell.SendKeys stretch shell.SendKeys "{ENTER}" WSH.Sleep 500 end if ' stretch ' Type Alt-F A for File > Save As shell.SendKeys "%fa", false WSH.Sleep 500 ' Type P(ng) Save. No Wait arg defaults to true. shell.SendKeys "%tp%s" WSH.Sleep 1000 end if ' TIF next ' file WSH.Sleep 500 shell.SendKeys "%fx", false ' Exit Paint
Windows Paint program does a good job of translating a wide variety of graphic file formats with its Save As command. It also affords simple but effective editing, particularly cropping and resizing. However, it affords only direct interactive control, limiting its usefulness. It is simply too tedious to use for repetitive manipulation of more than one or two files. It is not intended for serious production, which can justify a professional program. But many users have very light production needs for which such a program is overkill but which are tedious to do interactively.
In this example, to include my own patent images, which are only available as TIFF, in a web page, I wanted to convert the files to png. I also wanted to shrink the images, which are twice as large as appropriate for web viewing. For one file, Paint would be ideal. I had more than a few files to process but I did not intend to go into production.
WSH (Windows Scripting Host) provides the means to invoke and control programs that, like Paint, afford no automation facilities, but they have to be used carefully. Using the WSH Exec function, a script can invoke and get status information about an external program, allowing some closed loop coordination. GUI programs, like Paint, cannot be launched by Exec but only by Run, which affords no such coordination. Any script launching Paint must run open-loop, with delays at least long enough to guarantee that Paint has finished the previous command before sending another. The commands themselves are sent through the WSH function SendKeys. It is easy to send key events to whatever program is listening but very difficult to force or even know for sure what program will actually receive these keystrokes. Further, SendKeys has an optional Wait argument, which is true by default, making the call blocking, which is unacceptable if the script must send additional events. However, whether or not the call actually blocks depends on application context. The effect is not the same for the main target program, its own dialogs, and any common shell dialogs (such as file open, in this example) the application invokes.
To keep this application simple, certain limitations are imposed. All tif files in the current directory are converted. No other directory can be specified and files are filtered strictly on the extension tif (case-insensitive).
The script could operate on one given file, which could be provided by another program or command, such as dir. However, this might create additional uncertainty in the key stuffing interface between the script and Paint, making it especially vulnerable on large file sets. I have chosen instead to have the script get the current directory from the shell and use the file system object to iterate over this.
This script has performed reliably even given the problematic nature of open-loop operation and key stuffing uncertainty. It is a reasonable model to guide the creation of similar scripts for other uses of Paint and other similarly automation-challenged programs. Some general design suggestions derived from this are:
shell.Run "mspaint ", 1
rem makeSvnRepo.bat if exist log del log echo Create Subversion repository Svn7kFw >> log md Svn7kFw >> log svnadmin create Svn7kFw >> log echo Create Svn7kFw/Src from the Unified code set >> log svn import Unified %1/Svn7kFw/Src -m "Unified coding venue" >> log echo Create Ver and Alt folders >> log svn mkdir %1/Svn7kFw/Ver -m "Unified versions" >> log svn mkdir %1/Svn7kFw/Alt -m "Unified alternatives" >> log echo Copy the Unified code from Src to Ver/2.0 to capture version U2.0 >> log svn copy %1/Svn7kFw/Src %1/Svn7kFw/Ver/2.0 -m "Unified code 2.0" >> log echo Create Polycom alternative >> log svn mkdir %1/Svn7kFw/Alt/Polycom -m "Polycom alternate program design" >> log echo Copy unified code u0 as the first Polycom version >> log svn copy %1/Svn7kFw/Src %1/Svn7kFw/Alt/Polycom/Src -m \ "Unified 2.0 baseline for Polycom" >> log echo Create Polycom Ver and Alt folders >> log svn mkdir %1/Svn7kFw/Alt/Polycom/Ver -m "Polycom versions" >> log svn mkdir %1/Svn7kFw/Alt/Polycom/Alt -m "Polycom alternatives" >> log echo Capture Polycom version 2.0 = u0 >> log svn copy %1/Svn7kFw/Alt/Polycom/Src %1/Svn7kFw/Alt/Polycom/Ver/2.0 -m \ "Polycom 2.0 is Unified 2.0" >> log @ rem Prepare Polycom version P2.1. The current version, which is really the u0 @ rem (version U2.0) code set, is first checked out just to prepare the @ rem Subversion client venue. Then all of the checked out code is discarded and @ rem replaced by the Polycom-Unified code set. This is then checked in @ rem (committed). Then it is captured into version 2.1 @ if exist src/nul RD /S /Q src @ rem md src -- not needed. svn checkout creates Src (like it or not) svn checkout %1/Svn7kFw/Alt/Polycom/Src >> log del /Q Src copy PolyUni Src >> log svn commit Src -m "Touch Apps Internal\exchange\firmware\DJ\\ Gestures\preliminary\48pin_Polycom_Unified with merged includes" >> log svn copy %1/Svn7kFw/Alt/Polycom/Src %1/Svn7kFw/Alt/Polycom/Ver/2.1 -m \ "Polycom version 2.1 is Touch Apps Internal\exchange\firmware\DJ\Gestures\ \preliminary\48pin_Polycom_Unified with merged includes" >> log @ rem Prepare, commit and capture Polycom 2.2. This procedure is the same as use @ rem for version 2.1 except that the initial checkout to the temporary Src is @ rem not needed, as the directory is already in the required state. del /Q Src copy Polycom Src >> log svn commit Src -m "Touch Apps Internal\Firmware Released to field\Polycom\\ 2011-0701 Polycom v15 Faster FrontEndFilter\2011-0701 Polycom v15" >> log svn copy %1/Svn7kFw/Alt/Polycom/Src %1/Svn7kFw/Alt/Polycom/Ver/2.2 -m "Polycom version 2.2 Touch Apps Internal\Firmware Released to field\Polycom\\ 2011-0701 Polycom v15 Faster FrontEndFilter\2011-0701 Polycom v15 with merged \ includes" >> log
See process diagram
This builds a Subversion repository for an existing project with numerous branches and no existing version control. Knitting these into a unified project repository requires a long sequence simulating what should have been done in the first place, that is choosing a trunk, creating branches, and merging the branches back into the trunk.
Normally, there would be no reason to script this process because it would have evolved over time. However, in this situation, the process is very complex and likely to have errors until completed and fully tested. It was much faster for me to be able to automate the entire process as I developed and tested it so that I could easily scrap the test repository and start over.
The fact that the programmers had ongoing developments while I designed and tested this process meant that I was working with constantly changing sources so any repository that I did build was almost immediately invalid. Further, to start using the new version control I would have to tell all of the programmers to stop working while I built it and switched them over to it. By scripting everything and fully testing the results in a sandbox, I was able to suspend their work for less than 10 minutes, which the new repository was built from current sources and the programmers were all transparently switched over to it.
This is a batch file that creates the initial repository as a local file from a set of three original source folders. The three source folders, Unified, PolyUni, and Polycom contain fully prepared sources, including endstep.bat, .cproject, and .project Eclipse files. The include files have been moved into the source folder.
The repository is created at the same level as the three source files. The batch file is invoked with the URL of this working directory as a parameter to avoid the complexity of DOS path conversion to URL.
makeSvnRepo.bat: Make Svn7kFw Subversion repository from Unified, Polycom-Unified, and Polycom sources. This requires a command argument, which is the URL version of the CWD (%CD% in XP batch). i.e. file:///C:/path... The Svn7kFw Subversion repository will be created under CWD. This requires the source folders, Unified, PolyUni, and Polycom, to exist in CWD and to contain only the files that will be under version control, *.c, *.h, 7000scrpt.ld, ".project", ".cproject", and endstep.bat.
rem getCe60.bat for /F "tokens=1,2" %%p in (..\Specific\ProjList) do ( echo ......... Project %%p ........... md %%p > nul 2> nul cd %%p !vc! WorkFold "$/WinCE/CeProgs/%%p" !CD! echo. rem Get the development version of project control files. Most or all rem of these will be overwritten by the User versions but getting these rem relieves the User domain of having to create duplicates where there rem is no difference (makefile is the most likely one of these). echo Getting development configuration files for %%p version %%q from CeProgs for %%f in (bib,pbpxml,reg,pbcXml) do ( !vc! get -Vl%%q -W "$/WinCE/CeProgs/%%p/%%p.%%f" ) !vc! get -Vl%%q -W "$/WinCE/CeProgs/%%p/sources" !vc! get -Vl%%q -W "$/WinCE/CeProgs/%%p/makefile" rem Get User versions of these files over the development versions. These are rem release-specific rather than project version-specific and may change very rem little even between releases. !vc! WorkFold "$/WinCE/User/CE60/%%p" !CD! echo. echo Getting User configuration files for %%p release version !ReleaseVer! !vc! get -Vl!ReleaseVer! -W "$/WinCE/User/CE60/%%p" rem Get dll, exe, pdb, and rel files for every supported CPU for /F %%c in (..\..\Specific\CpuList) do ( echo Getting files for %%c md %%c > nul 2> nul cd %%c !vc! WorkFold "$/WinCE/CeProgs/%%p/CE60/%%c/retail" !CD! echo. !vc! get -Vl%%q -W "$/WinCE/CeProgs/%%p/CE60/%%c/retail" if errorlevel 1 ( echo Unable to get version %%q of %%p %%c retail components exit /B 1 ) rem Return to project directory to process the next cpu cd .. ) rem Return to BuildDir directory to process the next project cd .. ) rem We are now at BuildDir directory. echo Getting User general installation programs for release version !ReleaseVer! !vc! WorkFold "$/WinCE/User/CE60" !CD! echo. !vc! get -Vl"!ReleaseVer!" -W "$/WinCE/User/CE60" echo End GetCE60.bat rem Return to CePack
getCe60.bat is one component in a hierarchical system of scripts that automates building a Windows CE release package. The entire process fully automates clearing all residuals from the build computer, checking out of version control all sources, including the build files themselves, and building a suite of device drivers, libraries, and applications for five different CPUs and three versions of WinCE. This particular file, is from the WinCE version group. It only provides information specific to WinCE 6.0. Other scripts provide version release file lists, overall packaging procedures, and CPU-specific information.
This resides in version control under $WinCe\Package\CE60 and is labelled by package release version. Related files are ProjList and CpuList.
This should not be invoked directly but only from CePack (note echo already is off. This is normally invoked from the BuildDir directory even though it and some files that it references are located in the Specific directory. Since BuildDir and Generic are at the same level, ..\Specific\ path doesn't fail whether CWD is BuildDir or Specific.
Environment variables inherited from CePack are CePackVer=CE60 and e.g. ReleaseVer=114, PackVer=CE60V114, GetVer=GetCE60.bat, PackName=EloCE60V114
This requires a release-labeled ProjList file, which contains pairs of
component/file names and their versions for this release, e.g. (for V114)
EloHid 1.23
EloTch 1.20
EloStub 3.11
EloVa 2.20
EloCpl 3.13
This script does not embed the release version, which is human input via CePack. It does not know the project/components or their version numbers, which is provided by the release-labelled ProjList file. It does not know which target CPUs are support, which is provided by the release-labelled CpuList. However, it assumes that bib,pbpxml,reg files constitute the complete list of files that are needed for each project because this is CE6.0-specific. This script may never change but will be labelled for each release version so that it can change if necessary without disturbing previous releases.
Tell installed programs by searching uninstall reg key and filtering on DisplayName (using grep). Arguments: None needed but if one is provided, another level of filtering is applied to it (by reinvoking grep). e.g. installed mcaf shows all installed McAfee programs. If an argument is not provided a second level of grep is still done to filter out the many Microsoft “update” items, which are rarely of any interest.
-w is used for grep to filter out instances of DisplayName that are embedded in other names e.g. ParentDisplayName, which appears in W764 Wow6432Node. Wow6432Node Uninstall is added for W764 for the 32-bit programs that are installed, e.g. 7-zip. This doesn't require a special version of the script; The key is queried unconditionally with reg errors directed to nul.
rem installed.bat if _%1 == _? ( echo installed.bat lists installed programs by searching uninstall reg key echo With no argument, all programs are listed. Otherwise, the argument is a filter echo passed to grep -i. This can be a regular expression if it includes -E, e.g. echo installed -E "(libre)|(7-zip)" goto done ) if "%1" == "" ( reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /s | grep -w DisplayName | grep -v -i update echo ------------------------------ reg query "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" /s 2>nul | grep -w DisplayName | grep -v -i update ) else ( reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /s | grep -w DisplayName | grep -i %* echo ------------------------------ reg query "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" /s 2>nul | grep -w DisplayName | grep -i %* ) :done
See installed.awk
Show the DisplayName, Publisher, and InstallLocation of all installed programs or just those that contain the given filter string in one of these three values. Programs whose name contains “update” are ignored. Backslash in path filters must be replaced by / at the command. See installed.awk for additional details. Use installed.bat for listing and filtering on just the DisplayName.
Invocation Examples:
installedx
installedx libre
installedx d:/wpgm
Wow6432Node Uninstall is added for W764 for the 32-bit programs that are installed, e.g. 7-zip. This doesn't require a special version of the script. The key is queried unconditionally with reg errors directed to nul.
This passes the name of installed.awk to awk. A a full path is needed unless installed.awk is in %CD%, which is unlikely, given that this batch script is a general utility, which may be invoked from any directory. Instead of hard-wiring a path, it is assumed that the awk script will always be located in the same directory as the batch script, whose path is represented by %~dp0.
rem installedx.bat if _%1 == _? ( echo installedx shows the DisplayName, Publisher, and InstallLocation of all echo installed programs or just those that contain the given filter string in echo one of these three values. Backslash in path filters must be replaced by echo / in the command line. goto done ) if "%1" == "" ( reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /s | awk -f %~dp0installed.awk echo ------------------------------ reg query "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" /s 2>nul | awk -f %~dp0installed.awk ) else ( reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /s | awk -v filter=%1 -f %~dp0installed.awk echo ------------------------------ reg query "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" /s 2>nul | awk -v filter=%1 -f %~dp0installed.awk ) :done
rem link.bat setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION if _%1 == _ ( :help ... goto done ) if /i _%1 == _show ( if _%2 == _ ( for %%f in (*.lnk) do shortcut /F:"%%f" /A:Q goto done ) if /i _%2 == _target ( set SearchFor=TargetPath= ) else if /i _%2 == _arguments ( set SearchFor=Arguments= ) else if /i _%2 == _directory ( set SearchFor=WorkingDirectory= ) else if /i _%2 == _icon ( set SearchFor=IconLocation= ) else goto showOne for %%f in (*.lnk) do ( echo -- In "%%f" see -- shortcut /F:"%%f" /A:Q | grep !SearchFor! ) ) else if /i _%1 == _find ( if _%2 == _ ( echo Command error: missing search string goto help ) for %%f in (*.lnk) do ( echo -- In [%%f] find -- shortcut /F:"%%f" /A:Q | grep -i %2 ) ) else if /i _%1 == _FolderIcon ( if "%LINKFOLDERICON%" == "" ( shortcut /F:%2 /A:E /I:%%SystemRoot%%\system32\SHELL32.dll,155 ) else ( shortcut /F:%2 /A:E /I:%LINKFOLDERICON% ) ) goto done :showOne if not exist %2 ( echo Command error: '%2' does not exist goto help ) else ( shortcut /F:%2 /A:Q ) goto done :done set /p dummy=Press Enter to close
link.bat displays attributes of one or all lnk files in CWD
rem dirCsv.bat dir %* /b | awk -f %~dp0toCsv.awk
dirCsv.bat shows files names in comma-delimited list. This can take arguments to dir, such as filter and /s, but not formatting switches. Use dirCsvQ.bat for quoted names.
rem dirCsvQ.bat dir %* /b | awk -v Q=1 -f %~dp0toCsv.awk
dirCsvQ.bat shows quoted file names in comma-delimited list. This can take arguments to dir, such as filter and /s, but not formatting switches. Use dirCsv.bat for unquoted names.
# toCsv.awk BEGIN { FS="\n" } { if( Q == 1 ) printf "\"%s\", ", $1 ; else printf "%s, ", $1 }
toCsv.awk prints the input string with trailing comma and space but not newline. Mainly used with dirCsv.bat to show all files in the current directory in a comma-delimited list. Each string is quoted if the command line variable Q is defined as 1, e.g. dir %* /b | awk -v Q=1 -f \CmdTools\toCsv.awk