Talk:Uninstall only installed files: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
(File /r and uninstall only installed files)
 
No edit summary
Line 1: Line 1:
The solution presented here to uninstall only the files we installed works fine. There is only one shortcoming: the list of files has to be known beforehand. This solution actually overloads ''File'' in a way that denies the ''recursive'' switch. Meaning that the created installation log is only useful if you don’t grab all the files from one source directory in your installer: '''File /r <my_dir>'''
I searched a lot for a way to combine these 2 features using the NSIS script and I finally gave up. I wrote a small Python script that generates a pair of lists, to be included in a pair of sections (install and uninstall) in your .nsi script. I post hereafter the python scrip. Here is the small .bat that you would use to call in sequence that script and the NSIS build.
python gen_list_files_for_nsis.py %LOCATION_SOURCES%  install_list.nsh  uninstall_list.nsh
"C:\Program Files\NSIS\makensis.exe" /DINST_LIST=install_list.nsh  /DUNINST_LIST=uninstall_list.nsh      my_product.nsi
== The Script ==


The solution presented here to uninstall only the files we installed works fine. There is only one shortcoming: the list of files has to be known beforehand. This solution actually overloads ''File'' in a way that denies the ''recursive'' switch. Meaning that the created installation log is only useful if you don’t grab all the files from one source directory in your installer: '''File /r <my_dir>'''
<highlight-python>
"""
This script generates 2 lists of NSIS commands (install&uninstall)
for all files in a given directory
 
Usage:
    gen_list_files_for_nsis.py  <dir src> <inst list> <uninst list>
Where
    <dir src>      :  dir with sources; must exist
    <inst list>    :  list of files to install (NSIS syntax)
    <uninst list>  :   list of files to uninstall (NSIS syntax)
                        (both these will be overwriten each time)
"""
import sys, os, glob
 
# global settings
just_print_flag = 0 # turn to 1 for debugging
 
# templates for the output
inst_dir_tpl  = '  SetOutPath $INSTDIR%s'
inst_file_tpl = ' File ${FILES_SOURCE_PATH}%s'
uninst_file_tpl = ' Delete $INSTDIR%s'
uninst_dir_tpl  = ' RMDir $INSTDIR%s'
 
# check args
if len(sys.argv) != 4:
    print __doc__
    sys.exit(1)
source_dir = sys.argv[1]
if not os.path.isdir(source_dir):
    print __doc__
    sys.exit(1)
 
def open_file_for_writting(filename):
    "return a handle to the file to write to"
    try:
        h = file(filename, "w")
    except:
        print "Problem opening file %s for writting"%filename
        print __doc__
        sys.exit(1)
    return h
 
inst_list = sys.argv[2]
uninst_list = sys.argv[3]
if not just_print_flag:
    ih= open_file_for_writting(inst_list)
    uh= open_file_for_writting(uninst_list)
 
stack_of_visited = []
counter_files = 0
counter_dirs = 0
print "Generating the install & uninstall list of files"
print "  for directory", source_dir
print >> ih,  "  ; Files to install\n"
print >> uh,  "  ; Files and dirs to remove\n"
 
# man page of walk() in Python 2.2  (the new one in 2.4 is easier to use)
 
# os.path.walk(path, visit, arg)
    #~ Calls the function visit with arguments (arg, dirname, names) for each directory
    #~ in the directory tree rooted at path (including path itself, if it is a directory).
    #~ The argument dirname specifies the visited directory, the argument names lists
    #~ the files in the directory (gotten from os.listdir(dirname)). The visit function
    #~ may modify names to influence the set of directories visited below dirname,
    #~ e.g., to avoid visiting certain parts of the tree. (The object referred to by names
    #~ must be modified in place, using del or slice assignment.)
 
