-1

My software has some user modifiable files which I don't want to overwrite during re-installation if modified.

I decided to use archive bit to signal that the destination file is modified or not by the user. During fresh install all archive bit of all related files will be set to OFF.

archive OFF: means that this file is unmodified therefore can be replaced with a recent version archive ON: means that this file is modified by the user and should not be overwritten by the installer even if there is a newer version.

Note: as you know, by design, editing and saving a file sets the archive bit ON.

I even thought to use robocopy within NSIS script, however exclusion parameters of robocopy is related to source files not to destination files as far as I can see. I used the code below:

    robocopy c:\source c:\target /XA:A

Can you kindly give clues to implement such a feature.

  • Are you using `File /r` or one `File` instruction per file? – Anders Feb 04 '19 at 23:38
  • 1
    Welcome to Stackoverflow. Please have a look at https://stackoverflow.com/help/how-to-ask. Share the relevant code snippets you have tried so far. – Nagama Inamdar Feb 05 '19 at 06:01
  • @Anders I'd prefer [File /r] since there are hundreds of such files recursively. – Silin Misler Feb 05 '19 at 06:50
  • You have hundreds of user modifiable files or hundreds of files in total? I don't know if it is possible for `/r` to handle the attribute flags. – Anders Feb 05 '19 at 19:51

1 Answers1

0

I don't think it is possible to do this with File /r.

You can do it when you extract each file manually:

!include LogicLib.nsh

!define /IfNDef FILE_ATTRIBUTE_ARCHIVE 0x20
!define /IfNDef INVALID_FILE_ATTRIBUTES 0xffffffff
!define File_NoArchiveOverwrite "!insertmacro File_NoArchiveOverwrite "
!macro File_NoArchiveOverwrite sourcefile destinationfile
    Push $1
    System::Call 'KERNEL32::GetFileAttributes(ts)i.r1' "${destinationfile}"
    ${IfThen} $1 = ${INVALID_FILE_ATTRIBUTES} ${|} StrCpy $1 0 ${|} ; File does not exist? Make sure "it" has no attributes.
    IntOp $1 $1 & ${FILE_ATTRIBUTE_ARCHIVE}
    ${If} $1 = 0 ; Archive not set?
        SetOverwrite on
        File "/oname=${destinationfile}" "${sourcefile}"
        SetOverwrite lastused
    ${EndIf}
    Pop $1
!macroend


Section "Example preparation"
InitPluginsDir 
StrCpy $InstDir $PluginsDir ; Just a hack for this example

; Prepare some test files for this example:
File "/oname=$InstDir\test1.ini" "${__FILE__}"
SetFileAttributes "$InstDir\test1.ini" ARCHIVE
File "/oname=$InstDir\test2.ini" "${__FILE__}"
SetFileAttributes "$InstDir\test2.ini" NORMAL
SectionEnd

Section "Real code"
SetOutPath $InstDir
#File /r /x "*.ini" c:\myfiles\*.* ; Exclude .ini files because we handle them manually

${File_NoArchiveOverwrite} "${__FILE__}" "$InstDir\test1.ini" ; Not extracted because the dummy test1.ini file has ARCHIVE set
${File_NoArchiveOverwrite} "${__FILE__}" "$InstDir\test2.ini" ; This will overwrite the test2.ini dummy file.
${File_NoArchiveOverwrite} "${__FILE__}" "$InstDir\test3.ini" ; This file does not exist so we will extract a new file

SectionEnd

If you have too many files to manually create the ${File_NoArchiveOverwrite} calls then you can use !system to call a batch file (or any other script or application) that generates a .NSH file with the ${File_NoArchiveOverwrite} calls and then you can !include this file:

Section
SetOutPath $InstDir
!tempfile FILELIST
!system 'generatefilecommands.cmd "${FILELIST}"'
!include "${FILELIST}"
!delfile "${FILELIST}"
!undef FILELIST
SectionEnd

This assumes that generatefilecommands.cmd is a file you write and it might look something like this:

@ECHO OFF&SETLOCAL ENABLEEXTENSIONS DISABLEDELAYEDEXPANSION
FOR %%A IN (*.ini) DO (
    ECHO >> "%~1" ${File_NoArchiveOverwrite} "%%~fA" "$InstDir\%%~A"
)
Anders
  • 97,548
  • 12
  • 110
  • 164