Release v1.0.22: fix auto-update loop on Program Files installs.
Auto-updater now requests UAC elevation, force-unlocks the exe, and verifies the copy before restart. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2659de14b9
commit
d2a1a86794
@ -31,7 +31,6 @@ namespace Электронная_Фармация.Classes
|
|||||||
if (string.IsNullOrWhiteSpace(downloadUrl) ||
|
if (string.IsNullOrWhiteSpace(downloadUrl) ||
|
||||||
!downloadUrl.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
!downloadUrl.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Prefer zipball if asset missing
|
|
||||||
if (!string.IsNullOrWhiteSpace(downloadUrl) &&
|
if (!string.IsNullOrWhiteSpace(downloadUrl) &&
|
||||||
!downloadUrl.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
!downloadUrl.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@ -75,10 +74,14 @@ namespace Электронная_Фармация.Classes
|
|||||||
throw new InvalidOperationException("В архиве не найден exe приложения.");
|
throw new InvalidOperationException("В архиве не найден exe приложения.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var needsElevation = !IsDirectoryWritable(installDir);
|
||||||
var updaterBat = Path.Combine(workRoot, "apply-update.cmd");
|
var updaterBat = Path.Combine(workRoot, "apply-update.cmd");
|
||||||
WriteUpdaterScript(updaterBat);
|
WriteUpdaterScript(updaterBat);
|
||||||
|
|
||||||
Report(status, "Подготовка перезапуска...");
|
Report(status, needsElevation
|
||||||
|
? "Нужны права администратора для установки в Program Files..."
|
||||||
|
: "Подготовка перезапуска...");
|
||||||
|
|
||||||
var psi = new ProcessStartInfo
|
var psi = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = updaterBat,
|
FileName = updaterBat,
|
||||||
@ -86,13 +89,31 @@ namespace Электронная_Фармация.Classes
|
|||||||
Quote(Path.GetFileName(currentExe)) + " " +
|
Quote(Path.GetFileName(currentExe)) + " " +
|
||||||
Process.GetCurrentProcess().Id,
|
Process.GetCurrentProcess().Id,
|
||||||
UseShellExecute = true,
|
UseShellExecute = true,
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
|
||||||
WorkingDirectory = workRoot,
|
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",
|
AppDebugLog.Info("AutoUpdater",
|
||||||
$"Updater started. payload={payloadDir}, install={installDir}, ver={update.LatestVersion}");
|
$"Updater started. elevate={needsElevation}, payload={payloadDir}, install={installDir}, ver={update.LatestVersion}");
|
||||||
}
|
}
|
||||||
catch
|
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)
|
private static async Task DownloadAsync(string url, string zipPath)
|
||||||
{
|
{
|
||||||
using (var client = new HttpClient { Timeout = TimeSpan.FromMinutes(10) })
|
using (var client = new HttpClient { Timeout = TimeSpan.FromMinutes(10) })
|
||||||
@ -143,7 +179,8 @@ namespace Электронная_Фармация.Classes
|
|||||||
{
|
{
|
||||||
var name = Path.GetFileName(p) ?? string.Empty;
|
var name = Path.GetFileName(p) ?? string.Empty;
|
||||||
return !name.EndsWith(".vshost.exe", StringComparison.OrdinalIgnoreCase) &&
|
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);
|
return exe == null ? null : Path.GetDirectoryName(exe);
|
||||||
@ -151,7 +188,8 @@ namespace Электронная_Фармация.Classes
|
|||||||
|
|
||||||
private static void WriteUpdaterScript(string path)
|
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();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("@echo off");
|
sb.AppendLine("@echo off");
|
||||||
sb.AppendLine("setlocal EnableDelayedExpansion");
|
sb.AppendLine("setlocal EnableDelayedExpansion");
|
||||||
@ -159,28 +197,49 @@ namespace Электронная_Фармация.Classes
|
|||||||
sb.AppendLine("set \"DST=%~2\"");
|
sb.AppendLine("set \"DST=%~2\"");
|
||||||
sb.AppendLine("set \"EXENAME=%~3\"");
|
sb.AppendLine("set \"EXENAME=%~3\"");
|
||||||
sb.AppendLine("set \"PID=%~4\"");
|
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("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(":waitloop");
|
||||||
sb.AppendLine("tasklist /FI \"PID eq %PID%\" 2>nul | findstr /C:\" %PID% \" >nul");
|
sb.AppendLine("tasklist /FI \"PID eq %PID%\" 2>nul | findstr /C:\" %PID% \" >nul");
|
||||||
sb.AppendLine("if errorlevel 1 goto donewait");
|
sb.AppendLine("if errorlevel 1 goto donewait");
|
||||||
sb.AppendLine("set /a N+=1");
|
sb.AppendLine("set /a N+=1");
|
||||||
sb.AppendLine("echo still alive n=!N! >> \"%TEMP%\\elfisa-updater.log\"");
|
sb.AppendLine("if !N! GEQ 40 (");
|
||||||
sb.AppendLine("if !N! GEQ 30 (");
|
sb.AppendLine(" echo force kill PID %PID% >> \"%LOG%\"");
|
||||||
sb.AppendLine(" echo force kill %PID% >> \"%TEMP%\\elfisa-updater.log\"");
|
|
||||||
sb.AppendLine(" taskkill /PID %PID% /F >nul 2>&1");
|
sb.AppendLine(" taskkill /PID %PID% /F >nul 2>&1");
|
||||||
sb.AppendLine(" goto donewait");
|
sb.AppendLine(" goto donewait");
|
||||||
sb.AppendLine(")");
|
sb.AppendLine(")");
|
||||||
// ping instead of timeout — timeout often hangs in hidden windows
|
|
||||||
sb.AppendLine("ping 127.0.0.1 -n 2 >nul");
|
sb.AppendLine("ping 127.0.0.1 -n 2 >nul");
|
||||||
sb.AppendLine("goto waitloop");
|
sb.AppendLine("goto waitloop");
|
||||||
sb.AppendLine(":donewait");
|
sb.AppendLine(":donewait");
|
||||||
sb.AppendLine("ping 127.0.0.1 -n 2 >nul");
|
sb.AppendLine("ping 127.0.0.1 -n 2 >nul");
|
||||||
sb.AppendLine("echo Copying update... >> \"%TEMP%\\elfisa-updater.log\"");
|
// Kill any leftover instances by image name (locks Program Files exe).
|
||||||
sb.AppendLine("robocopy \"%SRC%\" \"%DST%\" /E /R:2 /W:1 /NFL /NDL /NJH /NJS /XF efClient.db *.db /XD Logs");
|
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("set \"RC=!ERRORLEVEL!\"");
|
||||||
sb.AppendLine("echo robocopy=!RC! >> \"%TEMP%\\elfisa-updater.log\"");
|
sb.AppendLine("echo robocopy=!RC! >> \"%LOG%\"");
|
||||||
sb.AppendLine("echo Starting \"%DST%\\%EXENAME%\" >> \"%TEMP%\\elfisa-updater.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("start \"\" /D \"%DST%\" \"%DST%\\%EXENAME%\"");
|
||||||
sb.AppendLine("exit /b 0");
|
sb.AppendLine("exit /b 0");
|
||||||
File.WriteAllText(path, sb.ToString(), Encoding.Default);
|
File.WriteAllText(path, sb.ToString(), Encoding.Default);
|
||||||
|
|||||||
@ -32,6 +32,6 @@ using System.Runtime.InteropServices;
|
|||||||
// пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ
|
// пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ
|
||||||
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ "*", пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ:
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ "*", пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.0.21.0")]
|
[assembly: AssemblyVersion("1.0.22.0")]
|
||||||
[assembly: AssemblyFileVersion("1.0.21.0")]
|
[assembly: AssemblyFileVersion("1.0.22.0")]
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user