Pull to refresh

Comments 55

Другой подход: myprogram может сама переименовать свой исполняемый файл myprogram.exe в myprogram.bak
После этого скачать обновление в файл с именем myprogram.exe.
А дальше апдейтер просто перезапустит myprogram.exe.
Только лучше сначала скачать новый файл, чтобы не получилась ситуация, когда посередине загрузки, по каким-либо причинам, приложение завершается и пользователь остаётся с .bak файлов и неполным .exe, т.е. без рабочего приложения. Это в варианте, когда myprogram обновляет сама себя, без updater.exe.
Конечно. Лучше сперва скачать, но это уже тонкости реализации :)
Так же возможен немного другой подход:
1. Приложение скачивает обновление и подменяет свой исполняемый файл. После чего само закрывается.
2. Резидентная программа раз в (10, 20, 30, 60, как нам удобнее) секунд проверяет наличие запущенной программы. Если не запущено, то просто запускает из заданной папки.

Получается что после апдейта и выхода, утилита запустит новую версию.
Поэтому для реализации мой метод и использую в приведенном виде.
Файл myprogram скачивает файл, затем запускает другой, чтобы тот сам все переименовал и вновь запустил основную программу.
А можно вопрос.
Как я понял из диалога, программа спокойно может заменить свой exe файл сама.
Почему нельзя сделать так:
1. Скачать новый файл.bak
2. Заменить свой экзешник.
3. Запустить обновлённый файл (при этом запустится вторая копия программы)
4. Завершиться.

Если вдруг нельзя допустить запуска 2 копий одновременно (например какие то ресурсы с монопольным доступом), то можно сделать так.
перед пунктом 3 Создать глобальный mutex «Перезагрузка», а на старте программы проверять его наличие и ждать пока он не исчезнет (породившая его программа, завершилась)
А зачем такие сложности? У меня куда проще — скачал обновление, запустил второй процесс, который заменил файл основной программы, и вновь запустил его, а сам закрылся.
Простите, возможно я чего-то не понимаю, но оба ваших варианта попахивают каким-то адом. Причем, первый ад я узнаю — это Delphi-way. Одна только функция убиения процесса вызывает судорогу.

Зачем другая программа для перезапуска, если есть механизмы OS, которые позволяют выполнить перезапуск самого себя?

Почему-бы не реализовывать загрузку средствами OS, используя тот же BITS? Если уж решились на велосипед, пускай он будет стильным.
Спасибо за наводку, изучу материал и приму решение изменять ли мне код и принцип обновления ПО.
А что делать если Windows есть, а BITS нет? И нужна ли лишняя зависимость от BITS вообще? На счет кривости рук автора — согласен, а вот насет BITS — нет.
Ну, автору хотелось свое и попроще. Только не увидел проверки на контрольную сумму. А если уж хотелось в пару строк и тривиально, можно было обойтись без апдейтера: скачать в %appdata%\%projectname%\updates\application.exe.upd, уйти в рестарт самого себя, при старте чекнуть наличие апдейта по указанному пути, скопировать + переименовать его, запустить с ключом на аннигиляцию родителя и подмену.
Честно сказать, мне мой код проще будет, нежели такой метод.
Возможно, попробую улучшить его, а пока действую по правилу №1: «Работает — НЕ ТРОГАЙ».
Прочитав статью на MSDN, сделал вывод, что приложение устанавливать надо через установщик Windows в то время как указанный метод сразу заменяет файл безо всякой установки. На мой взгляд, этот метод удобней и быстрей. Хотя кому как.
Установщик Windows тоже может заменять файл при апгрейде на новую версию. Также он может заменять несколько файлов и делать это внутри транзакции с возможностью отката. И еще много чего полезного. Советую покопать в сторону Windows Installer в целом и WiX Toolset в частности.
Windows Installer не пройдет по причине того, что конкретный пример статьи основан на модпаке для игры и является лишь лаунчером, а установка самого модпака осуществляется другими средствами. Вряд ли пользователям будет удобно устанавливать модпак и потом дополнительно еще и лаунчер к нему — это не целесообразно.
Ох, с ним много что не так. Особенно доставляет, что встроенный метод CheckForDetailedUpdate для проверки новой версии после определённого числа вызовов кидает COMException из-за ошибки в реализации. Чтобы этого избежать нужно самостоятельно выкачивать манифест приложения и сверять версию программы. И таких граблей там на каждом шагу. Однако, предпочитаю доработать напильником, чем изобретать свой велосипед.
ClickOnce может прижиться при развертывании в локальной сети. Как средство обновления через Интернет — он бяка.
Выбран не самый удачный пример на скриншоте (надеюсь, у вас сделано не так). Показывать пользователю модальное (скорее всего) информационное окно с единственной кнопкой «Ok», не оставляя выбора — не самая хорошая практика. Нужно делать всё тихо и незаметно, либо ненавязчиво предложить обновиться с возможностью сделать это как-нибудь потом, когда пользователю будет удобнее.
Не поверишь — было: тихо скачиваем, тихо устанавливаем, громко перезапускаем прогу без каких-либо вопросов.
Заказчик попросил это уведомление добавить.
А по поводу выбора «обновить сейчас» или «потом» — добавлю такую кнопочку…
Можно тихо скачать, а установить при следующем запуске программы. Тогда не будет громкого неожиданного перезапуска, приводящего пользователя в недоумение.

