Dynamically create and delete directories

From NSIS Wiki
Jump to navigationJump to search
Author: StB (talk, contrib)


Description

These macros allow you to remove exactly those directories in the uninstaller which were created by the installer. So already existing directories (and non-empty ones) won't be removed on uninstall.

Provided "as is" by stefan.bertels.org. Use on your own risk. Please report any bugs here.

The Script

; ################################################################
; appends \ to the path if missing
; example: !insertmacro GetCleanDir "c:\blabla"
; Pop $0 => "c:\blabla\"
!macro GetCleanDir INPUTDIR
  ; ATTENTION: USE ON YOUR OWN RISK!
  ; Please report bugs here: http://stefan.bertels.org/
  !define Index_GetCleanDir 'GetCleanDir_Line${__LINE__}'
  Push $R0
  Push $R1
  StrCpy $R0 "${INPUTDIR}"
  StrCmp $R0 "" ${Index_GetCleanDir}-finish
  StrCpy $R1 "$R0" "" -1
  StrCmp "$R1" "\" ${Index_GetCleanDir}-finish
  StrCpy $R0 "$R0\"
${Index_GetCleanDir}-finish:
  Pop $R1
  Exch $R0
  !undef Index_GetCleanDir
!macroend
 
; ################################################################
; appends \ to the path if missing and returns parent directory
; example: !insertmacro GetCleanDir "c:\blabla\subdir"
; Pop $0 => "c:\blabla\"
!macro GetCleanParentDir INPUTDIR
  ; ATTENTION: USE ON YOUR OWN RISK!
  ; Please report bugs here: http://stefan.bertels.org/
  !define Index_GetCleanParentDir 'GetCleanParentDir_Line${__LINE__}'
  Push $R0
  Push $R1
  Push $R2
  !insertmacro GetCleanDir "${INPUTDIR}"
  Pop $R0
  StrCpy $R1 "$R0"
${Index_GetCleanParentDir}-loop:
  StrCmp "$R1" "" ${Index_GetCleanParentDir}-finish
  StrCpy $R1 "$R1" -1
  StrCpy $R2 "$R1" "" -1
  StrCmp "$R2" "\" 0 ${Index_GetCleanParentDir}-loop
  StrCpy $R0 "$R1"
${Index_GetCleanParentDir}-finish:
  Pop $R2
  Pop $R1
  Exch $R0
  !undef Index_GetCleanParentDir
!macroend
 
; ################################################################
; split "c:\test" into "c:" and "\test"
; split "\\server\share\test\test" into "\\server\share" and "\test\test"
; split other patterns into "" and "PATH"
; the two parts will be pushed on the stack
; get parts with Pop $drive and Pop $folder
!macro SplitPath PATH
  ; ATTENTION: USE ON YOUR OWN RISK!
  ; Please report bugs here: http://stefan.bertels.org/
  !define Index_SplitPath 'SplitPath_${__LINE__}'
  Push $R0
  StrCpy $R0 "${PATH}" ; $R0 contains PATH
  Push $R1
  Push $R2 ; number of the first "\" of folder part
  Push $R3
  Push $R4
 
  ; check for path type (c:\test or \\server\share\test)
  StrCpy $R2 $R0 2 0
  StrCmp $R2 "\\" 0 ${Index_SplitPath}-nounc
  StrCpy $R2 3
  StrLen $R1 $R0
  StrCpy $R4 -1
${Index_SplitPath}-loop:
  IntOp $R4 $R4 + 1
  IntCmp $R4 $R1 ${Index_SplitPath}-end
  StrCpy $R3 $R0 1 $R4
  StrCmp $R3 "\" 0 ${Index_SplitPath}-loop
  IntCmp $R2 0 ${Index_SplitPath}-split
  IntOp $R2 $R2 - 1
  Goto ${Index_SplitPath}-loop
${Index_SplitPath}-split:
  StrCpy $R1 $R0 "" $R4
  StrCpy $R0 $R0 $R4
  Goto ${Index_SplitPath}-finish
${Index_SplitPath}-end:
  StrCpy $R1 ""
  Goto ${Index_SplitPath}-finish
 
${Index_SplitPath}-nounc:
  StrCpy $R2 $R0 1 1
  StrCmp $R2 ":" 0 ${Index_SplitPath}-fallback
  StrCpy $R1 $R0 "" 2
  StrCpy $R0 $R0 2
  Goto ${Index_SplitPath}-finish
 
${Index_SplitPath}-fallback:
  StrCpy $R1 $R0
  StrCpy $R0 ""
${Index_SplitPath}-finish:
  Pop $R4
  Pop $R3
  Pop $R2
  Exch $R1 ; folder part
  Exch
  Exch $R0 ; drive part
  !undef Index_SplitPath
!macroend
 
