@@ -42,7 +42,7 @@ ArchitecturesInstallIn64BitMode=x64compatible
4242ArchitecturesAllowed = x64compatible
4343WizardImageStretch = no
4444WindowResizable = no
45- CloseApplications = yes
45+ CloseApplications = no
4646ChangesEnvironment = yes
4747RestartIfNeededByRun = yes
4848
@@ -59,8 +59,14 @@ Name: "full"; Description: "Full installation"; Flags: iscustom;
5959Type : files ; Name : " {app} \ucrtbase.dll"
6060
6161[Files]
62- DestDir : " {app} " ; Flags : ignoreversion recursesubdirs ; Source :" {#LayoutDir}\*"
63- DestDir : " {app} " ; Flags : ignoreversion ; Source :" {#LayoutDir}\GVFS.Service.exe" ; AfterInstall : InstallGVFSService
62+ ; Normal install: all files go to {app}, service gets AfterInstall callback
63+ DestDir : " {app} " ; Flags : ignoreversion recursesubdirs ; Source :" {#LayoutDir}\*" ; Check : IsNormalInstall
64+ DestDir : " {app} " ; Flags : ignoreversion ; Source :" {#LayoutDir}\GVFS.Service.exe" ; AfterInstall : InstallGVFSService; Check : IsNormalInstall
65+ ; Staging install: most files go to {app}\PendingUpgrade, but GVFS.Service.exe
66+ ; goes directly to {app} so the restarted service has PendingUpgradeHandler code.
67+ ; The service is briefly stopped/restarted (mounts are independent processes).
68+ DestDir : " {app} \PendingUpgrade" ; Flags : ignoreversion recursesubdirs ; Source :" {#LayoutDir}\*" ; Check : IsStagingInstall
69+ DestDir : " {app} " ; Flags : ignoreversion ; Source :" {#LayoutDir}\GVFS.Service.exe" ; AfterInstall : StagingUpdateService; Check : IsStagingInstall
6470
6571[Dirs]
6672Name : " {app} \ProgramData\{#ServiceName}" ; Permissions: users-readexec
@@ -84,6 +90,17 @@ Root: HKLM; SubKey: "{#GvFltAutologgerKey}"; Flags: deletekey
8490[Code]
8591var
8692 ExitCode: Integer;
93+ KeepMountsRunning: Boolean;
94+
95+ function IsNormalInstall (): Boolean;
96+ begin
97+ Result := not KeepMountsRunning;
98+ end ;
99+
100+ function IsStagingInstall (): Boolean;
101+ begin
102+ Result := KeepMountsRunning;
103+ end ;
87104
88105function NeedsAddPath (Param: string): boolean;
89106var
@@ -156,6 +173,55 @@ begin
156173 end ;
157174end ;
158175
176+ procedure WaitForServiceProcessToExit (ServiceName: string);
177+ var
178+ ResultCode: integer;
179+ Attempts: integer;
180+ TempFile: string;
181+ QueryOutput: ansiString;
182+ begin
183+ // sc stop/delete returns before the service process actually exits.
184+ // Poll sc query until the service is fully gone (1060) or stopped.
185+ Attempts := 0 ;
186+ TempFile := ExpandConstant(' {tmp}\~scquery.txt' );
187+ while Attempts < 30 do
188+ begin
189+ if Exec(ExpandConstant(' {cmd}' ), ' /C "' + ExpandConstant(' {sys}\SC.EXE' ) + ' " query ' + ServiceName + ' > "' + TempFile + ' " 2>&1' , ' ' , SW_HIDE, ewWaitUntilTerminated, ResultCode) then
190+ begin
191+ // 1060 = service does not exist (fully deleted and process exited)
192+ if ResultCode = 1060 then
193+ begin
194+ Log(' WaitForServiceProcessToExit: Service no longer exists' );
195+ break;
196+ end ;
197+ if LoadStringFromFile(TempFile, QueryOutput) then
198+ begin
199+ if Pos(' STOPPED' , QueryOutput) > 0 then
200+ begin
201+ Log(' WaitForServiceProcessToExit: Service is stopped' );
202+ break;
203+ end ;
204+ end ;
205+ end
206+ else
207+ begin
208+ Log(' WaitForServiceProcessToExit: sc query failed, assuming service is gone' );
209+ break;
210+ end ;
211+ Attempts := Attempts + 1 ;
212+ Log(' WaitForServiceProcessToExit: Waiting for service to stop (attempt ' + IntToStr(Attempts) + ' )' );
213+ Sleep(1000 );
214+ end ;
215+ if Attempts >= 30 then
216+ begin
217+ if LoadStringFromFile(TempFile, QueryOutput) then
218+ Log(' WaitForServiceProcessToExit: Timed out. Last sc query output: ' + QueryOutput)
219+ else
220+ Log(' WaitForServiceProcessToExit: Timed out waiting for service to stop' );
221+ end ;
222+ DeleteFile(TempFile);
223+ end ;
224+
159225procedure UninstallService (ServiceName: string; ShowProgress: boolean);
160226var
161227 ResultCode: integer;
@@ -178,6 +244,8 @@ begin
178244 RaiseException(' Fatal: Could not uninstall service: ' + ServiceName);
179245 end ;
180246
247+ WaitForServiceProcessToExit(ServiceName);
248+
181249 if (ShowProgress) then
182250 begin
183251 WizardForm.StatusLabel.Caption := ' Waiting for pending ' + ServiceName + ' deletion to complete. This may take a while.' ;
@@ -245,6 +313,33 @@ begin
245313 end ;
246314end ;
247315
316+ procedure StagingUpdateService ();
317+ var
318+ ResultCode: integer;
319+ StatusText: string;
320+ begin
321+ // In staging mode: the service was stopped in PrepareToInstall so its exe
322+ // could be replaced. Now start it with the new binary. The new service has
323+ // PendingUpgradeHandler which will complete the upgrade on next restart
324+ // when no mounts are running.
325+ StatusText := WizardForm.StatusLabel.Caption;
326+ WizardForm.StatusLabel.Caption := ' Starting GVFS.Service.' ;
327+ WizardForm.ProgressGauge.Style := npbstMarquee;
328+
329+ try
330+ Log(' StagingUpdateService: Starting service with new binary' );
331+ if not Exec(ExpandConstant(' {sys}\SC.EXE' ), ' start GVFS.Service' , ' ' , SW_HIDE, ewWaitUntilTerminated, ResultCode) then
332+ begin
333+ Log(' StagingUpdateService: Warning - could not start service: ' + SysErrorMessage(ResultCode));
334+ end ;
335+
336+ WriteOnDiskVersion16CapableFile();
337+ finally
338+ WizardForm.StatusLabel.Caption := StatusText;
339+ WizardForm.ProgressGauge.Style := npbstNormal;
340+ end ;
341+ end ;
342+
248343function DeleteFileIfItExists (FilePath: string) : Boolean;
249344begin
250345 Result := False;
@@ -485,39 +580,6 @@ begin
485580 MigrateFile(CommonAppDataDir + ' \{#ServiceName}\{#GVFSStatuscacheTokenFileName}' , SecureAppDataDir + ' \{#ServiceName}\{#GVFSStatuscacheTokenFileName}' );
486581end ;
487582
488- function ConfirmUnmountAll (): Boolean;
489- var
490- MsgBoxResult: integer;
491- Repos: ansiString;
492- ResultCode: integer;
493- MsgBoxText: string;
494- begin
495- Result := False;
496- if ExecWithResult(' gvfs.exe' , ' service --list-mounted' , ' ' , SW_HIDE, ewWaitUntilTerminated, ResultCode, Repos) then
497- begin
498- if Repos = ' ' then
499- begin
500- Result := False;
501- end
502- else
503- begin
504- if ResultCode = 0 then
505- begin
506- MsgBoxText := ' The following repos are currently mounted:' + #13 #10 + Repos + #13 #10 + ' Setup needs to unmount all repos before it can proceed, and those repos will be unavailable while setup is running. Do you want to continue?' ;
507- MsgBoxResult := SuppressibleMsgBox(MsgBoxText, mbConfirmation, MB_OKCANCEL, IDOK);
508- if (MsgBoxResult = IDOK) then
509- begin
510- Result := True;
511- end
512- else
513- begin
514- Abort();
515- end ;
516- end ;
517- end ;
518- end ;
519- end ;
520-
521583function EnsureGvfsNotRunning (): Boolean;
522584var
523585 MsgBoxResult: integer;
@@ -647,12 +709,21 @@ begin
647709 case CurStep of
648710 ssInstall:
649711 begin
650- UninstallService(' GVFS.Service' , True);
712+ if not KeepMountsRunning then
713+ UninstallService(' GVFS.Service' , True);
651714 end ;
652715 ssPostInstall:
653716 begin
717+ if KeepMountsRunning then
718+ begin
719+ // All staged files have been written to PendingUpgrade.
720+ // Write .ready marker so the service knows the staging is
721+ // complete and safe to apply.
722+ SaveStringToFile(ExpandConstant(' {app}\PendingUpgrade\.ready' ), ' ' , False);
723+ Log(' CurStepChanged: Wrote PendingUpgrade .ready marker' );
724+ end ;
654725 MigrateConfigAndStatusCacheFiles();
655- if ExpandConstant(' {param:REMOUNTREPOS|true}' ) = ' true' then
726+ if ( not KeepMountsRunning) and ( ExpandConstant(' {param:REMOUNTREPOS|true}' ) = ' true' ) then
656727 begin
657728 MountRepos();
658729 end
@@ -677,22 +748,125 @@ begin
677748end ;
678749
679750function PrepareToInstall (var NeedsRestart: Boolean): String;
751+ var
752+ MsgBoxResult: integer;
753+ Repos: ansiString;
754+ ResultCode: integer;
755+ HasMounts: Boolean;
680756begin
681757 NeedsRestart := False;
758+ KeepMountsRunning := False;
682759 Result := ' ' ;
683760 SetNuGetFeedIfNecessary();
684- if ConfirmUnmountAll() then
761+
762+ // Check for mounted repos by querying the service, and also check for
763+ // running GVFS processes (a mount can be running without being registered
764+ // in the service's repo-registry, e.g., after a reinstall).
765+ HasMounts := False;
766+ if ExecWithResult(' gvfs.exe' , ' service --list-mounted' , ' ' , SW_HIDE, ewWaitUntilTerminated, ResultCode, Repos) then
767+ begin
768+ if (ResultCode = 0 ) and (Repos <> ' ' ) then
769+ HasMounts := True;
770+ end ;
771+ if (not HasMounts) and IsGVFSRunning() then
685772 begin
686- if ExpandConstant(' {param:REMOUNTREPOS|true}' ) = ' true' then
773+ HasMounts := True;
774+ Repos := ' (GVFS processes detected)' ;
775+ Log(' PrepareToInstall: No registered mounts but GVFS processes are running' );
776+ end ;
777+
778+ if HasMounts then
779+ begin
780+ if WizardSilent() then
687781 begin
688- UnmountRepos();
782+ // Silent mode: STAGEIFMOUNTED=true stages files instead of unmounting.
783+ // Default: false (clean upgrade, matching pre-existing behavior).
784+ KeepMountsRunning := ExpandConstant(' {param:STAGEIFMOUNTED|false}' ) = ' true' ;
785+ if KeepMountsRunning then
786+ Log(' PrepareToInstall: Silent mode with mounted repos, KeepMountsRunning=True' )
787+ else
788+ Log(' PrepareToInstall: Silent mode with mounted repos, KeepMountsRunning=False' );
689789 end
790+ else
791+ begin
792+ // Interactive mode: let user choose
793+ MsgBoxResult := SuppressibleMsgBox(
794+ ' The following repos are currently mounted:' + #13 #10 + Repos + #13 #10 #13 #10 +
795+ ' Click Yes to keep repos mounted during the upgrade.' + #13 #10 +
796+ ' The upgrade will complete automatically when all repos are unmounted.' + #13 #10 #13 #10 +
797+ ' Click No to unmount all repos now and upgrade without restart.' + #13 #10 +
798+ ' Repos will be temporarily unavailable during the upgrade.' ,
799+ mbConfirmation, MB_YESNOCANCEL, IDYES);
800+ if MsgBoxResult = IDYES then
801+ KeepMountsRunning := True
802+ else if MsgBoxResult = IDNO then
803+ KeepMountsRunning := False
804+ else
805+ begin
806+ Result := ' Installation cancelled.' ;
807+ exit;
808+ end ;
809+ end ;
690810 end ;
691- if not EnsureGvfsNotRunning() then
811+
812+ if KeepMountsRunning then
692813 begin
693- Abort();
814+ // Staging mode: most files go to {app}\PendingUpgrade\ via [Files] entries
815+ // with Check: IsStagingInstall. GVFS.Service.exe goes directly to {app}.
816+ // Clean up any leftover staging dirs from a prior attempt first,
817+ // so we don't mix files from different upgrade versions.
818+ if DirExists(ExpandConstant(' {app}\PendingUpgrade' )) then
819+ begin
820+ Log(' PrepareToInstall: Removing stale PendingUpgrade from prior staging attempt' );
821+ DelTree(ExpandConstant(' {app}\PendingUpgrade' ), True, True, True);
822+ end ;
823+ if DirExists(ExpandConstant(' {app}\PreviousVersion' )) then
824+ begin
825+ Log(' PrepareToInstall: Removing stale PreviousVersion from prior staging attempt' );
826+ DelTree(ExpandConstant(' {app}\PreviousVersion' ), True, True, True);
827+ end ;
828+ // Stop the service now so its exe is unlocked for replacement.
829+ // Mounts are independent processes and unaffected.
830+ Log(' PrepareToInstall: Staging mode. Stopping service for exe replacement.' );
831+ StopService(' GVFS.Service' );
832+ WaitForServiceProcessToExit(' GVFS.Service' );
833+ end
834+ else
835+ begin
836+ // Clean upgrade: unmount, stop everything, replace files directly.
837+ // Remove any leftover PendingUpgrade or PreviousVersion from a
838+ // previous staging install so stale files don't interfere with
839+ // the fresh install.
840+ if DirExists(ExpandConstant(' {app}\PendingUpgrade' )) then
841+ begin
842+ Log(' PrepareToInstall: Removing leftover PendingUpgrade directory' );
843+ DelTree(ExpandConstant(' {app}\PendingUpgrade' ), True, True, True);
844+ end ;
845+ if DirExists(ExpandConstant(' {app}\PreviousVersion' )) then
846+ begin
847+ Log(' PrepareToInstall: Removing leftover PreviousVersion directory' );
848+ DelTree(ExpandConstant(' {app}\PreviousVersion' ), True, True, True);
849+ end ;
850+ if HasMounts then
851+ begin
852+ UnmountRepos();
853+ end ;
854+ // With CloseApplications=no, Restart Manager won't kill GVFS
855+ // processes. If unmount-all didn't clean up everything (e.g.
856+ // registry was empty), force-kill remaining processes since
857+ // the user already consented to a full upgrade.
858+ if IsGVFSRunning() then
859+ begin
860+ Log(' PrepareToInstall: GVFS processes still running after unmount, force-killing' );
861+ Exec(' powershell.exe' , ' -NoProfile "Get-Process gvfs,gvfs.mount -ErrorAction SilentlyContinue | Stop-Process -Force"' , ' ' , SW_HIDE, ewWaitUntilTerminated, ResultCode);
862+ Sleep(2000 );
863+ end ;
864+ if not EnsureGvfsNotRunning() then
865+ begin
866+ Abort();
867+ end ;
868+ StopService(' GVFS.Service' );
869+ UninstallGvFlt();
870+ UninstallProjFSIfNecessary();
694871 end ;
695- StopService(' GVFS.Service' );
696- UninstallGvFlt();
697- UninstallProjFSIfNecessary();
698872end ;
0 commit comments