Talk:Uninstall only installed files: Difference between revisions
(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 == | |||
<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 | |||
</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