def my_visitor(my_stack, cur_dir, files_and_dirs):
    "add files to the install list and accumulate files for the uninstall list"
    global counter_dirs, counter_files, stack_of_visited
    counter_dirs += 1
   
    if just_print_flag:
        print "here", my_dir
        return
   
    # first separate files
    my_files = [x for x in files_and_dirs if os.path.isfile(cur_dir+os.sep+x)]
    # and truncate dir name
    my_dir = cur_dir[len(source_dir):]
    if my_dir=="": my_dir = "\\."
   
    # save it for uninstall
    stack_of_visited.append( (my_files, my_dir) )
   
    # build install list
    if len(my_files):
        print >> ih,  inst_dir_tpl % my_dir
        for f in my_files:
            print >> ih,  inst_file_tpl % my_dir+os.sep+f
            counter_files += 1
        print >> ih, "  "
   
os.path.walk( source_dir, my_visitor,  stack_of_visited)
ih.close()
print "Install list done"
print "  ", counter_files, "files in", counter_dirs, "dirs"
 
stack_of_visited.reverse()
# Now build the uninstall list
for (my_files, my_dir) in stack_of_visited:
        for f in my_files:
            print >> uh,  uninst_file_tpl % my_dir+os.sep+f
        print >> uh, uninst_dir_tpl % my_dir
        print >> uh, "  "
 
# now close everything
uh.close()
print "Uninstall list done. Got to end.\n"
</highlight-python>
 
And your NSIS script would look something like this:
<highlight-nsis>
;--------------------------------
;
;  the stuff to install
;
;--------------------------------
 
Section "" ; No components page, name is not important
 
  !include  ${INST_LIST} ; the payload of this installer is described in an externally generated list of files
 
  ;File /r "${FILES_SOURCE_PATH}\*.*" ; not OK because with /r we can not log what was installed
                                      ; and without logging we cannot uninstall only the files installed by us
 
SectionEnd
 
;--------------------------------
;
;  uninstaller
;
;--------------------------------
 
Section "Uninstall"
 
  ; Remove the files (using externally generated file list)
  !include ${UNINST_LIST} 
 
  ; Remove uninstaller
  Delete $INSTDIR\uninst*.exe
 
  RMDir $INSTDIR ; this is safe; it's not forced if you still have private files there.
 
  ; Important note: RMDir /r "$INSTDIR"  ; is NOT OK!
  ; if the user installed in "C:\Program Files" by mistake, then we totally screw up his machine!
 
SectionEnd ; Uninstall


I searched a lot for a way to combine these 2 features using the NSIS script and I finally gave up. I wrote a small Python script that generates a pair of lists, to be included in a pair of sections (install and uninstall) in your .nsi script. I post here the python script and the small .bat that is needed to call in sequence that script and the NSIS build.
</highlight-nsis>

Revision as of 18:29, 21 April 2006

The solution presented here to uninstall only the files we installed works fine. There is only one shortcoming: the list of files has to be known beforehand. This solution actually overloads File in a way that denies the recursive switch. Meaning that the created installation log is only useful if you don’t grab all the files from one source directory in your installer: File /r <my_dir>

I searched a lot for a way to combine these 2 features using the NSIS script and I finally gave up. I wrote a small Python script that generates a pair of lists, to be included in a pair of sections (install and uninstall) in your .nsi script. I post hereafter the python scrip. Here is the small .bat that you would use to call in sequence that script and the NSIS build.

python gen_list_files_for_nsis.py %LOCATION_SOURCES% install_list.nsh uninstall_list.nsh

"C:\Program Files\NSIS\makensis.exe" /DINST_LIST=install_list.nsh /DUNINST_LIST=uninstall_list.nsh my_product.nsi


The Script

"""
This script generates 2 lists of NSIS commands (install&uninstall)
for all files in a given directory
 
Usage:
    gen_list_files_for_nsis.py  <dir src> <inst list> <uninst list>
Where
    <dir src>       :   dir with sources; must exist
    <inst list>     :   list of files to install (NSIS syntax)
    <uninst list>   :   list of files to uninstall (NSIS syntax)
                        (both these will be overwriten each time)
"""
import sys, os, glob
 
# global settings
just_print_flag = 0 # turn to 1 for debugging
 
# templates for the output
inst_dir_tpl  = '  SetOutPath $INSTDIR%s'
inst_file_tpl = '  File ${FILES_SOURCE_PATH}%s'
uninst_file_tpl = '  Delete $INSTDIR%s'
uninst_dir_tpl  = '  RMDir $INSTDIR%s'
 
