WinGit: Fix uninstall check regarding ssh-agent.exe
[msysgit.git] / share / WinGit / install.iss
blobc2ed8a19cbd76d051447d1ec32261064f8c0b38f
1 #define APP_NAME 'Git'
2 #define APP_VERSION '%APPVERSION%'
3 #define APP_URL 'http://code.google.com/p/msysgit/'
4 #define APP_BUILTINS 'etc\fileList-builtins.txt'
6 [Setup]
7 ; Compiler-related
8 InternalCompressLevel=max
9 OutputBaseFilename={#emit APP_NAME+'-'+APP_VERSION}
10 OutputDir=%OUTPUTDIR%
11 SolidCompression=yes
13 ; Installer-related
14 AllowNoIcons=yes
15 AppName={#emit APP_NAME}
16 AppPublisherURL={#emit APP_URL}
17 AppVersion={#emit APP_VERSION}
18 AppVerName={#emit APP_NAME+' '+APP_VERSION}
19 ChangesEnvironment=yes
20 DefaultDirName={pf}\{#emit APP_NAME}
21 DefaultGroupName={#emit APP_NAME}
22 DisableReadyPage=yes
23 InfoAfterFile=ReleaseNotes.rtf
24 LicenseFile=gpl-2.0.rtf
25 PrivilegesRequired=none
26 UninstallDisplayIcon=etc\git.ico
28 ; Cosmetic
29 SetupIconFile=etc\git.ico
30 WizardSmallImageFile=git.bmp
32 [Tasks]
33 Name: quicklaunchicon; Description: "Create a &Quick Launch icon"; GroupDescription: "Additional icons:"; Flags: checkedonce
34 Name: desktopicon; Description: "Create a &Desktop icon"; GroupDescription: "Additional icons:"; Flags: checkedonce
35 Name: shellextension; Description: "Add ""Git Ba&sh Here"""; GroupDescription: "Windows Explorer integration:"; Flags: checkedonce
36 Name: guiextension; Description: "Add ""Git &GUI Here"""; GroupDescription: "Windows Explorer integration:"; Flags: checkedonce
38 [Files]
39 Source: "*"; DestDir: "{app}"; Excludes: "\*.bmp, gpl-2.0.rtf, \install.*, \tmp.*, \bin\*install*"; Flags: recursesubdirs
41 [Icons]
42 Name: "{group}\Git GUI"; Filename: "{app}\bin\wish.exe"; Parameters: """{app}\bin\git-gui"""; WorkingDir: "%USERPROFILE%"; IconFilename: "{app}\etc\git.ico"
43 Name: "{group}\Git Bash"; Filename: "{syswow64}\cmd.exe"; Parameters: "/c """"{app}\bin\sh.exe"" --login -i"""; WorkingDir: "%USERPROFILE%"; IconFilename: "{app}\etc\git.ico"
44 Name: "{group}\Uninstall Git"; Filename: "{uninstallexe}"
45 Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\Git Bash"; Filename: "{syswow64}\cmd.exe"; Parameters: "/c """"{app}\bin\sh.exe"" --login -i"""; WorkingDir: "%USERPROFILE%"; IconFilename: "{app}\etc\git.ico"; Tasks: quicklaunchicon
46 Name: "{code:GetShellFolder|desktop}\Git Bash"; Filename: "{syswow64}\cmd.exe"; Parameters: "/c """"{app}\bin\sh.exe"" --login -i"""; WorkingDir: "%USERPROFILE%"; IconFilename: "{app}\etc\git.ico"; Tasks: desktopicon
48 [Messages]
49 BeveledLabel={#emit APP_URL}
51 [UninstallDelete]
52 Type: files; Name: "{app}\bin\git-*.exe"
53 Type: dirifempty; Name: "{app}\home\{username}"
54 Type: dirifempty; Name: "{app}\home"
56 [Code]
58 Helper methods
61 function GetShellFolder(Param:string):string;
62 begin
63 if IsAdminLoggedOn then begin
64 Param:='{common'+Param+'}';
65 end else begin
66 Param:='{user'+Param+'}';
67 end;
68 Result:=ExpandConstant(Param);
69 end;
71 function CreateHardLink(lpFileName,lpExistingFileName:string;lpSecurityAttributes:Integer):Boolean;
72 external 'CreateHardLinkA@Kernel32.dll';
74 function GetEnvStrings(VarName:string;AllUsers:Boolean):TArrayOfString;
75 var
76 Path:string;
77 i:Longint;
78 p:Integer;
79 begin
80 Path:='';
82 // See http://www.jrsoftware.org/isfaq.php#env
83 if AllUsers then begin
84 // We ignore errors here. The resulting array of strings will be empty.
85 RegQueryStringValue(HKEY_LOCAL_MACHINE,'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',VarName,Path);
86 end else begin
87 // We ignore errors here. The resulting array of strings will be empty.
88 RegQueryStringValue(HKEY_CURRENT_USER,'Environment',VarName,Path);
89 end;
91 // Make sure we have at least one semicolon.
92 Path:=Path+';';
94 // Split the directories in PATH into an array of strings.
95 i:=0;
96 SetArrayLength(Result,0);
98 p:=Pos(';',Path);
99 while p>0 do begin
100 SetArrayLength(Result,i+1);
101 if p>1 then begin
102 Result[i]:=Copy(Path,1,p-1);
103 i:=i+1;
104 end;
105 Path:=Copy(Path,p+1,Length(Path));
106 p:=Pos(';',Path);
107 end;
108 end;
110 function SetEnvStrings(VarName:string;AllUsers,DeleteIfEmpty:Boolean;DirStrings:TArrayOfString):Boolean;
112 Path,KeyName:string;
113 i:Longint;
114 begin
115 // Merge all non-empty directory strings into a PATH variable.
116 Path:='';
117 for i:=0 to GetArrayLength(DirStrings)-1 do begin
118 if Length(DirStrings[i])>0 then begin
119 if Length(Path)>0 then begin
120 Path:=Path+';'+DirStrings[i];
121 end else begin
122 Path:=DirStrings[i];
123 end;
124 end;
125 end;
127 // See http://www.jrsoftware.org/isfaq.php#env
128 if AllUsers then begin
129 KeyName:='SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
130 if DeleteIfEmpty and (Length(Path)=0) then begin
131 Result:=(not RegValueExists(HKEY_LOCAL_MACHINE,KeyName,VarName))
132 or RegDeleteValue(HKEY_LOCAL_MACHINE,KeyName,VarName);
133 end else begin
134 Result:=RegWriteStringValue(HKEY_LOCAL_MACHINE,KeyName,VarName,Path);
135 end;
136 end else begin
137 KeyName:='Environment';
138 if DeleteIfEmpty and (Length(Path)=0) then begin
139 Result:=(not RegValueExists(HKEY_CURRENT_USER,KeyName,VarName))
140 or RegDeleteValue(HKEY_CURRENT_USER,KeyName,VarName);
141 end else begin
142 Result:=RegWriteStringValue(HKEY_CURRENT_USER,KeyName,VarName,Path);
143 end;
144 end;
145 end;
148 Installer code
152 EnvPage:TWizardPage;
153 RdbGitBash,RdbGitCmd,RdbGitCmdTools:TRadioButton;
155 procedure InitializeWizard;
157 LblGitBash,LblGitCmd,LblGitCmdTools,LblGitCmdToolsWarn:TLabel;
158 begin
159 // Create a custom page for modifying the environment.
160 EnvPage:=CreateCustomPage(
161 wpSelectTasks,
162 'Adjusting your PATH environment',
163 'How would you like to use Git from the command line?'
166 // 1st choice
167 RdbGitBash:=TRadioButton.Create(EnvPage);
168 with RdbGitBash do begin
169 Parent:=EnvPage.Surface;
170 Caption:='Use Git Bash only';
171 Left:=ScaleX(4);
172 Top:=ScaleY(8);
173 Width:=ScaleX(129);
174 Height:=ScaleY(17);
175 Font.Style:=[fsBold];
176 TabOrder:=0;
177 Checked:=True;
178 end;
179 LblGitBash:=TLabel.Create(EnvPage);
180 with LblGitBash do begin
181 Parent:=EnvPage.Surface;
182 Caption:=
183 'This is the most conservative choice if you are concerned about the' + #13 +
184 'stability of your system. Your PATH will not be modified.';
185 Left:=ScaleX(28);
186 Top:=ScaleY(32);
187 Width:=ScaleX(324);
188 Height:=ScaleY(26);
189 end;
191 // 2nd choice
192 RdbGitCmd:=TRadioButton.Create(EnvPage);
193 with RdbGitCmd do begin
194 Parent:=EnvPage.Surface;
195 Caption:='Run Git from the Windows Command Prompt';
196 Left:=ScaleX(4);
197 Top:=ScaleY(76);
198 Width:=ScaleX(281);
199 Height:=ScaleY(17);
200 Font.Style:=[fsBold];
201 TabOrder:=1;
202 end;
203 LblGitCmd:=TLabel.Create(EnvPage);
204 with LblGitCmd do begin
205 Parent:=EnvPage.Surface;
206 Caption:=
207 'This option is considered safe and no conflicts with other tools are' + #13 +
208 'known. Only Git will be added to your PATH.';
209 Left:=ScaleX(28);
210 Top:=ScaleY(100);
211 Width:=ScaleX(316);
212 Height:=ScaleY(26);
213 end;
215 // 3rd choice
216 RdbGitCmdTools:=TRadioButton.Create(EnvPage);
217 with RdbGitCmdTools do begin
218 Parent:=EnvPage.Surface;
219 Caption:='Run Git and included Unix tools from the Windows Command Prompt';
220 Left:=ScaleX(4);
221 Top:=ScaleY(152);
222 Width:=ScaleX(405);
223 Height:=ScaleY(17);
224 Font.Style:=[fsBold];
225 TabOrder:=2;
226 end;
227 LblGitCmdTools:=TLabel.Create(EnvPage);
228 with LblGitCmdTools do begin
229 Parent:=EnvPage.Surface;
230 Caption:='Both Git and the Unix tools will be added to your PATH.';
231 Left:=ScaleX(28);
232 Top:=ScaleY(176);
233 Width:=ScaleX(280);
234 Height:=ScaleY(13);
235 end;
236 LblGitCmdToolsWarn:=TLabel.Create(EnvPage);
237 with LblGitCmdToolsWarn do begin
238 Parent:=EnvPage.Surface;
239 Caption:=
240 'Warning: This will override Windows tools like find.exe and' + #13 +
241 'sort.exe. Select this option only if you understand the implications.';
242 Left:=ScaleX(28);
243 Top:=ScaleY(192);
244 Width:=ScaleX(376);
245 Height:=ScaleY(26);
246 Font.Color:=255;
247 Font.Style:=[fsBold];
248 end;
249 end;
251 procedure CurStepChanged(CurStep:TSetupStep);
253 AppDir,FileName,Cmd,Msg:string;
254 BuiltIns,EnvPath,EnvHome:TArrayOfString;
255 Count,i:Longint;
256 IsNTFS:Boolean;
257 RootKey:Integer;
258 begin
259 if CurStep<>ssPostInstall then begin
260 Exit;
261 end;
263 AppDir:=ExpandConstant('{app}');
266 Create the built-ins
269 // Load the built-ins from a text file.
270 FileName:=ExpandConstant('{app}\'+'{#emit APP_BUILTINS}');
271 if LoadStringsFromFile(FileName,BuiltIns) then begin
272 // Check if we are running on NTFS.
273 IsNTFS:=False;
274 if SetNTFSCompression(AppDir+'\bin\git.exe',true) then begin
275 IsNTFS:=SetNTFSCompression(AppDir+'\bin\git.exe',false);
276 end;
278 Count:=GetArrayLength(BuiltIns)-1;
280 // Map the built-ins to git.exe.
281 if IsNTFS then begin
282 Log('Line {#emit __LINE__}: Assuming the partition is formatted using NTFS.');
284 for i:=0 to Count do begin
285 FileName:=AppDir+'\'+BuiltIns[i];
287 // On non-NTFS partitions, create hard links.
288 if (not DeleteFile(FileName))
289 or (not CreateHardLink(FileName,AppDir+'\bin\git.exe',0)) then begin
290 Log('Line {#emit __LINE__}: Unable to create hard link "'+FileName+'", will try to copy files.');
291 IsNTFS:=False;
292 Break;
293 end;
294 end;
295 end;
297 // The fallback is to copy the files.
298 if not IsNTFS then begin
299 for i:=0 to Count do begin
300 FileName:=AppDir+'\'+BuiltIns[i];
302 // On non-NTFS partitions, copy simply the files (overwriting existing ones).
303 if not FileCopy(AppDir+'\bin\git.exe',FileName,false) then begin
304 Msg:='Line {#emit __LINE__}: Unable to create built-in "'+FileName+'".';
305 MsgBox(Msg,mbError,MB_OK);
306 Log(Msg);
307 // This is not a critical error, Git could basically be used without the
308 // aliases for built-ins, so we continue.
309 end;
310 end;
311 end;
312 end else begin
313 Msg:='Line {#emit __LINE__}: Unable to read file "{#emit APP_BUILTINS}".';
314 MsgBox(Msg,mbError,MB_OK);
315 Log(Msg);
316 // This is not a critical error, Git could basically be used without the
317 // aliases for built-ins, so we continue.
318 end;
322 Modify the environment
324 This must happen no later than ssPostInstall to make
325 "ChangesEnvironment=yes" not happend before the change!
328 // Get the current user's directories in PATH.
329 EnvPath:=GetEnvStrings('PATH',IsAdminLoggedOn);
331 // First, remove the installation directory from PATH in any case.
332 for i:=0 to GetArrayLength(EnvPath)-1 do begin
333 if Pos(AppDir,EnvPath[i])=1 then begin
334 EnvPath[i]:='';
335 end;
336 end;
338 // Delete HOME if a previous installation modified it.
339 EnvHome:=GetEnvStrings('HOME',IsAdminLoggedOn);
340 if (GetArrayLength(EnvHome)=1) and
341 (CompareStr(EnvHome[0],GetIniString('Environment','HOME','',AppDir+'\setup.ini'))=0) then begin
342 if not SetEnvStrings('HOME',IsAdminLoggedOn,True,[]) then begin
343 Msg:='Line {#emit __LINE__}: Unable to reset HOME prior to install.';
344 MsgBox(Msg,mbError,MB_OK);
345 Log(Msg);
346 // This is not a critical error, the user can probably fix it manually,
347 // so we continue.
348 end;
349 end;
351 // Modify the PATH variable as requested by the user.
352 if RdbGitCmd.Checked or RdbGitCmdTools.Checked then begin
353 i:=GetArrayLength(EnvPath);
354 SetArrayLength(EnvPath,i+1);
356 // List \cmd before \bin so \cmd has higher priority and programs in
357 // there will be called in favor of those in \bin.
358 EnvPath[i]:=ExpandConstant('{app}\cmd');
360 if RdbGitCmdTools.Checked then begin
361 SetArrayLength(EnvPath,i+2);
362 EnvPath[i+1]:=ExpandConstant('{app}\bin');
364 // Set HOME for the Windows Command Prompt.
365 EnvHome:=GetEnvStrings('HOME',IsAdminLoggedOn);
366 i:=GetArrayLength(EnvHome);
367 if (i=0) or ((i=1) and (Length(EnvHome[0])=0)) then begin
368 SetArrayLength(EnvHome,1);
369 EnvHome[0]:=ExpandConstant('{%USERPROFILE}');
370 if not SetEnvStrings('HOME',IsAdminLoggedOn,True,EnvHome) then begin
371 Msg:='Line {#emit __LINE__}: Unable to set the HOME environment variable.';
372 MsgBox(Msg,mbError,MB_OK);
373 Log(Msg);
374 // This is not a critical error, the user can probably fix it manually,
375 // so we continue.
376 end;
378 // Mark that we have changed HOME.
379 FileName:=AppDir+'\setup.ini';
380 if not SetIniString('Environment','HOME',EnvHome[0],FileName) then begin
381 Msg:='Line {#emit __LINE__}: Unable to write to file "'+FileName+'".';
382 MsgBox(Msg,mbError,MB_OK);
383 Log(Msg);
384 // This is not a critical error, though uninstall / reinstall will probably not run cleanly,
385 // so we continue.
386 end;
387 end;
388 end;
389 end;
391 // Set the current user's PATH directories.
392 if not SetEnvStrings('PATH',IsAdminLoggedOn,True,EnvPath) then begin
393 Msg:='Line {#emit __LINE__}: Unable to set the PATH environment variable.';
394 MsgBox(Msg,mbError,MB_OK);
395 Log(Msg);
396 // This is not a critical error, the user can probably fix it manually,
397 // so we continue.
398 end;
401 Create the Windows Explorer shell extensions
404 if IsAdminLoggedOn then begin
405 RootKey:=HKEY_LOCAL_MACHINE;
406 end else begin
407 RootKey:=HKEY_CURRENT_USER;
408 end;
410 if IsTaskSelected('shellextension') then begin
411 Cmd:=ExpandConstant('"{syswow64}\cmd.exe"');
412 if (not RegWriteStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_shell','','Git Ba&sh Here'))
413 or (not RegWriteStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_shell\command','',Cmd+' /c "pushd "%1" && "'+AppDir+'\bin\sh.exe" --login -i"')) then begin
414 Msg:='Line {#emit __LINE__}: Unable to create "Git Bash Here" shell extension.';
415 MsgBox(Msg,mbError,MB_OK);
416 Log(Msg);
417 // This is not a critical error, the user can probably fix it manually,
418 // so we continue.
419 end;
420 end;
422 if IsTaskSelected('guiextension') then begin
423 if (not RegWriteStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_gui','','Git &GUI Here'))
424 or (not RegWriteStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_gui\command','','"'+AppDir+'\bin\wish.exe" "'+AppDir+'\bin\git-gui" "--working-dir" "%1"')) then begin
425 Msg:='Line {#emit __LINE__}: Unable to create "Git GUI Here" shell extension.';
426 MsgBox(Msg,mbError,MB_OK);
427 Log(Msg);
428 // This is not a critical error, the user can probably fix it manually,
429 // so we continue.
430 end;
431 end;
432 end;
435 Uninstaller code
438 function InitializeUninstall:Boolean;
440 FileName,NewName,Msg:string;
441 begin
442 FileName:=ExpandConstant('{app}')+'\bin\ssh-agent.exe';
443 if FileExists(FileName) then begin
444 // Create a temporary copy of the file we try to delete.
445 NewName:=FileName+'.'+IntToStr(1000+Random(9000));
446 Result:=FileCopy(FileName,NewName,True) and DeleteFile(FileName);
448 if not Result then begin
449 Msg:='Line {#emit __LINE__}: Please stop all ssh-agent processes and run uninstall again.';
450 MsgBox(Msg,mbError,MB_OK);
451 Log(Msg);
453 // Clean-up the temporary copy (ignoring any errors).
454 DeleteFile(NewName);
455 end else begin
456 // Clean-up the temporary copy (ignoring any errors).
457 RenameFile(NewName,FileName);
458 end;
459 end else begin
460 Result:=True;
461 end;
462 end;
464 procedure CurUninstallStepChanged(CurUninstallStep:TUninstallStep);
466 AppDir,Command,Msg:string;
467 EnvPath,EnvHome:TArrayOfString;
468 i:Longint;
469 RootKey:Integer;
470 begin
471 if CurUninstallStep<>usUninstall then begin
472 Exit;
473 end;
476 Modify the environment
478 This must happen no later than usUninstall to make
479 "ChangesEnvironment=yes" not happend before the change!
482 AppDir:=ExpandConstant('{app}');
484 // Get the current user's directories in PATH.
485 EnvPath:=GetEnvStrings('PATH',IsAdminLoggedOn);
487 // Remove the installation directory from PATH in any case, even if it
488 // was not added by the installer.
489 for i:=0 to GetArrayLength(EnvPath)-1 do begin
490 if Pos(AppDir,EnvPath[i])=1 then begin
491 EnvPath[i]:='';
492 end;
493 end;
495 // Reset the current user's directories in PATH.
496 if not SetEnvStrings('PATH',IsAdminLoggedOn,True,EnvPath) then begin
497 Msg:='Line {#emit __LINE__}: Unable to revert any possible changes to PATH.';
498 MsgBox(Msg,mbError,MB_OK);
499 Log(Msg);
500 // This is not a critical error, the user can probably fix it manually,
501 // so we continue.
502 end;
504 // Reset the current user's HOME if we modified it.
505 EnvHome:=GetEnvStrings('HOME',IsAdminLoggedOn);
506 if (GetArrayLength(EnvHome)=1) and
507 (CompareStr(EnvHome[0],GetIniString('Environment','HOME','',AppDir+'\setup.ini'))=0) then begin
508 if not SetEnvStrings('HOME',IsAdminLoggedOn,True,[]) then begin
509 Msg:='Line {#emit __LINE__}: Unable to revert any possible changes to HOME.';
510 MsgBox(Msg,mbError,MB_OK);
511 Log(Msg);
512 // This is not a critical error, the user can probably fix it manually,
513 // so we continue.
514 end;
515 end;
517 Command:=AppDir+'\setup.ini';
518 if not DeleteFile(Command) then begin
519 Msg:='Line {#emit __LINE__}: Unable to delete file "'+Command+'".';
520 MsgBox(Msg,mbError,MB_OK);
521 Log(Msg);
522 // This is not a critical error, the user can probably fix it manually,
523 // so we continue.
524 end;
527 Delete the Windows Explorer shell extensions
530 if IsAdminLoggedOn then begin
531 RootKey:=HKEY_LOCAL_MACHINE;
532 end else begin
533 RootKey:=HKEY_CURRENT_USER;
534 end;
536 Command:='';
537 RegQueryStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_shell\command','',Command);
538 if Pos(AppDir,Command)>0 then begin
539 if not RegDeleteKeyIncludingSubkeys(RootKey,'SOFTWARE\Classes\Directory\shell\git_shell') then begin
540 Msg:='Line {#emit __LINE__}: Unable to remove "Git Bash Here" shell extension.';
541 MsgBox(Msg,mbError,MB_OK);
542 Log(Msg);
543 // This is not a critical error, the user can probably fix it manually,
544 // so we continue.
545 end;
546 end;
548 Command:='';
549 RegQueryStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_gui\command','',Command);
550 if Pos(AppDir,Command)>0 then begin
551 if not RegDeleteKeyIncludingSubkeys(RootKey,'SOFTWARE\Classes\Directory\shell\git_gui') then begin
552 Msg:='Line {#emit __LINE__}: Unable to remove "Git GUI Here" shell extension.';
553 MsgBox(Msg,mbError,MB_OK);
554 Log(Msg);
555 // This is not a critical error, the user can probably fix it manually,
556 // so we continue.
557 end;
558 end;
559 end;