Хотя чаще всего эту функцию реализуют так, как, например, сделано в Google Chrome или Firefox.
Только подумал про Google Chrome. Хотя у них немного другой алгоритм реализован — в папку скачивается программа, а «стартер» (ака лаунчер браузера) запускает имя клиента которого является самым новым. Можно, конечно, старые файлы удалять, но вот зачем?
В плане браузерной ОС это хорошо, а в моем случае не комильфо.
Сравнение версий у вас неправильное. Проблемы будут, например, при обновлении с 9.12.2 до 10.0.0. Нужно честно нарезать на части по точкам и сравнивать массивы целых чисел.
А что мешает сравнивать непосредственно типы Version? Текущую получить очень просто:
var myVersion = System.Reflection.Assembly.GetEntryAssembly().GetName().Version;

А новую создать:
var xmlVersionText = "9.12.2"; // for test var version = new Version(xmlVersionText);
Незнание мной стандартной библиотеки .NET, очевидно :) Здорово, что такая штука там есть, естественно, лучше использовать встроенные средства, чем городить свои.
Вчера переписал часть кода по проверке версий с использованием System.Version…
Первое что бросилось в глаза — функция работает и также соглашусь со словами:
Незнание мной стандартной библиотеки .NET, очевидно :)
Какой ужасть.
По соглашениям винды у программы не должно быть прав на запись в свою же папку. Если это понадобилось, то нужно проверять, можем ли мы это сделать и если нет, то затребовать админских привилегий. Также невозможно заменить что-то кроме исполняемого файла, отсутствует возможность миграции настроек из старой версии в новую, неправильно сравниваются версии (замечание выше), присутствует «бег» (когда первая программа ещё не успела завершиться, но вспомогательная уже начала пытаться заменить её), нельзя отказаться от обновления и продолжить работу. Это всё так, на быстрый взгляд.
Да и вообще стиль кодирования — 3-й курс, не больше.
Самоучка я) Не в курсе как в ВУЗах преподают. Лично мне хватает, а по замечаниям учту. Спасибо!

По поводу «бега» — вспомогательная сначала проверяет завершился ли процесс, если нет — сама его принудительно выгружает и как только процесс закроется, после этого начинает обработку обновлений.
Зачем конвертить в Double, когда есть нормальный тип System.Version, который поддерживает сравнение?

            System.Version thisVersion = new System.Version(Application.ProductVersion);
            System.Version remoteVersion = new System.Version("1.0.2.37");
            if (remoteVersion > thisVersion)
            { 
                //обновляемся
            }
ваш Double неправильно сработает на версиях «1.2» и «1.1.100», например.
Это верно, хотя в моем случае программы имеют версию, состоящую из 4-х цифр: 0.0.0.0
Как говорится, век живи — век учись :)
Я новичок в данном языке (всего около года пишу), хотя уровня моих знаний вполне хватает для написания стабильных приложений, а улучшать не всегда есть время. Это жаль.
За подсказку спасибо! Сегодня потестирую.
Все интересно и просто. Сам использовал и пользуюсь подобным способом для простых программ (исполняемый файл и пара конфигов).
Но в случае использования более сложных программ (наборы библиотек, как своих так и сторонних и т.д.), нужно очень хорошо следить за целостностью файлов, об этом писалось выше, и такой способ неприемлем, увы.
АДЪ!!!
Так нельзя!
                    Process[] myProcesses2 = Process.GetProcessesByName(process);
                    for (int i = 1; i < myProcesses2.Length; i++) { myProcesses2[i].Kill(); }


