Автообновление приложений .NET

В этой статье рассматривается два наиболее простых способа реализации автообновления приложения .Net. Первый способ — с помощью стандартной технологии Microsoft — Click Once, второй — с помощью опенсорного компонента NET Application Updater Component.

Обновление с помощью ClickOnce

Процесс использования ClickOnce описан в этой статье. Однако, установка с помощью этой технология делает неправильный мед» не позволяет установить программу для всех пользователей, поэтому переходим к следующему пункту.

Сразу оговорюсь, что в итоге в Windows 7 хранение в папке ProgramData требует администраторских прав и в итоге, описанное здесь решение можно рассматривать только в качестве собственной реализации автообновления, исходный код которого всегда можно изменить для своих нужд.

Обновление с помощью NET Application Updater Component

Компонент явно сырой, но с определенными улучшениями и исправлениями, его можно использовать.

1. Создаем новый проект Windows Forms, например с именем MySimpleSample

2. Добавляем проекты AppStart и AppUpdater. Добавляем Reference на на проект AppUpdater. В AppUpdater есть ссылка на класс AppStartConfig, лежащий в проекте AppStart, поэтому проекты AppStart и AppUpdater лучше положить на одном уровне файловой структуры.

3. Добавляем на форму компонент AppUpdater ( Со окна Toolbox-AppUpdater Components). Тут были некоторые проблемы, скорее всего это связано с обновлением проекта до Visual Studio 2010 . UpdateLog хочет писать в файл AppUpdate.log, но текущим каталогом, возвращаемым функцией GetLogFilePath является путь к Visual Studio, куда, естественно запись запрещена. Можно поправить выбор каталога на что-нибудь такое :

DirectoryInfo DI = new DirectoryInfo(Assembly.GetCallingAssembly().Location);

В классе AppDownloader нас будет ожидать еще один сюрприз — захардкоженное имя конфига «AppStart.config». Явная недоделка — исправляем хотя бы на константу для централизации.

4. Приложением для запуска будет AppStart.exe, которое будет читать конфигурацию из файла AppStart.config. Предполагается, что версии приложении складываются в папки, соответствующие версии программы, например SampleApp_ClientSetup1.0.0.0

Момент с созданием конфига абсолютно непродуман, равно как и то, что автообновление будет требовать администраторских прав случае, если записывать в папку, находящуюся в Program Files. Но об этом позже.

<Config>
  <AppFolderName>1.0.0.0</AppFolderName>
  <AppExeName>SimpleSample.exe</AppExeName>
</Config>

5. Запускаем TestApp. И тут самое интересное — из Visual Studio все работает, а если просто запустить TestApp.exe, то получаем исключение: Configuration file AppStart.config does not have root tag. Разбираться почему работает из студии, тут небольшая дискуссия, смысл же простой, наш собственный конфиг считывается .NET как системный конфиг приложения. Самый простой вариант — как здесь, изменить расширение файла конфигурации.

6. Теперь к самому процессу обновления. На выбор предлагаются 2 метода (поле ChangeDetectionMode):

  • DirectFileCheck — сверка версий файлов напрямую. В этом случае в UpdateUrl указывается папка с файлами программы. Изначально рассчитано на использование HTTP-сервера(IIS) или Web Dav папками, но я добавил возможность использования обычной файловой системы — для того, чтобы можно было выкладывать обновления на сетевой ресурс (расшаренную папку). Обновление запустится, если найдутся файлы с более новой датой.
  • ServerManifestCheck — производится проверка текущей сборки и версии, указанной в конфигурационном файле — доступной для обновления.

