NSIS でインストーラを作っていて、インストール前に既存バージョンを消したいとき、.onInit に何をどう書けばいいか悩んでいましたが、結局こうしました。
; GetTime のために FileFunc を使う
!Include "FileFunc.nsh"
Function .onInit
; これは普通どおりレジストリを見て既インストールか確認してるだけ
ReadRegStr $R0 ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString"
StrCmp $R0 "" done
; これも別に要らないけど、まあ動作を知らせている
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "既存の ${PRODUCT_NAME} を先にアンインストールしてください。$\r$\n$\r$\nOK を押すと続行します。その後で、改めてインストール画面を出します。" IDOK uninst
Abort
uninst:
ClearErrors
${GetTime} "" "L" $0 $1 $2 $3 $4 $5 $6
; セキュリティ的にはもっと推測や衝突を考えた方がいいだろうが
StrCpy $R1 "$TEMP\uninstall-$2-$1-$0-$4-$5-$6.exe"
CopyFiles "$R0" "$R1"
ExecWait '"$R1" _?=$INSTDIR' $R2
Delete "$R1"
; アンインストーラが正常終了したときだけ続行するなら以下のようにしておく
StrCmp $R2 0 done
Abort
done:
FunctionEnd
アンインストーラは自分を消す必要があるので、_? が指定されていなければ一時フォルダに自分をコピーしてからそっちを呼びます。だから ExecWait (子を待つが孫は待たない)が効かないぞ、というのは FAQ みたいです。
で、どうするのかというと、その動作を止め(つつ消去先を指定す)るオプション _? をただ呼べばいいのではありません。それだけでは「自分が起動中なので消せない」というエラーになったり、エラーを消そうとして REBOOTOK にしたら再起動後に(新しい)アンインストーラが消えちゃってアンインストールできなくなったり、という落とし穴があるみたいです。
なのでアンインストーラを ExecWait したいなら自分で TEMP にコピーするというのが一番簡単みたいです。
ちなみに、上記のようにするまでは、アンインストーラが新規インストーラの上に来るように MessageBox を遅らせたりしていました。
Function un.onInit Sleep 800 MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "$(^Name) を削除していいですか?" IDYES +2 Abort FunctionEnd