# check args
if len(sys.argv) != 4:
    print __doc__
    sys.exit(1)
source_dir = sys.argv[1]
if not os.path.isdir(source_dir):
    print __doc__
    sys.exit(1)
 
def open_file_for_writting(filename):
    "return a handle to the file to write to"
    try:
        h = file(filename, "w")
    except:
        print "Problem opening file %s for writting"%filename
        print __doc__
        sys.exit(1)
    return h
 
inst_list = sys.argv[2]
uninst_list = sys.argv[3]
if not just_print_flag:
    ih= open_file_for_writting(inst_list)
    uh= open_file_for_writting(uninst_list)
 
stack_of_visited = []
counter_files = 0
counter_dirs = 0
print "Generating the install & uninstall list of files"
print "  for directory", source_dir
print >> ih,  "  ; Files to install\n"
print >> uh,  "  ; Files and dirs to remove\n"
 
# man page of walk() in Python 2.2  (the new one in 2.4 is easier to use)
 
# os.path.walk(path, visit, arg) 
    #~ Calls the function visit with arguments (arg, dirname, names) for each directory 
    #~ in the directory tree rooted at path (including path itself, if it is a directory). 
    #~ The argument dirname specifies the visited directory, the argument names lists 
    #~ the files in the directory (gotten from os.listdir(dirname)). The visit function 
    #~ may modify names to influence the set of directories visited below dirname, 
    #~ e.g., to avoid visiting certain parts of the tree. (The object referred to by names 
    #~ must be modified in place, using del or slice assignment.) 
 
def my_visitor(my_stack, cur_dir, files_and_dirs):
    "add files to the install list and accumulate files for the uninstall list"
    global counter_dirs, counter_files, stack_of_visited
    counter_dirs += 1
 
    if just_print_flag:
        print "here", my_dir
        return
 
    # first separate files
    my_files = [x for x in files_and_dirs if os.path.isfile(cur_dir+os.sep+x)]
    # and truncate dir name
    my_dir = cur_dir[len(source_dir):]
    if my_dir=="": my_dir = "\\."
 
    # save it for uninstall
    stack_of_visited.append( (my_files, my_dir) )
 
    # build install list
    if len(my_files):
        print >> ih,  inst_dir_tpl % my_dir
        for f in my_files:
            print >> ih,  inst_file_tpl % my_dir+os.sep+f
            counter_files += 1
        print >> ih, "  "
 
os.path.walk( source_dir, my_visitor,  stack_of_visited)
ih.close()
print "Install list done"
print "  ", counter_files, "files in", counter_dirs, "dirs"
 
stack_of_visited.reverse()
# Now build the uninstall list
for (my_files, my_dir) in stack_of_visited:
        for f in my_files:
            print >> uh,  uninst_file_tpl % my_dir+os.sep+f
        print >> uh, uninst_dir_tpl % my_dir
        print >> uh, "  "
 
# now close everything
uh.close()
print "Uninstall list done. Got to end.\n"

And your NSIS script would look something like this:

;--------------------------------
;
;  the stuff to install
;
;--------------------------------
 
Section "" ; No components page, name is not important
 
  !include  ${INST_LIST} ; the payload of this installer is described in an externally generated list of files
 
  ;File /r "${FILES_SOURCE_PATH}\*.*" ; not OK because with /r we can not log what was installed
                                      ; and without logging we cannot uninstall only the files installed by us
 
SectionEnd
 
;--------------------------------
;
;  uninstaller 
;
;--------------------------------
 
Section "Uninstall"
 
  ; Remove the files (using externally generated file list)
  !include ${UNINST_LIST}  
 
  ; Remove uninstaller
  Delete $INSTDIR\uninst*.exe
 
  RMDir $INSTDIR ; this is safe; it's not forced if you still have private files there.
 
  ; Important note: RMDir /r "$INSTDIR"  ; is NOT OK!
  ; if the user installed in "C:\Program Files" by mistake, then we totally screw up his machine!
 
SectionEnd ; Uninstall