7. Что-то нужно сделать с путями — используется ворох функций определения каталога, привязанных к программе, а также не пока непонятен процесс развертывания приложения. Я перенес функцию LoadConfig, проверяющую несколько путей для конфигурационного файла в класс AppStartConfig, так как в AppDownloader почему-то использовалась своя версия определения пути, что не может быть хорошо.
Хочется, чтобы обновляемые файлы хранились в каталоге пользователя, например, как это делает Google Chrome — в этом случае и возможно «тихое» обновление без прав администратора и запроса UAC. Для того, чтобы хранить в папке пользователя + именем компании + именем продукта можно использовать примерно такой код:

       /// <summary>  Получение рабочей папки приложения по умолчанию </summary>
        /// <returns>Путь к папку</returns>
        /// <summary>  Получение рабочей папки приложения по умолчанию </summary>
    /// <returns>Путь к папку</returns>
    public static string GetDefaultAppFolder()
    {
      string s = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);

      string product_name =   ProductName(Assembly.GetEntryAssembly());
      string company_name = CompanyName (Assembly.GetEntryAssembly());


      return Path.Combine(Path.Combine(s, company_name), product_name) + @"";
    }

    public static string ProductName(Assembly assembly)
    {
      object[] attributes = Assembly.GetCallingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false);

      if (attributes.Length==0)
        throw new ApplicationException("Reflection Error  on  get Product Name");

      return ((AssemblyProductAttribute)attributes[0]).Product;

    }

    public static string CompanyName(Assembly assembly)
    {
      object[] attributes = Assembly.GetCallingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);

      if (attributes.Length == 0)
        throw new ApplicationException("Reflection error  on get Company Name");

      return attributes.Length == 0 ? "" : ((AssemblyCompanyAttribute)attributes[0]).Company;

    }

Так как будем хранить данные приложения в с именем продукта и внутри каталога Environment.SpecialFolder.ApplicationData ( В Windows 7 это c:UsersUsernameAppDataRoaming), в InnoSetup этому пути будет соответствовать константа {userappdata}

Предлагаемое решение:

  • Используем Inno Setup
  • При установке мы кладем AutoStart.exe c конфигурационным файлом в пользовательский каталог , например c:Users%USERNAME%AppDataRoamingMyCompanyMySimpleSample (autoupdatable). В папку с постоянным именем (например, BaseInstall) копируем текущую версию приложения — чтобы не возиться с настройк
  • Генерируем конфиг из InnoSetup, либо просто копируем начальный конфиг, содержащий путь BaseInstall. Код генерации из InnoSetup (Весь скрипт инсталляции можно скачать отсюда)
  • 
    ; Additions
    #define BaseCatalog "BaseInstall"
    #define ConfigName "AppStart.conf"
    #define MainExeName "MySimpleApp.exe"
    [Code]
    procedure AfterMyProgInstall(S: String);
    var
      AppPath: string;
      ConfigPath: string;
      BaseInstallPath: string;
    begin
     AppPath := Format('%s%s%s', [ExpandConstant('{userappdata}'), ExpandConstant('{#MyAppPublisher}'),  ExpandConstant('{#MyAppName}')] );
     ConfigPath :=Format('%s%s',  [AppPath , ExpandConstant('{#ConfigName}')]);
     BaseInstallPath :=Format('%s%s',  [AppPath , ExpandConstant('{#BaseCatalog}')]);
    
      SaveStringToFile(ConfigPath,'<config>' + #13#10,false);
    
      SaveStringToFile(ConfigPath,'<appFolderName>' ,true);
      SaveStringToFile(ConfigPath, ExpandConstant('{#BaseCatalog}') ,true);
      SaveStringToFile(ConfigPath,'</appFolderName>' + #13#10 ,true);
    
      SaveStringToFile(ConfigPath,'<appExeName>' ,true);
      SaveStringToFile(ConfigPath,ExpandConstant('{#MainExeName}') ,true);
      SaveStringToFile(ConfigPath,'</appExeName>'+ #13#10 ,true);
    
    
      SaveStringToFile(ConfigPath,'</config>' + #13#10,true);
    end;
    

8. Похоже, что режим проверки наличия новой версии напрямую по дате не протестирован, список файлов берется из так называемого файла манифеста (класс AppManifest), однако, при обновлении записывается манифест со списком старых файлов. Так как хранить его нужно только для возобновления загрузки, то строку Manifest = AppManifest.Load(AppManifestPath); можно вообще заменить на Manifest = new AppManifest(AppManifestPath);

Для того, чтобы хранить одну версию программу первым в голову приходить использование пути ProgramData, однако запись по этому пути требует администраторских прав и ведет к чудесным вещам — в виде виртуализации пути для программы, работающей с правами обычного пользователя, заставляющим разбираться с Junction Points. Можно сделать вывод, что это является плохой практикой.

Полностью исходный код (со скриптом InnoSetup) можно скачать отсюда. Для тестирования нужно будет только настроить путь для обновлений на свой и попробовать обновить дату в каком-нибудь из файлов. На данный момент происходит только проверка загруженных сборок, если нужна проверка обновлений не для исполняемых файлов, придется добавить такую функциональность.

Ссылки по теме:

Комментарии:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *