Archive: Recursive File/Directory Operations


Recursive File/Directory Operations
Guys. I've been looking at this stuff for a while now, and getting all tangled up in knots, hope someone can help me through this one a bit :)

I need to move the contents of a directory (within which there are directories and files, each of which can contain directories and/or files, etc). I need to not move it en masse, but one at a time, as I need to perform an extra operation on each file that I move.

I need to retain the folder structure as well. Currently I have this, which a) doesn't work properly, and b) doesn't retain the folder structure:


Push $1
Push $8
Push $9
StrCpy $9 "$INSTDIR\scripts-temp\"

startSearch:
FindFirst $0 $1 "$9\*"
StrCpy $8 "0"
loop:
StrCmp $1 "" dirdone
IfFileExists "$9\$1\*.*" IsDir NotDir
IsDir:
StrCmp $1 "." bleh
StrCmp $1 ".." bleh
Push "$9$1\"
IntOp $8 $8 + "1"
Goto bleh
NotDir:
!insertmacro WRITE_TO_MAIN_LOG "Copying file $9$1 to $INSTDIR\scripts\" "**"

!insertmacro COPY_FILE $9$1 "$INSTDIR\scripts\"
bleh:
FindNext $0 $1
Goto loop
dirdone:
IntCmp $8 "0" done
IntOp $8 $8 - 1
Pop $9
Goto startSearch

done:
Pop $9
Pop $8
Pop $1


For completeness, here is the COPY_FILE macro:


!macro COPY_FILE OLD_FILEPATH NEW_FILEPATH
ClearErrors
Rename "${OLD_FILEPATH}" "${NEW_FILEPATH}"
IfErrors +4
!insertmacro WRITE_TO_MAIN_LOG "Wrote file ${NEW_FILEPATH}" "**"
!insertmacro WRITE_TO_FILELIST_LOG "${NEW_FILEPATH}"
Goto +2
!insertmacro WRITE_TO_MAIN_LOG "Unable to copy file '${OLD_FILEPATH}' to '${NEW_FILEPATH}'" "EE"
!macroend


Thanks guys, sorry to lumber you with a nasty one like this :)

-rob-

I have a function which I've been using over many installers in the past which you can use. You'll have to wait till I get home though because the function isn't on the Archive.

-Stu


Brilliant :) Thanks.


I'm getting there, just a quick bugfix and it copies it properly, but again without recreating the directory structure:


Push $1
Push $8
Push $9
StrCpy $9 "$INSTDIR\scripts-temp\"

StrCpy $8 "0"
startSearch:
FindFirst $0 $1 "$9\*"
loop:
StrCmp $1 "" dirdone
IfFileExists "$9\$1\*.*" IsDir NotDir
IsDir:
StrCmp $1 "." bleh
StrCmp $1 ".." bleh
Push "$9$1\"
IntOp $8 $8 + 1
Goto bleh
NotDir:
!insertmacro COPY_FILE $9$1 "$INSTDIR\scripts\$1"
bleh:
FindNext $0 $1
Goto loop
dirdone:
IntCmp $8 "0" done
IntOp $8 $8 - 1
Pop $9
Goto startSearch

done:
Pop $9
Pop $8
Pop $1

StrCpy $DirPath "C:\blah"
StrCpy $Location "D:\blah"
Function GetFiles
Push $R1
Push $R2
Push $R3
Push $R4

StrCpy $R3 1

Push ""

nextDir:
Pop $R4
IntOp $R3 $R3 - 1
ClearErrors

FindFirst $R1 $R2 "$DirPath$R4\*.*"
checkLoop:
IfFileExists "$DirPath$R4\$R2\*.*" +3

### Create directory
CreateDirectory "$Location$R4"

Goto checkDone
FindNext $R1 $R2
IfErrors 0 checkLoop
checkDone:
FindClose $R1

FindFirst $R1 $R2 "$DirPath$R4\*.*"

nextFile:
StrCmp $R2 "." gotoNextFile
StrCmp $R2 ".." gotoNextFile

IfFileExists "$DirPath$R4\$R2\*.*" 0 notDir
IntOp $R3 $R3 + 1
Push "$R4\$R2"
Goto gotoNextFile

notDir:

### Copy a file
CopyFiles "$DirPath$R4\$R2" "$Location$R2"

gotoNextFile:
FindNext $R1 $R2
IfErrors 0 nextFile

FindClose $R1

StrCmp $R3 0 0 nextDir

