Looking for an app or script to find, rename and replace files.

I know this can easily be done in Linux with various commands but I am unsure how to do this in Windows. I looked at Sysinternals first and they don't seem to have what I want. I need to search for a file, by name, rename it to filename.extension.old and replace it with a new version in another directory. I did some reading on .bat, Powershell and Windows Scripting but I feel way over my head in this. Are any of you familiar with an easy solution or know of an existing one that you have used in the past?

If you know how to do it under Linux (or can easily find out) then there's always Cygwin.

Yeah just use Cygwin to run the Unix commands under Windows. I do it all the time with grep.

Other people have given you a solution but if you do want to get into .bat then it's actually pretty easy.

Is the file in a fixed location? Then the job is even that much more easy. Keep in mind all a batch file (I prefer the .cmd extension myself but it is the same thing) does is string together commands you'd type into the command line/window.

Here is a sample batch file:

rename c:\test\filename.txt c:\test\filename.txt.old
copy d:\newfilename.txt c:\test\newfilename.txt

You can also get fancy and copy the file from another computer so you don't have to copy it over to the computer first:

rename c:\test\filename.txt c:\test\filename.txt.old
copy \\remotecomputer\sharename\newfilename.txt c:\test\newfilename.txt

Keep in mind if there are spaces in your path names you will need to use " to define your paths, like so "C:\Program Files\Test\". However, certain directories in Windows have builtin shorter names for variables in batch files:
(These are for XP they may not work on other OS's the same way)
%ALLUSERSPROFILE% C:\Documents and Settings\All Users
%APPDATA% C:\Documents and Settings\{username}\Application Data
%COMPUTERNAME% {computername}
%COMSPEC% C:\Windows\System32\cmd.exe
%HOMEDRIVE% C:
%HOMEPATH% \Documents and Settings\{username}
%PATH% C:\Windows\System32\;C:\Windows\;C:\Windows\System32\Wbem
%PATHEXT% .COM; .EXE; .BAT; .CMD; .VBS; .VBE; .JS ; .WSF; .WSH
%PROGRAMFILES% Directory containing program files, usually C:\Program Files
%PROMPT% Code for current command prompt format. Code is usually $P$G
%SYSTEMDRIVE% The drive containing the Windows XP root directory, usually C:
%SYSTEMROOT% The Windows XP root directory, usually C:\Windows
%TEMP% and %TMP% C:\DOCUME~1\{username}\LOCALS~1\Temp
%USERNAME% {username}
%USERPROFILE% C:\Documents and Settings\{username}
%WINDIR% C:\Windows

There is a way to do this from your machine on many remote machines with batch and using psexec from sysinternals but learn to walk first.

There is a freeware app called flexible renamer that can do what you ask.

Nerds. I smell nerds!

The forum has a code tag, no need to use quote and dots to indent.

EDIT: I see what I did wrong. You used the BBCode "code" tag, and I used the HTML "code" tag listed under the post window in the "allowed HTML tags" list.

Cygwin and BASH, baby! It only takes a few lines:

#!/bin/bash # rename.sh # usage: ./rename.sh (filename) # e.g. ./rename.sh myfile.txt # Cygwin mounts Windows filesystems in /cygdrive/(drive letter) REPLACE_DIR="/cygdrive/c/Documents and Settings/Edwin/My Documents/replace" FIND_PATH="/cygdrive/c/" echo "Edwin's Big Fat Search & Replace Script" echo "Searching for:" $1 find "$FIND_PATH" \( -path "$REPLACE_DIR" -prune \) -o -type f -name "$1" -print | while read FILE do echo "Found:" "$FILE" cp "$FILE" "$FILE".old echo "Renamed old version:" "$FILE".old cp "$REPLACE_DIR/$(basename "$FILE")" "$(dirname "$FILE")" echo "Copied" "$REPLACE_DIR/$(basename "$FILE")" "to location:" "$(dirname "$FILE")" done

That will search FIND_PATH (excluding your directory of replacements, if it falls within FIND_PATH) for the filename you input, make a .old copy in the same directory, then grab the file of that name from your replacements folder and copy it over the original.

It wouldn't be hard to extend it so that you can input a search path at the command line instead of having to edit the hard-coded FIND_PATH in the script.

Needs to be tested, I just whipped it up and did a couple of trivial tests (does work just fine in Cygwin and with all those Windows path names with spaces in them, but as far as complete correctness goes, I'm not an expert in the finer points of GNU findutils - or even the duller ones, really). But if it's fully correct, it probably would have run correctly 10-15 years ago, and it will probably still be usable 10-15 years from now.

EDIT: changed quote tag to code tag, thanks nossid

Your post is using <code> which breaks for me as well, while I used [code] in mine.

Edit: too slow

Edit: Problem resolved, post cleared to reduce clutter.

Figuring that out was more work than writing the damn script in the first place.

If dealing with large files, it might be wise to change the line

cp "$FILE" "$FILE".old

to

mv "$FILE" "$FILE".old

... so that it does actually rename instead of just make a copy with the .old name. I had it as a cp just so when I tested, I could just delete the .old files instead of having to rename my test file back.

EDIT: Lesson learned, use BBCode "code" instead of HTML "code" tag. Post removed.

All right, here is my jack of all trades psexec script(s). You can modify this to your hearts desire but one of the best way to learn to script is to see others. So here goes. NOTE - All quoted text should be one line, some lines wrapped.

This script can be tailored to do just about anything, it is a script that calls another script BUT with the important distinction that that other script is then running on another computer. I've rolled out the Win2K/2K3 DST changes to over 1000 machines back in 2005 with this script. I've created a profile backup tool with it for use in migrating a 1200+ machine environment to XP from Win2K. It can do almost anything you can script.

The script is actually three things, two scripts (the one that will call psexec and the script that psexec will execute) and a text file with machine names, one per line like this:
PC1
PC2
PC3
and so on. This is versatile so that if you have over a couple of hundred machines and the script takes a while to run, break the machine list up into batches of 50 and run it on different machines at once (each machine will need psexec and the three files needed by the script). With that said, here we go.

The first script is the script that calls psexec. I usually put psexec in my C:\Windows\System32 directory since that is alrady part of the path but you can put it anywhere you want just make sure to use the whole path in the script.

@echo off :: Begin Script - Name = Sample.cmd :: This is the most versatile script you'll ever use. :: Created by Les Bowman :: 08/01/2006 Ver 1.0 :: ** Following command changes the root folder to where batch file is running from. cd /d %~dp0 :: Requirements - sample-list.txt, a list file of PC names one-per-line w/o "\\" if not exist sample-list.txt goto :end del /q sample-dead.txt cls Echo - Echo Running List Check... Echo - FOR /F %%i in (sample-list.txt) do call :listcmd %%i pause goto end :listcmd @echo off if "%1"=="" goto :errmsg ::Display PC name being processed echo. echo. echo Processing: %1 :If file on remote PC not seen - skip as 'dead' if not exist \\%1\admin$\system32\ntoskrnl.exe goto dead :****************************************************************** :****************************************************************** :SECTION_BEGIN - FOR COMMAND LINES RUN AGAINST THE REMOTE SYSTEM :************ Modify entries within this section only ************* :****************************************************************** :: set local environment path for utilities set CMDTOOLS=\\remotesvr\path\ :: Copy All Files Needed to client copy "%cmdtools%\exename.exe" \\%1\admin$\system32 psexec \\%1 -c -f _sample.cmd :: Delete copied files from client del \\%1\admin$\system32\exename.exe :****************************************************************** :****************************************************************** :SECTION_END FOR COMMAND LINES RUN AGAINST THE REMOTE SYSTEM :****************************************************************** :****************************************************************** :errormsg echo There was an error with the -list file, verify that there are no blank lines. goto :end :cont ::Log system as done after running command against it echo %1 checked &gt;&gt;sample-checked.txt goto :end ::Log system as 'dead' if remote file check failed :dead echo Dead\Offline echo %1 &gt;&gt;sample-dead.txt goto :end :end

I have the file pretty well remarked but I'll go over it anyway.
@echo off

echo off tells the script not to print the commands of the script to the screen, you'll see only output. The @ in front of echo off tells the shell not to print that command. It's for presentation only, during writing and troubleshooting of scripts I recommend you keep this line commented (two colons will do for that). Commented lines don't run at all.

The sample-list.txt file is the list of computer names that you want to run the script on.

This script will create a file called sample-dead.txt with a list of the machines that weren't on when you ran the script or that weren't able to be accessed. This will allow you run the script again but you can copy/paste or rename the names in this file to the -list file. The script cleans itself up, so it will delete the -dead list everytime.

cls - clear screen

It is now piping output to the screen as it checks the list and goes through the rest of the script.
The FOR .. DO loop is a powerful thing, what this one does is for each line in the -list file it goes to the :listcmd passing the %%i parameter (%%i is a temporary variable that the script is using to keep track of the machine name). The FOR command can have its own post, do a for /? and read up on it if you like.

As you've seen I have sections of the script labeled, :listcmd is such a label, this way I can tell the script to jump around the script (usually only downward so you've got to do some logic) depending on certain conditions. In the case of the FOR DO loop I have it verify that the line (ie the machine name) isn't blank "" and if it is not it will go on to the :listcmd otherwise it will jump down to the :errormsg and tell the operator something is up.

So now that the script has a machine name (the script is now displaying "Processing MachineName") it needs to confirm if that machine name is valid it does this by seeing if the ntsokrnl.exe file exists in the C:\Windir\System32 directory of the remote machine. How do I know this? Look at the path:
\\%1\admin$\system32\ntoskrnl.exe
\\%1 is the machine name
\admin$ is the admin share, this is a built in share on a windows box that points to the C:\Windir directory, note that this will find the directory if it's Winnt or Windows or ScoobyDoo unless something is seriously FUBAR'd on the machine or there are group policy settings in place to turn off admin shares. This also means you will need admin rights on the machines you wish to run this script on.

So if this file doesn't exist because the machine isn't on or we can't reach it, we put the machine name in the dead list by jumping down to :dead. If the machine is on we continue.

We are now in the working part of the script, the other stuff before this was for speed (trying to copy files to dead machines take a while to time out) and error checking.
First the script defines a set location for necessary files. This could be a special .exe you don't want left around on the worker PC when the script is done. You may not need this section, I use it to keep things like the resource kit tools on a central server but copy them over to the client for speedier running and then delete the .exe from the local client when done. Delprof is a good example but you could also get by by just calling the command from the psexec section too. This is a demo script only, I've not really fine tuned it since I wrote it in 2005.

So if you need to copy files over from the resource location you can do that.

PSEXEC time. I put psexec in my system32 so if you put it somewhere else you may need to include the path ie:
C:\temp\psexec.exe \\%1 -c -f _sample.cmd

The -c and -f switches tell psexec to copy the _sample.cmd over to the remote machine (c) even if it exists (f). This allows it to run remotely, this also means that in your scripts directory you should have:
_Sample.cmd
Sample.cmd
sample-list.txt

This is another reason I use that resource share, it allows me to only keep the scripts/sub-scripts (the _sample) and the list/dead files in my script directory.

What is _sample.cmd? Here it is for this demo:

@echo off :: Begin Script - Name = _sample.cmd :: The actual script that the remote PC will run via PSEXEC. :: Created by ME :: 03/16/2010 Ver 1.0 :: Ensure log directory and file exists mkdir %windir%\logs Set log=%windir%\logs\sample.log scriptcmd.exe /arg1 /arg2 >>%log%

Not much I know but structure really is more important than payload. Basically, I like to have a log of each time a script runs. You can do more fancy things, I've added the date and time of the script being run as a header as the log file grows but this is a demo.

scriptcmd.exe /arg1 /arg2 can be any exe you want. It can be a .exe you copied over from the resource share it can be a bunch of batch commands like:

rename c:\test\filename.txt c:\test\filename.txt.old
copy \\remotecomputer\sharename\newfilename.txt c:\test\newfilename.txt
Echo FileRename script ran >>%log%

Here's the thing, if the script hangs, this is where it is going to hang. This is the script you need to troubleshoot on a few test machines locally to verify it works OK before trying it out on 1or3 using psexec.

When the script completes psexec closes and you're back at the sample.cmd where you left off.

Now the script will delete any files you copied over from the resource share and the FOR DO loop continues, so it starts the loop again if there is another machine name or it ends if there is not. I never have the master script delete this list like I do the dead list. This and logs can be helpful in determining when and who ran scripts.

We've already gone over the errmsg and dead portions of the script.

Damn that took longer than I meant it to.

I am going to try both methods tomorrow. While reading and experimenting with it today, do either of these scripts (PS or Cygwin) support archives? I noticed that the majority of files are in archives, which I didn't notice at the time?

Cygwin is not like installing an application, it's effectively a mini unix box on your windows machine, and is useful for a huge range of things.

If the files you're looking for are in a zip file or something, you'll need to change the script to list the contents of the archives, piping to a grep command looking for your search name. If it finds it, you'd need to unzip the file into a temp location, rename the file, rezip it and delete the unzipped files. All pretty easy with a bash script.

Windows command shell that's been around since NT is powerful enough to take on most search-and-rename operations. No warmed-over Unix utilities are needed. Familiarize yourself with options offered by the for command by typing help for at command line. Besides for, all you need are copy and rename commands, which are also built-in.

Gorilla.800.lbs wrote:

Windows command shell that's been around since NT is powerful enough to take on most search-and-rename operations. No warmed-over Unix utilities are needed. Familiarize yourself with options offered by the for command by typing help for at command line. Besides for, all you need are copy and rename commands, which are also built-in.

Right, my script is an uber for script (with lots of extra lines for documentation) but to make it shorter and sweeter it would just have to be (note I am still using a seperate txt file for machine names and assume admin rights):

@echo off cd /d %~dp0 :: Requirements - sample-list.txt, a list file of PC names one-per-line w/o "\\" if not exist sample-list.txt goto :end FOR /F %%i in (sample-list.txt) do call :listcmd %%i pause :listcmd ren \\%1\driveadminshare$\path\to\filename.ext \\%1\driveadminshare$\path\to\filename.ext.old copy \\servershare\wherenewfileis\filename.ext \\%1\driveadminshare$\path\to\filename.ext :end

I just like making the 'dead' list since running a script against 1000+ machines EVERY TIME gets a bit old, I'd like to make the batches smaller with each run until I've hit all the machines. I also include a lot of error checking and commentary since I assume I am not going to be working anywhere for the rest of my life and have been burned in the past by admins who don't like to document their work.

That's the way to go!

DudleySmith wrote:

Cygwin is not like installing an application, it's effectively a mini unix box on your windows machine, and is useful for a huge range of things.

If the files you're looking for are in a zip file or something, you'll need to change the script to list the contents of the archives, piping to a grep command looking for your search name. If it finds it, you'd need to unzip the file into a temp location, rename the file, rezip it and delete the unzipped files. All pretty easy with a bash script.

Here's another barely-tested BASH script that:

* searches FIND_PATH for all ZIP files
* looks inside each ZIP file (unzip -l) to find a filename match with the command-line argument
* If it finds one, extracts the archive to a folder in TMP_PATH, moves the matched file to a .old filename, copies in the new one from REPLACE_PATH, and ZIPs it back up to the original archive location

#!/bin/bash FIND_PATH="/home/legion/Downloads" REPLACE_PATH="/home/legion/replace" TMP_PATH="/tmp" echo "Edwin's Big Fat Replace-it-in-a-ZIP Script" echo "Searching for:" $1 find "$FIND_PATH" -name "*.zip" -type f | while read FILE do RESULT=`unzip -l "$FILE" | awk '{print $4}' | grep -w "$1"` if [ ${#RESULT} -gt 0 ]; then echo "Found" "$1" "in" "$FILE" "(""$RESULT"")" mkdir "$TMP_PATH/$(basename "$FILE")" unzip -o "$FILE" -d "$TMP_PATH/$(basename "$FILE")" mv "$TMP_PATH/$(basename "$FILE")/$1" "$TMP_PATH/$(basename "$FILE")/$1.old" cp "$REPLACE_PATH"/"$1" "$TMP_PATH/$(basename "$FILE")/" zip -j "$FILE" "$TMP_PATH"/$(basename "$FILE")/* fi done

And a smaller one that does only the search, no file replacing (for quickly scanning those archives to find a file)

#!/bin/bash FIND_PATH="/home/legion/Downloads" echo "Edwin's Big Fat Find-it-in-a-ZIP Script" echo "Searching for:" $1 find "$FIND_PATH" -name "*.zip" -type f | while read FILE do RESULT=`unzip -l "$FILE" | awk '{print $4}' | grep -w "$1"` if [ ${#RESULT} -gt 0 ]; then echo "Found" "$1" "in" "$FILE" "(""$RESULT"")" fi done

Again, barely tested, don't promise it won't rm -rf /, but something you can poke at.

I forgot to change my testing paths to the cygdrive paths, but I think you can handle that.

The only thing I would recommend changing about *legion*'s scripts would be to use -iname instead of -name as an option to find. It won't really matter on windows, since the filesystem is case insensitive, but you could miss stuff on a *nix box.

Tyrian wrote:

The only thing I would recommend changing about *legion*'s scripts would be to use -iname instead of -name as an option to find. It won't really matter on windows, since the filesystem is case insensitive, but you could miss stuff on a *nix box.

Good call.

*Legion* wrote:
Tyrian wrote:

The only thing I would recommend changing about *legion*'s scripts would be to use -iname instead of -name as an option to find. It won't really matter on windows, since the filesystem is case insensitive, but you could miss stuff on a *nix box.

Good call.

I love the find command. find $path -$modfiers -exec {} \; = win on so many levels. I've done some really awful things with that command as a base (shell one liners rule!). I was even more pleased with it once I discovered it had the -ioptions.

As I have said many times, you can very nearly bring about world peace from the Unix command line.

Malor wrote:

As I have said many times, you can very nearly bring about world peace from the Unix command line.

Only if your shell was compiled statically. As soon as you start relying on shared objects, all bets are off!

Hell, the unix command line has started plenty of wars in and of itself (shell purists, emacs vs vi[m], startx vs xinit)!

oh, uh.. finding stuff. replacing! On track, truly!

Tyrian wrote:

Hell, the unix command line has started plenty of wars in and of itself (shell purists, emacs vs vi[m], startx vs xinit)

Yeah, but those are just wars. The emacs heathens need to be snuffed out.

*Legion* wrote:
Tyrian wrote:

Hell, the unix command line has started plenty of wars in and of itself (shell purists, emacs vs vi[m], startx vs xinit)

Yeah, but those are just wars. The emacs heathens need to be snuffed out.

VIMer REPRESENT!

Tyrian wrote:

VIMer REPRESENT!

Your avatar dude looks like he's the one shouting that.

Recently I wanted to write a script that was dead simple in Linux, but couldn't even begin to figure out how to do it in Windows. But I wanted to learn how. And I didn't feel like installing Cygwin. Then I found this:

An A-Z Index of the Windows XP command line
http://ss64.com/nt/

It's not quite the same set of commands, but it's a lot better than having nothing or knowing nothing.

Yeah, Windows scripting isn't worthless, it's just not as strong as Bash shell scripting... plus, of course, the vast array of tiny Unix utilities that do just one thing extremely well.

The new Powershell looks pretty good. It's verbose, but looks quite powerful. It won't have anywhere near the same tool library, but it looks a HECK of a lot better than the old DOS-style scripting.

*Legion* wrote:
Tyrian wrote:

VIMer REPRESENT!

Your avatar dude looks like he's the one shouting that. :)

Who says he isn't?