diff --git a/src/ElectronicPharmacy/Classes/AutoUpdater.cs b/src/ElectronicPharmacy/Classes/AutoUpdater.cs index 7bda982..45d6542 100644 --- a/src/ElectronicPharmacy/Classes/AutoUpdater.cs +++ b/src/ElectronicPharmacy/Classes/AutoUpdater.cs @@ -31,7 +31,6 @@ namespace Электронная_Фармация.Classes if (string.IsNullOrWhiteSpace(downloadUrl) || !downloadUrl.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) { - // Prefer zipball if asset missing if (!string.IsNullOrWhiteSpace(downloadUrl) && !downloadUrl.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) { @@ -75,10 +74,14 @@ namespace Электронная_Фармация.Classes throw new InvalidOperationException("В архиве не найден exe приложения."); } + var needsElevation = !IsDirectoryWritable(installDir); var updaterBat = Path.Combine(workRoot, "apply-update.cmd"); WriteUpdaterScript(updaterBat); - Report(status, "Подготовка перезапуска..."); + Report(status, needsElevation + ? "Нужны права администратора для установки в Program Files..." + : "Подготовка перезапуска..."); + var psi = new ProcessStartInfo { FileName = updaterBat, @@ -86,13 +89,31 @@ namespace Электронная_Фармация.Classes Quote(Path.GetFileName(currentExe)) + " " + Process.GetCurrentProcess().Id, UseShellExecute = true, - WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = workRoot, - CreateNoWindow = true + WindowStyle = needsElevation ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden, + CreateNoWindow = !needsElevation }; - Process.Start(psi); + if (needsElevation) + { + // UAC prompt — без этого robocopy в Program Files молча не заменяет exe. + psi.Verb = "runas"; + } + + try + { + Process.Start(psi); + } + catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 1223) + { + // ERROR_CANCELLED — пользователь отклонил UAC + throw new InvalidOperationException( + "Обновление отменено: нужны права администратора " + + "(программа установлена в Program Files).", + ex); + } + AppDebugLog.Info("AutoUpdater", - $"Updater started. payload={payloadDir}, install={installDir}, ver={update.LatestVersion}"); + $"Updater started. elevate={needsElevation}, payload={payloadDir}, install={installDir}, ver={update.LatestVersion}"); } catch { @@ -112,6 +133,21 @@ namespace Электронная_Фармация.Classes } } + private static bool IsDirectoryWritable(string directory) + { + try + { + var probe = Path.Combine(directory, ".elfisa-write-" + Guid.NewGuid().ToString("N")); + File.WriteAllText(probe, "ok"); + File.Delete(probe); + return true; + } + catch + { + return false; + } + } + private static async Task DownloadAsync(string url, string zipPath) { using (var client = new HttpClient { Timeout = TimeSpan.FromMinutes(10) }) @@ -143,7 +179,8 @@ namespace Электронная_Фармация.Classes { var name = Path.GetFileName(p) ?? string.Empty; return !name.EndsWith(".vshost.exe", StringComparison.OrdinalIgnoreCase) && - !name.Equals("UpdateCheckSmoke.exe", StringComparison.OrdinalIgnoreCase); + !name.Equals("UpdateCheckSmoke.exe", StringComparison.OrdinalIgnoreCase) && + !name.StartsWith("unins", StringComparison.OrdinalIgnoreCase); }); return exe == null ? null : Path.GetDirectoryName(exe); @@ -151,7 +188,8 @@ namespace Электронная_Фармация.Classes private static void WriteUpdaterScript(string path) { - // Waits for app exit (max ~30s, then force-kill), copies files, keeps DB/Logs, restarts. + // Waits for app exit, force-kills leftovers, renames locked exe, + // copies with robocopy, verifies, restarts. Log: %TEMP%\elfisa-updater.log var sb = new StringBuilder(); sb.AppendLine("@echo off"); sb.AppendLine("setlocal EnableDelayedExpansion"); @@ -159,28 +197,49 @@ namespace Электронная_Фармация.Classes sb.AppendLine("set \"DST=%~2\""); sb.AppendLine("set \"EXENAME=%~3\""); sb.AppendLine("set \"PID=%~4\""); + sb.AppendLine("set \"LOG=%TEMP%\\elfisa-updater.log\""); + sb.AppendLine("echo ===== %DATE% %TIME% ===== > \"%LOG%\""); + sb.AppendLine("echo SRC=%SRC%>> \"%LOG%\""); + sb.AppendLine("echo DST=%DST%>> \"%LOG%\""); + sb.AppendLine("echo EXE=%EXENAME% PID=%PID%>> \"%LOG%\""); sb.AppendLine("set \"N=0\""); - sb.AppendLine("echo Waiting for PID %PID% ... > \"%TEMP%\\elfisa-updater.log\""); + sb.AppendLine("echo Waiting for PID %PID% ... >> \"%LOG%\""); sb.AppendLine(":waitloop"); sb.AppendLine("tasklist /FI \"PID eq %PID%\" 2>nul | findstr /C:\" %PID% \" >nul"); sb.AppendLine("if errorlevel 1 goto donewait"); sb.AppendLine("set /a N+=1"); - sb.AppendLine("echo still alive n=!N! >> \"%TEMP%\\elfisa-updater.log\""); - sb.AppendLine("if !N! GEQ 30 ("); - sb.AppendLine(" echo force kill %PID% >> \"%TEMP%\\elfisa-updater.log\""); + sb.AppendLine("if !N! GEQ 40 ("); + sb.AppendLine(" echo force kill PID %PID% >> \"%LOG%\""); sb.AppendLine(" taskkill /PID %PID% /F >nul 2>&1"); sb.AppendLine(" goto donewait"); sb.AppendLine(")"); - // ping instead of timeout — timeout often hangs in hidden windows sb.AppendLine("ping 127.0.0.1 -n 2 >nul"); sb.AppendLine("goto waitloop"); sb.AppendLine(":donewait"); sb.AppendLine("ping 127.0.0.1 -n 2 >nul"); - sb.AppendLine("echo Copying update... >> \"%TEMP%\\elfisa-updater.log\""); - sb.AppendLine("robocopy \"%SRC%\" \"%DST%\" /E /R:2 /W:1 /NFL /NDL /NJH /NJS /XF efClient.db *.db /XD Logs"); + // Kill any leftover instances by image name (locks Program Files exe). + sb.AppendLine("echo taskkill by name >> \"%LOG%\""); + sb.AppendLine("taskkill /F /IM \"%EXENAME%\" >nul 2>&1"); + sb.AppendLine("ping 127.0.0.1 -n 2 >nul"); + // Rename locked/old exe so copy can proceed even if handle lingered. + sb.AppendLine("if exist \"%DST%\\%EXENAME%.old\" del /F /Q \"%DST%\\%EXENAME%.old\" >nul 2>&1"); + sb.AppendLine("if exist \"%DST%\\%EXENAME%\" ("); + sb.AppendLine(" move /Y \"%DST%\\%EXENAME%\" \"%DST%\\%EXENAME%.old\" >> \"%LOG%\" 2>&1"); + sb.AppendLine(")"); + sb.AppendLine("echo Copying update... >> \"%LOG%\""); + // /IS /IT — force overwrite even if timestamps look the same + sb.AppendLine("robocopy \"%SRC%\" \"%DST%\" /E /IS /IT /R:5 /W:1 /NFL /NDL /NJH /NJS /XF efClient.db *.db /XD Logs"); sb.AppendLine("set \"RC=!ERRORLEVEL!\""); - sb.AppendLine("echo robocopy=!RC! >> \"%TEMP%\\elfisa-updater.log\""); - sb.AppendLine("echo Starting \"%DST%\\%EXENAME%\" >> \"%TEMP%\\elfisa-updater.log\""); + sb.AppendLine("echo robocopy=!RC! >> \"%LOG%\""); + sb.AppendLine("if not exist \"%DST%\\%EXENAME%\" ("); + sb.AppendLine(" echo COPY FAILED — restoring old exe >> \"%LOG%\""); + sb.AppendLine(" if exist \"%DST%\\%EXENAME%.old\" move /Y \"%DST%\\%EXENAME%.old\" \"%DST%\\%EXENAME%\" >nul 2>&1"); + sb.AppendLine(" echo Не удалось заменить файлы программы. См. %TEMP%\\elfisa-updater.log"); + sb.AppendLine(" pause"); + sb.AppendLine(" exit /b 1"); + sb.AppendLine(")"); + sb.AppendLine("if exist \"%DST%\\%EXENAME%.old\" del /F /Q \"%DST%\\%EXENAME%.old\" >nul 2>&1"); + sb.AppendLine("echo Starting \"%DST%\\%EXENAME%\" >> \"%LOG%\""); sb.AppendLine("start \"\" /D \"%DST%\" \"%DST%\\%EXENAME%\""); sb.AppendLine("exit /b 0"); File.WriteAllText(path, sb.ToString(), Encoding.Default); diff --git a/src/ElectronicPharmacy/Properties/AssemblyInfo.cs b/src/ElectronicPharmacy/Properties/AssemblyInfo.cs index 1090112..d183d3c 100644 --- a/src/ElectronicPharmacy/Properties/AssemblyInfo.cs +++ b/src/ElectronicPharmacy/Properties/AssemblyInfo.cs @@ -32,6 +32,6 @@ using System.Runtime.InteropServices; // пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ // пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ "*", пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.21.0")] -[assembly: AssemblyFileVersion("1.0.21.0")] +[assembly: AssemblyVersion("1.0.22.0")] +[assembly: AssemblyFileVersion("1.0.22.0")]