Pop $R4
Pop $R3
Pop $R2
Pop $R1
FunctionEnd


You just need to adapt it now.

-Stu

Function Locate v1.2
http://nsis.sourceforge.net/archive/...34&instances=0


Function Locate
;.....
FunctionEnd

Section
StrCpy $R0 "C:\CM" ;Directory copy from
StrCpy $R1 "C:\CM2" ;Directory copy into
StrLen $R2 $R1

!insertmacro Locate "$R0" "/L=F" "CallBack"

IfErrors 0 +2
MessageBox MB_OK 'error'
SectionEnd

Function CallBack
StrCpy $1 $R8 '' $R2
IfFileExists '$R1\$1\*.*' +2
CreateDirectory '$R1\$1'
CopyFiles /SILENT $R9 '$R1\$1'

;Your code ...
;$R9 "path\name" (source)
;$R8 "path" (source)
;$R1\$1 "path" (destination)
;$R7 "name"

Push $0
FunctionEnd

Instructor, Afrow, cheers guys much appreciated.

One question - in the Locate Callback, I want to be able to just copy the directories first, so I can log the file copying afterwards. If I comment out this line:


CopyFiles /SILENT $R9 '$R1\$1'


...then it doesn't make all of the directory structure, but it DOES make some of it!

Any reason? CopyFiles docs don't seem to really say whether or not the function will create directories as well as files...

Cheers

-rob-


Function Locate
;.....
FunctionEnd

Section
StrCpy $R0 "C:\CM" ;Directory copy from
StrCpy $R1 "C:\CM2" ;Directory copy into
StrLen $R2 $R1

GetTempFileName $0
FileOpen $R3 $0 w
!insertmacro Locate "$R0" "/L=F" "CallBack"
FileClose $R4

IfErrors 0 +2
MessageBox MB_OK 'error'

Exec '"notepad.exe" "$0"' ;view log
SectionEnd

Function CallBack
StrCpy $1 $R8 '' $R2
StrCmp $1 '' +2
StrCpy $1 '\$1'
IfFileExists '$R1\$1\*.*' +2
CreateDirectory '$R1$1'
CopyFiles /SILENT $R9 '$R1$1'

;Your code ...
;$R9 "path\name" (source)
;$R8 "path" (source)
;$R1$1 "path" (destination)
;$R7 "name"

IfFileExists '$R1$1\$R7' 0 +3
FileWrite $R3 "-old:$R9 -new:$R1$1\$R7 -success$\r$\n"
goto +2
FileWrite $R3 "-old:$R9 -new:$R1$1\$R7 -failed$\r$\n"

Push $0
FunctionEnd

Is that FileClose $R4 meant to be $R3? I'm not being funny but this isn't working properly either way so it could well be right...


Yes it should be.

-Stu


Instructor - I tried to get that to work but it was weird, the $0 variable seemed to be very temperamental.

Not sure what the problem was, so I went back to the solution I posted.

Thanks anyway both of you, much appreciated!

-rob-

P.S. if anyone has helpful comments for other people who need to do this feel free to post...


Sorry, I had little time for answer yesterday and I have done the pair of absurd mistakes.

Copy files with log:


Section
StrCpy $R0 "C:\CM" ;Directory copy from
StrCpy $R1 "C:\CM2" ;Directory copy into
StrLen $R2 $R0

GetTempFileName $0
FileOpen $R3 $0 w
!insertmacro Locate "$R0" "/L=FDE" "CallBack"
FileClose $R3

IfErrors 0 +2
MessageBox MB_OK 'error'

Exec '"notepad.exe" "$0"' ;view log
SectionEnd

Function CallBack
StrCpy $1 $R8 '' $R2

StrCmp $R6 '' 0 +3
CreateDirectory '$R1$1\$R7'
goto end
CreateDirectory '$R1$1'
CopyFiles /SILENT $R9 '$R1$1'

IfFileExists '$R1$1\$R7' 0 +3
FileWrite $R3 "-old:$R9 -new:$R1$1\$R7 -success$\r$\n"
goto +2
FileWrite $R3 "-old:$R9 -new:$R1$1\$R7 -failed$\r$\n"

end:
Push $0
FunctionEnd



If you want recreate directory structure:

Section
StrCpy $R0 "C:\CM" ;Directory structure from
StrCpy $R1 "C:\CM2" ;Directory structure into
StrLen $R2 $R0

!insertmacro Locate "$R0" "/L=D" "CallBack"