; ################################################################
; mkdir DIRECTORY and return the part which already existed (BASEDIR)
!macro MakeDirBase DIRECTORY
  ; ATTENTION: USE ON YOUR OWN RISK!
  ; Please report bugs here: http://stefan.bertels.org/
  !define Index_MakeDirBase 'MakeDirBase_${__LINE__}'
  Push $R0
  StrCpy $R0 "${DIRECTORY}" ; $R0 contains DIRECTORY
  Push $R1 ; $R1 is tmp path (increasing)
  Push $R2 ; number of "\" to ignore (1 for c:\, 4 for \\server\share\)
  Push $R3 ; pos
  Push $R4 ; len
  Push $R5 ; tmp char
  Push $R6 ; BASEDIR (return value)
  ; save outdir
  Push $OUTDIR
 
  !insertmacro GetCleanDir $R0
  Pop $R0
  !insertmacro SplitPath $R0
  Pop $R1 ; drive
  Pop $R2 ; folder
  StrCmp $R1 "" ${Index_MakeDirBase}-fallback ; ohne Laufwerk/UNC?
  StrCpy $R3 $R2 1 0
  StrCmp $R3 "\" 0 ${Index_MakeDirBase}-fallback ; relativ?
 
  StrCpy $R3 0
  StrCpy $R1 "$R1\"
  StrCpy $R6 $R1
  StrLen $R4 $R2
${Index_MakeDirBase}-loop:
  IntOp $R3 $R3 + 1
  IntCmp $R4 $R3 ${Index_MakeDirBase}-exists ; end of R0
  StrCpy $R5 $R2 1 $R3 ; get next char
  StrCpy $R1 "$R1$R5" ; add another char
  StrCmp $R5 "\" 0 ${Index_MakeDirBase}-loop
  ; debug  MessageBox MB_OK "check $R1"
  IfFileExists "$R1*.*" 0 ${Index_MakeDirBase}-mkdir
  StrCpy $R6 $R1
  Goto ${Index_MakeDirBase}-loop
${Index_MakeDirBase}-mkdir:
  ; debug  MessageBox MB_OK "mkdir $R0"
  SetOutPath $R0
  Goto ${Index_MakeDirBase}-finish
${Index_MakeDirBase}-exists:
  ;  debug MessageBox MB_OK "exists $R0"
  StrCpy $R6 $R0
  Goto ${Index_MakeDirBase}-finish
 
${Index_MakeDirBase}-fallback:
  ; debug MessageBox MB_OK "fallback"
  SetOutPath $R0
  !insertmacro GetCleanParentDir $R0
  Pop $R6
 
${Index_MakeDirBase}-finish:
  ; restore outdir
  Pop $OUTDIR
 
  StrCpy $R0 $R6
  Pop $R6
  Pop $R5
  Pop $R4
  Pop $R3
  Pop $R2
  Pop $R1
  Exch $R0
  !undef Index_MakeDirBase
!macroend
 
; ################################################################
; rmdir DIRECTORY and all parents (if empty) up to BASEDIR (leave BASEDIR there)
!macro RemoveDirBase DIRECTORY BASEDIR
  ; ATTENTION: USE ON YOUR OWN RISK!
  ; Please report bugs here: http://stefan.bertels.org/
  !define Index_RemoveDirBase 'RemoveDirBase_${__LINE__}'
  Push ${DIRECTORY}
  Push ${BASEDIR}
  Push $R1
  Exch
  Pop $R1 ; $R1 contains BASEDIR
  Push $R0
  Exch 2
  Pop $R0 ; $0R contains DIRECTORY
  ; stack order: TOP => old $R1 => old $R0
  Push $R2
  Push $R3
  Push $OUTDIR
 
  !insertmacro GetCleanDir $R0
  Pop $R0
 
  ; basedir vorhanden?
  StrCmp $R1 "" ${Index_RemoveDirBase}-fallback
 
  ; basedir teil von DIRECTORY?
  StrLen $R2 $R1
  StrCpy $R3 $R0 $R2
  StrCmp $R1 $R3 0 ${Index_RemoveDirBase}-fallback ; is basedir the beginning of directory?
 
  ; außerdem muss BASEDIR mindestens (drive) enthalten
  !insertmacro SplitPath $R1
  Pop $R2  ; drive part
  Pop $R3  ; folder part
  StrCmp $R2 "" ${Index_RemoveDirBase}-fallback
  ; basedir is ok
 
${Index_RemoveDirBase}-loop:
  StrCmp $R0 $R1 ${Index_RemoveDirBase}-finish
  !insertmacro GetCleanParentDir $R0
  Pop $R2
  StrCmp $R2 $R0 ${Index_RemoveDirBase}-finish ; zur Sicherheit (kann eigentlich nicht auftreten)
  SetOutPath $R2
  RmDir $R0
  StrCpy $R0 $R2
  goto ${Index_RemoveDirBase}-loop
 
${Index_RemoveDirBase}-fallback:
  !insertmacro GetCleanParentDir $R0
  Pop $R1
  SetOutPath $R1
  RmDir $R0
 
${Index_RemoveDirBase}-finish:
  Pop $OUTDIR
  Pop $R3
  POp $R2
  Pop $R1
  Pop $R0
  !undef Index_RemoveDirBase
!macroend

Usage

Installer code:

  !insertmacro MakeDirBase $INSTDIR
  Pop $0
  WriteRegStr HKLM "...your_regkey_path..." "BasePath" "$0"

Uninstaller code:

  ReadRegStr $0 HKLM "...your_regkey_path..." "BasePath"
  !insertmacro RemoveDirBase "$INSTDIR" $0