- NSIS Discussion
 - Recursive File/Directory Operations
 
Archive: Recursive File/Directory Operations
RobGrant
7th December 2004 12:34 UTC
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-
    
 
    
    
      Afrow UK
      7th December 2004 13:49 UTC
      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
     
    
    
      RobGrant
      7th December 2004 13:56 UTC
      Brilliant :) Thanks.
     
    
    
      RobGrant
      7th December 2004 14:08 UTC
      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
     
    
    
      Afrow UK
      7th December 2004 17:28 UTC
      
      
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
    
 
    
    
      Instructor
      8th December 2004 08:52 UTC
      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
     
    
    
      RobGrant
      8th December 2004 10:02 UTC
      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-
    
 
    
    
      Instructor
      8th December 2004 10:35 UTC
      
      
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
     
    
    
      RobGrant
      8th December 2004 11:43 UTC
      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...
     
    
    
      Afrow UK
      8th December 2004 13:13 UTC
      Yes it should be.
      
      -Stu
     
    
    
      RobGrant
      8th December 2004 14:12 UTC
      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...
     
    
    
      Instructor
      9th December 2004 08:07 UTC
      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
     
    
    
      RobGrant
      9th December 2004 10:53 UTC
      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
     
    
    
      RobGrant
      10th December 2004 12:32 UTC
      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?
    
 
    
    
      Instructor
      11th December 2004 10:43 UTC
      
      
Function "findusers"
        MessageBox MB_OK "$R7"
        Push $var     ; If $var=StopLocate Then exit from function
FunctionEnd
     
    
    
      atsuk
      12th December 2004 16:08 UTC
      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)
     
    
    
      Yathosho
      12th December 2004 16:30 UTC
      great, was just about to ask about such a function :)