857a09ec339a933b34f5f752e6a32d5dc8afcd69
[msysgit.git] / share / WinGit / install.iss
blob857a09ec339a933b34f5f752e6a32d5dc8afcd69
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 LicenseFile=gpl-2.0.rtf
24 PrivilegesRequired=none
25 UninstallDisplayIcon=etc\git.ico
27 ; Cosmetic
28 SetupIconFile=etc\git.ico
29 WizardSmallImageFile=git.bmp
31 [Tasks]
32 Name: quicklaunchicon; Description: "Create a &Quick Launch icon"; GroupDescription: "Additional icons:"; Flags: checkedonce
33 Name: desktopicon; Description: "Create a &Desktop icon"; GroupDescription: "Additional icons:"; Flags: checkedonce
34 Name: shellextension; Description: "Add ""Git Ba&sh Here"""; GroupDescription: "Windows Explorer integration:"; Flags: checkedonce
35 Name: guiextension; Description: "Add ""Git &GUI Here"""; GroupDescription: "Windows Explorer integration:"; Flags: checkedonce
37 [Files]
38 Source: "*"; DestDir: "{app}"; Excludes: "\*.bmp, gpl-2.0.rtf, \install.*, \tmp.*, \bin\*install*"; Flags: recursesubdirs
39 Source: ReleaseNotes.rtf; DestDir: "{app}"; Flags: isreadme
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}
50 SetupAppTitle={#emit APP_NAME} Setup
51 SetupWindowTitle={#emit APP_NAME} Setup
53 [UninstallDelete]
54 Type: files; Name: "{app}\bin\git-*.exe"
55 Type: dirifempty; Name: "{app}\home\{username}"
56 Type: dirifempty; Name: "{app}\home"
58 [Code]
60 Helper methods
63 function GetShellFolder(Param:string):string;
64 begin
65 if IsAdminLoggedOn then begin
66 Param:='{common'+Param+'}';
67 end else begin
68 Param:='{user'+Param+'}';
69 end;
70 Result:=ExpandConstant(Param);
71 end;
73 function CreateHardLink(lpFileName,lpExistingFileName:string;lpSecurityAttributes:Integer):Boolean;
74 external 'CreateHardLinkA@Kernel32.dll';
76 function GetEnvStrings(VarName:string;AllUsers:Boolean):TArrayOfString;
77 var
78 Path:string;
79 i:Longint;
80 p:Integer;
81 begin
82 Path:='';
84 // See http://www.jrsoftware.org/isfaq.php#env
85 if AllUsers then begin
86 // We ignore errors here. The resulting array of strings will be empty.
87 RegQueryStringValue(HKEY_LOCAL_MACHINE,'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',VarName,Path);
88 end else begin
89 // We ignore errors here. The resulting array of strings will be empty.
90 RegQueryStringValue(HKEY_CURRENT_USER,'Environment',VarName,Path);
91 end;
93 // Make sure we have at least one semicolon.
94 Path:=Path+';';
96 // Split the directories in PATH into an array of strings.
97 i:=0;
98 SetArrayLength(Result,0);
100 p:=Pos(';',Path);
101 while p>0 do begin
102 SetArrayLength(Result,i+1);
103 if p>1 then begin
104 Result[i]:=Copy(Path,1,p-1);
105 i:=i+1;
106 end;
107 Path:=Copy(Path,p+1,Length(Path));
108 p:=Pos(';',Path);
109 end;
110 end;
112 function SetEnvStrings(VarName:string;AllUsers,DeleteIfEmpty:Boolean;DirStrings:TArrayOfString):Boolean;
114 Path,KeyName:string;
115 i:Longint;
116 begin
117 // Merge all non-empty directory strings into a PATH variable.
118 Path:='';
119 for i:=0 to GetArrayLength(DirStrings)-1 do begin
120 if Length(DirStrings[i])>0 then begin
121 if Length(Path)>0 then begin
122 Path:=Path+';'+DirStrings[i];
123 end else begin
124 Path:=DirStrings[i];
125 end;
126 end;
127 end;
129 // See http://www.jrsoftware.org/isfaq.php#env
130 if AllUsers then begin
131 KeyName:='SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
132 if DeleteIfEmpty and (Length(Path)=0) then begin
133 Result:=(not RegValueExists(HKEY_LOCAL_MACHINE,KeyName,VarName))
134 or RegDeleteValue(HKEY_LOCAL_MACHINE,KeyName,VarName);
135 end else begin
136 Result:=RegWriteStringValue(HKEY_LOCAL_MACHINE,KeyName,VarName,Path);
137 end;
138 end else begin
139 KeyName:='Environment';
140 if DeleteIfEmpty and (Length(Path)=0) then begin
141 Result:=(not RegValueExists(HKEY_CURRENT_USER,KeyName,VarName))
142 or RegDeleteValue(HKEY_CURRENT_USER,KeyName,VarName);
143 end else begin
144 Result:=RegWriteStringValue(HKEY_CURRENT_USER,KeyName,VarName,Path);
145 end;
146 end;
147 end;
150 Installer code
154 EnvPage:TWizardPage;
155 RdbGitBash,RdbGitCmd,RdbGitCmdTools:TRadioButton;
157 procedure InitializeWizard;
159 LblGitBash,LblGitCmd,LblGitCmdTools,LblGitCmdToolsWarn:TLabel;
160 begin
161 // Create a custom page for modifying the environment.
162 EnvPage:=CreateCustomPage(
163 wpSelectTasks,
164 'Adjusting your PATH environment',
165 'How would you like to use Git from the command line?'
168 // 1st choice
169 RdbGitBash:=TRadioButton.Create(EnvPage);
170 with RdbGitBash do begin
171 Parent:=EnvPage.Surface;
172 Caption:='Use Git Bash only';
173 Left:=ScaleX(4);
174 Top:=ScaleY(8);
175 Width:=ScaleX(129);
176 Height:=ScaleY(17);
177 Font.Style:=[fsBold];
178 TabOrder:=0;
179 Checked:=True;
180 end;
181 LblGitBash:=TLabel.Create(EnvPage);
182 with LblGitBash do begin
183 Parent:=EnvPage.Surface;
184 Caption:=
185 'This is the most conservative choice if you are concerned about the' + #13 +
186 'stability of your system. Your PATH will not be modified.';
187 Left:=ScaleX(28);
188 Top:=ScaleY(32);
189 Width:=ScaleX(324);
190 Height:=ScaleY(26);
191 end;
193 // 2nd choice
194 RdbGitCmd:=TRadioButton.Create(EnvPage);
195 with RdbGitCmd do begin
196 Parent:=EnvPage.Surface;
197 Caption:='Run Git from the Windows Command Prompt';
198 Left:=ScaleX(4);
199 Top:=ScaleY(76);
200 Width:=ScaleX(281);
201 Height:=ScaleY(17);
202 Font.Style:=[fsBold];
203 TabOrder:=1;
204 end;
205 LblGitCmd:=TLabel.Create(EnvPage);
206 with LblGitCmd do begin
207 Parent:=EnvPage.Surface;
208 Caption:=
209 'This option is considered safe and no conflicts with other tools are' + #13 +
210 'known. Only Git will be added to your PATH.';
211 Left:=ScaleX(28);
212 Top:=ScaleY(100);
213 Width:=ScaleX(316);
214 Height:=ScaleY(26);
215 end;
217 // 3rd choice
218 RdbGitCmdTools:=TRadioButton.Create(EnvPage);
219 with RdbGitCmdTools do begin
220 Parent:=EnvPage.Surface;
221 Caption:='Run Git and included Unix tools from the Windows Command Prompt';
222 Left:=ScaleX(4);
223 Top:=ScaleY(152);
224 Width:=ScaleX(405);
225 Height:=ScaleY(17);
226 Font.Style:=[fsBold];
227 TabOrder:=2;
228 end;
229 LblGitCmdTools:=TLabel.Create(EnvPage);
230 with LblGitCmdTools do begin
231 Parent:=EnvPage.Surface;
232 Caption:='Both Git and the Unix tools will be added to your PATH.';
233 Left:=ScaleX(28);
234 Top:=ScaleY(176);
235 Width:=ScaleX(280);
236 Height:=ScaleY(13);
237 end;
238 LblGitCmdToolsWarn:=TLabel.Create(EnvPage);
239 with LblGitCmdToolsWarn do begin
240 Parent:=EnvPage.Surface;
241 Caption:=
242 'Warning: This will override Windows tools like find.exe and' + #13 +
243 'sort.exe. Select this option only if you understand the implications.';
244 Left:=ScaleX(28);
245 Top:=ScaleY(192);
246 Width:=ScaleX(376);
247 Height:=ScaleY(26);
248 Font.Color:=255;
249 Font.Style:=[fsBold];
250 end;
251 end;
253 procedure CurStepChanged(CurStep:TSetupStep);
255 AppDir,FileName,Cmd,Msg:string;
256 BuiltIns,EnvPath,EnvHome:TArrayOfString;
257 Count,i:Longint;
258 IsNTFS:Boolean;
259 RootKey:Integer;
260 begin
261 if CurStep<>ssPostInstall then begin
262 Exit;
263 end;
265 AppDir:=ExpandConstant('{app}');
268 Create the built-ins
271 // Load the built-ins from a text file.
272 FileName:=ExpandConstant('{app}\'+'{#emit APP_BUILTINS}');
273 if LoadStringsFromFile(FileName,BuiltIns) then begin
274 // Check if we are running on NTFS.
275 IsNTFS:=False;
276 if SetNTFSCompression(AppDir+'\bin\git.exe',true) then begin
277 IsNTFS:=SetNTFSCompression(AppDir+'\bin\git.exe',false);
278 end;
280 Count:=GetArrayLength(BuiltIns)-1;
282 // Delete all scripts as they might have been replaced by built-ins by now.
283 for i:=0 to Count do begin
284 FileName:=ChangeFileExt(AppDir+'\'+BuiltIns[i],'');
285 if (FileExists(FileName) and (not DeleteFile(FileName))) then begin
286 Log('Line {#emit __LINE__}: Unable to delete script "'+FileName+'", ignoring.');
287 end;
288 end;
290 // Map the built-ins to git.exe.
291 if IsNTFS then begin
292 Log('Line {#emit __LINE__}: Partition seems to be NTFS, trying to create built-in aliases as hard links.');
294 for i:=0 to Count do begin
295 FileName:=AppDir+'\'+BuiltIns[i];
297 // On non-NTFS partitions, create hard links.
298 if (FileExists(FileName) and (not DeleteFile(FileName)))
299 or (not CreateHardLink(FileName,AppDir+'\bin\git.exe',0)) then begin
300 Log('Line {#emit __LINE__}: Unable to create hard link "'+FileName+'", will try to copy files.');
301 IsNTFS:=False;
302 Break;
303 end;
304 end;
306 Log('Line {#emit __LINE__}: Successfully created built-in aliases as hard links.');
307 end;
309 // The fallback is to copy the files.
310 if not IsNTFS then begin
311 Log('Line {#emit __LINE__}: Trying to create built-in aliases as file copies.');
313 for i:=0 to Count do begin
314 FileName:=AppDir+'\'+BuiltIns[i];
316 // On non-NTFS partitions, copy simply the files (overwriting existing ones).
317 if not FileCopy(AppDir+'\bin\git.exe',FileName,false) then begin
318 Msg:='Line {#emit __LINE__}: Unable to create file copy "'+FileName+'".';
319 MsgBox(Msg,mbError,MB_OK);
320 Log(Msg);
321 // This is not a critical error, Git could basically be used without the
322 // aliases for built-ins, so we continue.
323 end;
324 end;
326 Log('Line {#emit __LINE__}: Successfully created built-in aliases as file copies.');
327 end;
328 end else begin
329 Msg:='Line {#emit __LINE__}: Unable to read file "{#emit APP_BUILTINS}".';
330 MsgBox(Msg,mbError,MB_OK);
331 Log(Msg);
332 // This is not a critical error, Git could basically be used without the
333 // aliases for built-ins, so we continue.
334 end;
338 Modify the environment
340 This must happen no later than ssPostInstall to make
341 "ChangesEnvironment=yes" not happend before the change!
344 // Get the current user's directories in PATH.
345 EnvPath:=GetEnvStrings('PATH',IsAdminLoggedOn);
347 // First, remove the installation directory from PATH in any case.
348 for i:=0 to GetArrayLength(EnvPath)-1 do begin
349 if Pos(AppDir,EnvPath[i])=1 then begin
350 EnvPath[i]:='';
351 end;
352 end;
354 // Delete HOME if a previous installation modified it.
355 EnvHome:=GetEnvStrings('HOME',IsAdminLoggedOn);
356 if (GetArrayLength(EnvHome)=1) and
357 (CompareStr(EnvHome[0],GetIniString('Environment','HOME','',AppDir+'\setup.ini'))=0) then begin
358 if not SetEnvStrings('HOME',IsAdminLoggedOn,True,[]) then begin
359 Msg:='Line {#emit __LINE__}: Unable to reset HOME prior to install.';
360 MsgBox(Msg,mbError,MB_OK);
361 Log(Msg);
362 // This is not a critical error, the user can probably fix it manually,
363 // so we continue.
364 end;
365 end;
367 // Modify the PATH variable as requested by the user.
368 if RdbGitCmd.Checked or RdbGitCmdTools.Checked then begin
369 i:=GetArrayLength(EnvPath);
370 SetArrayLength(EnvPath,i+1);
372 // List \cmd before \bin so \cmd has higher priority and programs in
373 // there will be called in favor of those in \bin.
374 EnvPath[i]:=ExpandConstant('{app}\cmd');
376 if RdbGitCmdTools.Checked then begin
377 SetArrayLength(EnvPath,i+2);
378 EnvPath[i+1]:=ExpandConstant('{app}\bin');
380 // Set HOME for the Windows Command Prompt.
381 EnvHome:=GetEnvStrings('HOME',IsAdminLoggedOn);
382 i:=GetArrayLength(EnvHome);
383 if (i=0) or ((i=1) and (Length(EnvHome[0])=0)) then begin
384 SetArrayLength(EnvHome,1);
385 EnvHome[0]:=ExpandConstant('{%USERPROFILE}');
386 if not SetEnvStrings('HOME',IsAdminLoggedOn,True,EnvHome) then begin
387 Msg:='Line {#emit __LINE__}: Unable to set the HOME environment variable.';
388 MsgBox(Msg,mbError,MB_OK);
389 Log(Msg);
390 // This is not a critical error, the user can probably fix it manually,
391 // so we continue.
392 end;
394 // Mark that we have changed HOME.
395 FileName:=AppDir+'\setup.ini';
396 if not SetIniString('Environment','HOME',EnvHome[0],FileName) then begin
397 Msg:='Line {#emit __LINE__}: Unable to write to file "'+FileName+'".';
398 MsgBox(Msg,mbError,MB_OK);
399 Log(Msg);
400 // This is not a critical error, though uninstall / reinstall will probably not run cleanly,
401 // so we continue.
402 end;
403 end;
404 end;
405 end;
407 // Set the current user's PATH directories.
408 if not SetEnvStrings('PATH',IsAdminLoggedOn,True,EnvPath) then begin
409 Msg:='Line {#emit __LINE__}: Unable to set the PATH environment variable.';
410 MsgBox(Msg,mbError,MB_OK);
411 Log(Msg);
412 // This is not a critical error, the user can probably fix it manually,
413 // so we continue.
414 end;
417 Create the Windows Explorer shell extensions
420 if IsAdminLoggedOn then begin
421 RootKey:=HKEY_LOCAL_MACHINE;
422 end else begin
423 RootKey:=HKEY_CURRENT_USER;
424 end;
426 if IsTaskSelected('shellextension') then begin
427 Cmd:=ExpandConstant('"{syswow64}\cmd.exe"');
428 if (not RegWriteStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_shell','','Git Ba&sh Here'))
429 or (not RegWriteStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_shell\command','',Cmd+' /c "pushd "%1" && "'+AppDir+'\bin\sh.exe" --login -i"')) then begin
430 Msg:='Line {#emit __LINE__}: Unable to create "Git Bash Here" shell extension.';
431 MsgBox(Msg,mbError,MB_OK);
432 Log(Msg);
433 // This is not a critical error, the user can probably fix it manually,
434 // so we continue.
435 end;
436 end;
438 if IsTaskSelected('guiextension') then begin
439 if (not RegWriteStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_gui','','Git &GUI Here'))
440 or (not RegWriteStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_gui\command','','"'+AppDir+'\bin\wish.exe" "'+AppDir+'\bin\git-gui" "--working-dir" "%1"')) then begin
441 Msg:='Line {#emit __LINE__}: Unable to create "Git GUI Here" shell extension.';
442 MsgBox(Msg,mbError,MB_OK);
443 Log(Msg);
444 // This is not a critical error, the user can probably fix it manually,
445 // so we continue.
446 end;
447 end;
448 end;
451 Uninstaller code
454 function InitializeUninstall:Boolean;
456 FileName,NewName,Msg:string;
457 begin
458 FileName:=ExpandConstant('{app}')+'\bin\ssh-agent.exe';
459 if FileExists(FileName) then begin
460 // Create a temporary copy of the file we try to delete.
461 NewName:=FileName+'.'+IntToStr(1000+Random(9000));
462 Result:=FileCopy(FileName,NewName,True) and DeleteFile(FileName);
464 if not Result then begin
465 Msg:='Line {#emit __LINE__}: Please stop all ssh-agent processes and run uninstall again.';
466 MsgBox(Msg,mbError,MB_OK);
467 Log(Msg);
469 // Clean-up the temporary copy (ignoring any errors).
470 DeleteFile(NewName);
471 end else begin
472 // Clean-up the temporary copy (ignoring any errors).
473 RenameFile(NewName,FileName);
474 end;
475 end else begin
476 Result:=True;
477 end;
478 end;
480 procedure CurUninstallStepChanged(CurUninstallStep:TUninstallStep);
482 AppDir,Command,Msg:string;
483 EnvPath,EnvHome:TArrayOfString;
484 i:Longint;
485 RootKey:Integer;
486 begin
487 if CurUninstallStep<>usUninstall then begin
488 Exit;
489 end;
492 Modify the environment
494 This must happen no later than usUninstall to make
495 "ChangesEnvironment=yes" not happend before the change!
498 AppDir:=ExpandConstant('{app}');
500 // Get the current user's directories in PATH.
501 EnvPath:=GetEnvStrings('PATH',IsAdminLoggedOn);
503 // Remove the installation directory from PATH in any case, even if it
504 // was not added by the installer.
505 for i:=0 to GetArrayLength(EnvPath)-1 do begin
506 if Pos(AppDir,EnvPath[i])=1 then begin
507 EnvPath[i]:='';
508 end;
509 end;
511 // Reset the current user's directories in PATH.
512 if not SetEnvStrings('PATH',IsAdminLoggedOn,True,EnvPath) then begin
513 Msg:='Line {#emit __LINE__}: Unable to revert any possible changes to PATH.';
514 MsgBox(Msg,mbError,MB_OK);
515 Log(Msg);
516 // This is not a critical error, the user can probably fix it manually,
517 // so we continue.
518 end;
520 // Reset the current user's HOME if we modified it.
521 EnvHome:=GetEnvStrings('HOME',IsAdminLoggedOn);
522 if (GetArrayLength(EnvHome)=1) and
523 (CompareStr(EnvHome[0],GetIniString('Environment','HOME','',AppDir+'\setup.ini'))=0) then begin
524 if not SetEnvStrings('HOME',IsAdminLoggedOn,True,[]) then begin
525 Msg:='Line {#emit __LINE__}: Unable to revert any possible changes to HOME.';
526 MsgBox(Msg,mbError,MB_OK);
527 Log(Msg);
528 // This is not a critical error, the user can probably fix it manually,
529 // so we continue.
530 end;
531 end;
533 Command:=AppDir+'\setup.ini';
534 if (FileExists(Command) and (not DeleteFile(Command))) then begin
535 Msg:='Line {#emit __LINE__}: Unable to delete file "'+Command+'".';
536 MsgBox(Msg,mbError,MB_OK);
537 Log(Msg);
538 // This is not a critical error, the user can probably fix it manually,
539 // so we continue.
540 end;
543 Delete the Windows Explorer shell extensions
546 if IsAdminLoggedOn then begin
547 RootKey:=HKEY_LOCAL_MACHINE;
548 end else begin
549 RootKey:=HKEY_CURRENT_USER;
550 end;
552 Command:='';
553 RegQueryStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_shell\command','',Command);
554 if Pos(AppDir,Command)>0 then begin
555 if not RegDeleteKeyIncludingSubkeys(RootKey,'SOFTWARE\Classes\Directory\shell\git_shell') then begin
556 Msg:='Line {#emit __LINE__}: Unable to remove "Git Bash Here" shell extension.';
557 MsgBox(Msg,mbError,MB_OK);
558 Log(Msg);
559 // This is not a critical error, the user can probably fix it manually,
560 // so we continue.
561 end;
562 end;
564 Command:='';
565 RegQueryStringValue(RootKey,'SOFTWARE\Classes\Directory\shell\git_gui\command','',Command);
566 if Pos(AppDir,Command)>0 then begin
567 if not RegDeleteKeyIncludingSubkeys(RootKey,'SOFTWARE\Classes\Directory\shell\git_gui') then begin
568 Msg:='Line {#emit __LINE__}: Unable to remove "Git GUI Here" shell extension.';
569 MsgBox(Msg,mbError,MB_OK);
570 Log(Msg);
571 // This is not a critical error, the user can probably fix it manually,
572 // so we continue.
573 end;
574 end;
575 end;