IfErrors 0 +2
MessageBox MB_OK 'error'
SectionEnd

Function CallBack
StrCpy $1 $R9 '' $R2
StrCmp $1 '' +2
CreateDirectory '$R1$1'

Push $0
FunctionEnd

Instructor - thanks so much that was exactly what I needed. Here's the code just for reference purposes (the 2 logging macros aren't necessary) - it assumes that the original directory is $INSTDIR\<var>-temp and the new one is $INSTDIR\<var>, and that you want to delete the temp directory afterwards. Just Push the <var> having created the zip file and the 2 directories and go for it!


Function unzipFiles
Exch $R9
CreateDirectory "$INSTDIR\$R9"
NsExec::Exec '"unzip.exe" -o $R9.zip'

StrCpy $R0 "$INSTDIR\$R9-temp" ;Directory structure from
StrCpy $R1 "$INSTDIR\$R9" ;Directory structure into
StrLen $R2 $R0
StrCpy $0 $FILE_LOG_TEMP_FILELIST
FileOpen $R3 $0 a
FileSeek $R3 "0" END
ClearErrors
!insertmacro Locate "$R0" "/L=D" "CallBack-Directory"

IfErrors 0 noscriptsdirerrors
!insertmacro WRITE_TO_MAIN_LOG "Error writing directories within $R9\ directory" "EE"
Goto writeScriptsFiles

noscriptsdirerrors:
!insertmacro WRITE_TO_MAIN_LOG "Finished writing directories within $R9\ directory" "**"

writeScriptsFiles:
ClearErrors
!insertmacro Locate "$R0" "/L=FDE" "CallBack"
FileClose $R3

IfErrors 0 uzEnd
!insertmacro WRITE_TO_MAIN_LOG "Error writing files within $R9\ directory" "EE"

uzEnd:
SetOutPath "$INSTDIR"
RMDir /r "$INSTDIR\$R9-temp"
Exch $R9
FunctionEnd

Function CallBack
StrCpy $1 $R8 '' $R2

StrCmp $R6 '' 0 +3
CreateDirectory '$R1$1\$R7'
goto end
CreateDirectory '$R1$1'
CopyFiles /SILENT $R9 '$R1$1'

IfFileExists '$R1$1\$R7' 0 c-errors
FileWrite $R3 "$R1$1\$R7$\r$\n"
!insertmacro WRITE_TO_MAIN_LOG "Wrote file $R1$1\$R7" "**"
goto end

c-errors:
!insertmacro WRITE_TO_MAIN_LOG "Failed to write $R1$1\$R7" "EE"

end:
Push $0
FunctionEnd

Function CallBack-Directory
StrCpy $1 $R9 '' $R2
StrCmp $1 '' +2

ClearErrors
CreateDirectory '$R1$1'
IfErrors c-dir-errors
FileWrite $R3 '$R1$1$\r$\n'
!insertmacro WRITE_TO_MAIN_LOG "Created directory $R1$1\" "**"
Goto end

c-dir-errors:
!insertmacro WRITE_TO_MAIN_LOG "Failed to create directory $R1$1\" "EE"

end:
Push $0
FunctionEnd

While we're on the subject of Locate, I thought I'd have a go at listing all Windows users of a computer, by listing the names of the directories which are immediate children of the C:\Documents and Settings folder (it's a Win2k or above installer so that's fine, AFAIK).

To do that I do this:


Function
...
Push "$PROFILE"
Call GetParent
Pop $R0
!insertmacro Locate "$R0" "/L=D /G=0" "findusers"
...
FunctionEnd

Function "findusers"
MessageBox MB_OK "$R7"
FunctionEnd


Rather than giving me a nice sequence of messageboxes teling me user names, it crashes after the first one (Administrator in my case). Any ideas why?


Function "findusers"
MessageBox MB_OK "$R7"

Push $var ; If $var=StopLocate Then exit from function
FunctionEnd

Afrow UK's example seems to be quite good short and easy, but the problem is that it does not complitelly support wildcards. suppose i need to find *.txt instead of *.*
in this case it only copyes files from $Location, but not from its subdirectories. And in case you only have $Location\subdir\file.txt it does not copy anything.

does anybody have some ideas how to make it work with wildcards also?

one thing should be different:
original line:
### Copy a file
CopyFiles "$DirPath$R4\$R2" "$Location$R2"
should be replaced with
CopyFiles "$DirPath$R4\$R2" "$Location$R4\$R2"

..but still very good script(y)


great, was just about to ask about such a function :)