Мало того, что вы таким способом можете не убить процесс (закрытие главного окна может не привести к завершению процесса), так ещё и повиснуть этот код может (если, наример, MainWindow будет переоткрываться после закрытия, или, например при попытке закрытия будет выдаваться MessageBox с предложением сохранить результаты работы).
Чтобы окно выдавало сообщение с просьбой сохранить файлы/завершить работу, передается функция
myProcesses[i].CloseMainWindow();

А если именно завершить процесс, то:
myProcesses[0].Kill();

И, как показала практика запуска приложения на нескольких машинах, эта функция работает.
Правда, 1 из 10 раз приложение действительно зависало, предлагая отправить отчет разработчику ОС…
>> Правда, 1 из 10 раз приложение действительно зависало, предлагая отправить отчет разработчику ОС…
Ничего страшного. Мы все так пишем. Главное чтобы не чаще 3-4 раз из 10.
>Так как версия у нас может иметь большое число, используем тип переменной double. Для сравнения версий мы удаляем все точки и конвертируем версию из строки в число (в примере получится число 10237).

Лучше сразу использовать тогда Int64. Те же 8 байт, что и в double, но уже без плавающей точки.
System.Version. Если хочется написать велосипед, то нужно строки с версиями разбить по '.' на четыре токена и сравнивать каждую пару. Но никак не Int64 и тем более double.
Основная программа при запуске проверяет версию updater.exe на сайте и, если найдет, скачивает. Уже после этого проверяет обновления себя самой.

Чуть не забыл: также программа обновляет (а если файлов нет — скачивает) дополнительные библиотеки, необходимые для работы ПО.
Можно updater.exe запаковать в ресурсы программы. При обновлении качается обновленная версия программы, распаковывается updater.exe, дальше всё, как у автора. При запуске программы, если имеется файл updater.exe, то он удаляется.
Мне такой способ больше нравится, так как нет лишних исполняемых файлов для пользователя и есть возможность обновления (хотя и не очень оптимальная) updater.exe.
Можно, но не особо нужно. В любом случае пользователь не сможет сам воспользоваться updater.exe, так как он работает только с определенным набором параметров (указаны в статье). Так что в конкретно моем случае размещение файла updater.exe вне ресурсов основной программы является оптимальным решение.
Хм. использую штатные средства MS VS для публикации проекта.
Там прямо в настройках можно указать, куда лезть за обновлениями, и как регулярно их проверять.
Это полезно при самостоятельном проекте.
Если приложение используется как вторичное, то само устанавливаться оно не будет, вот и используются вышеописанные методы.
В любом случае, любому новичку, я так думаю, данный пример пригодится.
А теперь вы попробуйте обновить программу в папке Program Files в windows vista и старше и слегка удивитесь
Мсье, Вы сильно удивитесь…
Windows 7 x64. Скопировал файл в папку «c:\Program Files\myProgram\» — запустил.
Прога скачала все необходимые библиотеки с сайта, перезапустилась, нашла обновление — скачала, предложила обновиться — согласился. Обновилась, перезапустилась…
… ни единой ошибки! :)
А запуск от имени администратора что ли? Просто из-за виртуализации program files мы получали кучу проблем если запускать просто от пользователя
Просто от пользователя не пробовал. Я админ в системе и являюсь одной учетной записью на компе (из числа вручную созданных) при установке ОС.
Попробую сегодня запустить под обычным юзером на другой машине, сообщу о результатах.
Просто при попытке записи файлов в папке программы реально эти файлы создавались в папке Program data, и если потом запуститься от администратора или от другого пользователя их не было видно.
Просто от пользователя не пробовал. Я админ в системе
Windows 7 x64
UAC, видимо, отключили?
Совершенно верно, сразу же после установки ОС.
Ну что ж, вы сами себе злобный Буратино ;)
Позвольте только поинтересоваться, а зачем?
Позвольте узнать у Вас, зачем он нужен сисадмину со стажем? :)
такой первоапрельский комментарий…
Правда, код в данной статье до вчерашней правки. После правки некоторая его часть выглядит так
Sign up to leave a comment.

Articles