diff --git a/.gitignore b/.gitignore
index 2f97a261..37efa7e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,10 @@ bld/
[Oo]bj/
[Ll]og/
+build/
+# Keep nuspec files in build directories
+!**/build/*.nuspec
+
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
diff --git a/AutoUpdater.NET.Markdown/AutoUpdater.NET.Markdown.csproj b/AutoUpdater.NET.Markdown/AutoUpdater.NET.Markdown.csproj
new file mode 100644
index 00000000..fbaa2848
--- /dev/null
+++ b/AutoUpdater.NET.Markdown/AutoUpdater.NET.Markdown.csproj
@@ -0,0 +1,36 @@
+
+
+ library
+ net462;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows
+ true
+ AutoUpdaterDotNET.Markdown
+ AutoUpdater.NET.Markdown
+ RBSoft
+ AutoUpdater.NET.Markdown
+ Copyright © 2012-2025 RBSoft
+ 1.9.5.1
+
+
+ en
+ Autoupdater.NET.Markdown
+ true
+ MIT
+ AutoUpdater.NET Markdown Extension
+ rbsoft
+ Markdown extension for AutoUpdater.NET that provides markdown support.
+ https://github.com/ravibpatel/AutoUpdater.NET
+ autoupdate updater markdown
+ https://github.com/ravibpatel/AutoUpdater.NET/releases
+ build
+ true
+ latest
+
+
+
+
+
+
+
+
+
+
diff --git a/AutoUpdater.NET.Markdown/MarkdownViewer.cs b/AutoUpdater.NET.Markdown/MarkdownViewer.cs
new file mode 100644
index 00000000..292d1754
--- /dev/null
+++ b/AutoUpdater.NET.Markdown/MarkdownViewer.cs
@@ -0,0 +1,154 @@
+using System.Windows.Forms;
+using AutoUpdaterDotNET.ChangelogViewers;
+using Markdig;
+
+namespace AutoUpdaterDotNET.Markdown;
+
+///
+/// A changelog viewer that renders Markdown content using either a provided viewer or a WebBrowser control.
+///
+public class MarkdownViewer : IChangelogViewer
+{
+ private readonly IChangelogViewer _innerViewer;
+ private readonly bool _cleanup;
+ private const string DefaultStyle = @"
+ ";
+
+ ///
+ /// Initializes a new instance of the MarkdownViewer class.
+ ///
+ /// Optional viewer to use for rendering. If not provided, uses WebBrowser control.
+ /// Whether to clean up the inner viewer when this viewer is disposed.
+ public MarkdownViewer(IChangelogViewer viewer = null, bool cleanup = true)
+ {
+ _innerViewer = viewer ?? new WebBrowserViewer();
+ _cleanup = cleanup || viewer == null;
+ }
+
+ ///
+ public Control Control => _innerViewer.Control;
+
+ ///
+ public bool SupportsUrl => true;
+
+ ///
+ public void LoadContent(string content)
+ {
+ if (_innerViewer is RichTextBoxViewer or MarkdownViewer)
+ {
+ _innerViewer.LoadContent(content);
+ return;
+ }
+
+ var pipeline = new MarkdownPipelineBuilder()
+ .UseAdvancedExtensions()
+ .Build();
+
+ var html = Markdig.Markdown.ToHtml(content, pipeline);
+ var fullHtml = $"
{DefaultStyle}{html}";
+
+ _innerViewer.LoadContent(fullHtml);
+ }
+
+ ///
+ public void LoadUrl(string url)
+ {
+ using var client = new System.Net.WebClient();
+ if (AutoUpdater.BasicAuthChangeLog != null)
+ {
+ var auth = (BasicAuthentication)AutoUpdater.BasicAuthChangeLog;
+ client.Credentials = new System.Net.NetworkCredential(auth.Username, auth.Password);
+ }
+
+ var content = client.DownloadString(url);
+ LoadContent(content);
+ }
+
+ ///
+ public void Cleanup()
+ {
+ if (_cleanup)
+ _innerViewer.Cleanup();
+ }
+}
\ No newline at end of file
diff --git a/AutoUpdater.NET.Markdown/MarkdownViewerProvider.cs b/AutoUpdater.NET.Markdown/MarkdownViewerProvider.cs
new file mode 100644
index 00000000..d76ab127
--- /dev/null
+++ b/AutoUpdater.NET.Markdown/MarkdownViewerProvider.cs
@@ -0,0 +1,39 @@
+using AutoUpdaterDotNET.ChangelogViewers;
+
+namespace AutoUpdaterDotNET.Markdown;
+
+///
+/// Provides a Markdown viewer that uses a WebBrowser control or a user provided viewer to render Markdown content.
+///
+public class MarkdownViewerProvider(int priority = 2) : IChangelogViewerProvider
+{
+ private readonly IChangelogViewer _viewer;
+
+ ///
+ /// Creates a new instance of the with default priority 2.
+ ///
+ public MarkdownViewerProvider() : this(2) { }
+
+ ///
+ /// Creates a new instance of the with a specific viewer.
+ ///
+ /// The viewer to use for rendering Markdown content.
+ /// The priority of this provider.
+ public MarkdownViewerProvider(IChangelogViewer viewer, int priority = 2) : this(priority)
+ {
+ _viewer = viewer;
+ }
+
+ ///
+ /// Gets whether this provider is available. Always returns true as it uses WebBrowser as fallback.
+ ///
+ public bool IsAvailable => true;
+
+ ///
+ public int Priority { get; } = priority;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public IChangelogViewer CreateViewer() => new MarkdownViewer(_viewer);
+}
\ No newline at end of file
diff --git a/AutoUpdater.NET.Markdown/build/Autoupdater.NET.Markdown.nuspec b/AutoUpdater.NET.Markdown/build/Autoupdater.NET.Markdown.nuspec
new file mode 100644
index 00000000..a0e6942a
--- /dev/null
+++ b/AutoUpdater.NET.Markdown/build/Autoupdater.NET.Markdown.nuspec
@@ -0,0 +1,53 @@
+
+
+
+ Autoupdater.NET.Official.Markdown
+ 1.9.5.1
+ AutoUpdater.NET Markdown Extension
+ rbsoft
+ false
+ MIT
+ docs\README.md
+ https://licenses.nuget.org/MIT
+ https://github.com/ravibpatel/AutoUpdater.NET
+ Markdown extension for AutoUpdater.NET that provides Markdown rendering capabilities for changelogs.
+ https://github.com/ravibpatel/AutoUpdater.NET/releases
+ Copyright 2012-2025 RBSoft
+ autoupdate updater markdown changelog
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj b/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj
new file mode 100644
index 00000000..dfc8e753
--- /dev/null
+++ b/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj
@@ -0,0 +1,34 @@
+
+
+
+ library
+ net462;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows
+ true
+ AutoUpdaterDotNET.WebView2
+ AutoUpdater.NET.WebView2
+ RBSoft
+ AutoUpdater.NET.WebView2
+ Copyright © 2012-2025 RBSoft
+ 1.9.5.1
+ true
+ ..\AutoUpdater.NET\AutoUpdater.NET.snk
+ en
+ Autoupdater.NET.WebView2
+ true
+ MIT
+ AutoUpdater.NET WebView2 Extension
+ rbsoft
+ WebView2 extension for AutoUpdater.NET that provides modern web rendering capabilities for changelogs.
+ https://github.com/ravibpatel/AutoUpdater.NET
+ autoupdate updater webview2 edge
+ https://github.com/ravibpatel/AutoUpdater.NET/releases
+ build
+ true
+ latest
+
+
+
+
+
+
+
diff --git a/AutoUpdater.NET.WebView2/WebView2Viewer.cs b/AutoUpdater.NET.WebView2/WebView2Viewer.cs
new file mode 100644
index 00000000..6203c990
--- /dev/null
+++ b/AutoUpdater.NET.WebView2/WebView2Viewer.cs
@@ -0,0 +1,110 @@
+using System;
+using System.IO;
+using System.Windows.Forms;
+using System.Threading.Tasks;
+using Microsoft.Web.WebView2.Core;
+using AutoUpdaterDotNET.ChangelogViewers;
+
+namespace AutoUpdaterDotNET.WebView2;
+
+///
+/// A changelog viewer for displaying changelogs using a modern browser.
+///
+public class WebView2Viewer : IChangelogViewer
+{
+ private bool _isInitialized;
+ private readonly Microsoft.Web.WebView2.WinForms.WebView2 _webView = new()
+ {
+ Dock = DockStyle.Fill,
+ AllowExternalDrop = false
+ };
+
+ ///
+ public Control Control => _webView;
+
+ ///
+ public bool SupportsUrl => true;
+
+ private async Task EnsureInitialized()
+ {
+ if (_isInitialized) return;
+
+ try
+ {
+ await _webView.EnsureCoreWebView2Async(await CoreWebView2Environment.CreateAsync(null, Path.GetTempPath()));
+ _isInitialized = true;
+ }
+ catch (Exception)
+ {
+ throw new InvalidOperationException("WebView2 runtime is not available");
+ }
+ }
+
+ ///
+ public async void LoadContent(string content)
+ {
+ await EnsureInitialized();
+ _webView.CoreWebView2.SetVirtualHostNameToFolderMapping("local.files", Path.GetTempPath(), CoreWebView2HostResourceAccessKind.Allow);
+
+ // Write content to a temporary HTML file
+ var tempFile = Path.Combine(Path.GetTempPath(), "changelog.html");
+ File.WriteAllText(tempFile, content, System.Text.Encoding.UTF8);
+
+ // Navigate to the local file
+ _webView.CoreWebView2.Navigate("https://local.files/changelog.html");
+ }
+
+ ///
+ public async void LoadUrl(string url)
+ {
+ await EnsureInitialized();
+
+ if (AutoUpdater.BasicAuthChangeLog != null)
+ {
+ _webView.CoreWebView2.BasicAuthenticationRequested += delegate (object _, CoreWebView2BasicAuthenticationRequestedEventArgs args)
+ {
+ args.Response.UserName = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Username;
+ args.Response.Password = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Password;
+ };
+ }
+
+ _webView.CoreWebView2.Navigate(url);
+ }
+
+ ///
+ public void Cleanup()
+ {
+ if (File.Exists(Path.Combine(Path.GetTempPath(), "changelog.html")))
+ {
+ try
+ {
+ File.Delete(Path.Combine(Path.GetTempPath(), "changelog.html"));
+ }
+ catch
+ {
+ // Ignore deletion errors
+ }
+ }
+ _webView.Dispose();
+ }
+
+ ///
+ /// Checks if WebView2 is available on the current environment
+ ///
+ /// True if WebView2 is available, false otherwise
+ public static bool IsAvailable()
+ {
+ try
+ {
+ var availableBrowserVersion = CoreWebView2Environment.GetAvailableBrowserVersionString(null);
+ const string requiredMinBrowserVersion = "86.0.616.0";
+ return !string.IsNullOrEmpty(availableBrowserVersion) &&
+ CoreWebView2Environment.CompareBrowserVersions(availableBrowserVersion,
+ requiredMinBrowserVersion) >= 0;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs b/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs
new file mode 100644
index 00000000..9f088478
--- /dev/null
+++ b/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs
@@ -0,0 +1,27 @@
+using AutoUpdaterDotNET.ChangelogViewers;
+
+namespace AutoUpdaterDotNET.WebView2;
+
+///
+/// Provides a changelog viewer that uses the modern WebView2 control.
+///
+public class WebView2ViewerProvider(int priority = 3) : IChangelogViewerProvider
+{
+ ///
+ /// Creates a new instance of the with default priority 3.
+ ///
+ public WebView2ViewerProvider() : this(3) { }
+
+ ///
+ /// Gets whether WebView2 runtime is available on the current system.
+ ///
+ public bool IsAvailable => WebView2Viewer.IsAvailable();
+
+ ///
+ public int Priority { get; } = priority;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public IChangelogViewer CreateViewer() => new WebView2Viewer();
+}
diff --git a/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec b/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec
new file mode 100644
index 00000000..ecf0b656
--- /dev/null
+++ b/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec
@@ -0,0 +1,70 @@
+
+
+
+ Autoupdater.NET.WebView2
+ 1.9.5.1
+ AutoUpdater.NET WebView2 Extension
+ rbsoft
+ false
+ MIT
+ docs\README.md
+ https://licenses.nuget.org/MIT
+ https://github.com/ravibpatel/AutoUpdater.NET
+ WebView2 extension for AutoUpdater.NET that provides modern web rendering capabilities for changelogs.
+ https://github.com/ravibpatel/AutoUpdater.NET/releases
+ Copyright 2012-2025 RBSoft
+ autoupdate updater webview2 edge
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AutoUpdater.NET.sln b/AutoUpdater.NET.sln
index 8b512fc2..b360789e 100644
--- a/AutoUpdater.NET.sln
+++ b/AutoUpdater.NET.sln
@@ -5,21 +5,29 @@ VisualStudioVersion = 16.0.29215.179
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdater.NET", "AutoUpdater.NET\AutoUpdater.NET.csproj", "{FB9E7E6B-B19F-4F37-A708-2996190CEF13}"
ProjectSection(ProjectDependencies) = postProject
- {91DE558C-6DB8-429B-A069-C0491DCFF15B} = {91DE558C-6DB8-429B-A069-C0491DCFF15B}
+ {EDB311FC-50D3-468B-AC36-4CDFE04D29A3} = {EDB311FC-50D3-468B-AC36-4CDFE04D29A3}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1DBD2EE1-6C31-4B24-9212-E221404F4ADE}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
appveyor.yml = appveyor.yml
+ build.bat = build.bat
LICENSE = LICENSE
README.md = README.md
- build.bat = build.bat
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZipExtractor", "ZipExtractor\ZipExtractor.csproj", "{EDB311FC-50D3-468B-AC36-4CDFE04D29A3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdaterTest", "AutoUpdaterTest\AutoUpdaterTest.csproj", "{5C1E186E-2622-425D-8E90-2CC6C25EDE29}"
+ ProjectSection(ProjectDependencies) = postProject
+ {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3} = {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}
+ {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A} = {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdater.NET.WebView2", "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj", "{A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdater.NET.Markdown", "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj", "{D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -39,6 +47,14 @@ Global
{5C1E186E-2622-425D-8E90-2CC6C25EDE29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C1E186E-2622-425D-8E90-2CC6C25EDE29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C1E186E-2622-425D-8E90-2CC6C25EDE29}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/AutoUpdater.NET/AutoUpdater.NET.csproj b/AutoUpdater.NET/AutoUpdater.NET.csproj
index 2d680f5c..0e25b592 100644
--- a/AutoUpdater.NET/AutoUpdater.NET.csproj
+++ b/AutoUpdater.NET/AutoUpdater.NET.csproj
@@ -10,11 +10,8 @@
AutoUpdater.NET
RBSoft
AutoUpdater.NET
- Copyright © 2012-2024 RBSoft
- 1.9.3.0
- 1.9.3.0
- 1.9.3.0
- 1.9.3.0
+ Copyright © 2012-2025 RBSoft
+ 1.9.5.1
true
AutoUpdater.NET.snk
en
@@ -28,7 +25,7 @@
autoupdate updater c# vb wpf winforms
https://github.com/ravibpatel/AutoUpdater.NET/releases
build
- $(OutDir)\AutoUpdater.NET.xml
+ true
latest
@@ -48,9 +45,6 @@
-
-
-
DownloadUpdateDialog.cs
diff --git a/AutoUpdater.NET/AutoUpdater.cs b/AutoUpdater.NET/AutoUpdater.cs
index 480419c5..178aec3a 100644
--- a/AutoUpdater.NET/AutoUpdater.cs
+++ b/AutoUpdater.NET/AutoUpdater.cs
@@ -1,758 +1,770 @@
-using System;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Drawing;
-using System.Globalization;
-using System.IO;
-using System.Net;
-using System.Net.Cache;
-using System.Reflection;
-using System.Threading;
-using System.Windows;
-using System.Windows.Forms;
-using System.Xml;
-using System.Xml.Serialization;
-using AutoUpdaterDotNET.Properties;
-using Application = System.Windows.Forms.Application;
-using MessageBox = System.Windows.Forms.MessageBox;
-using Size = System.Drawing.Size;
-using Timer = System.Timers.Timer;
-using WinFormsMethodInvoker = System.Windows.Forms.MethodInvoker;
-
-namespace AutoUpdaterDotNET;
-
-///
-/// Enum representing the remind later time span.
-///
-public enum RemindLaterFormat
-{
- ///
- /// Represents the time span in minutes.
- ///
- Minutes,
-
- ///
- /// Represents the time span in hours.
- ///
- Hours,
-
- ///
- /// Represents the time span in days.
- ///
- Days
-}
-
-///
-/// Enum representing the effect of Mandatory flag.
-///
-public enum Mode
-{
- ///
- /// In this mode, it ignores Remind Later and Skip values set previously and hide both buttons.
- ///
- [XmlEnum("0")] Normal,
-
- ///
- /// In this mode, it won't show close button in addition to Normal mode behaviour.
- ///
- [XmlEnum("1")] Forced,
-
- ///
- /// In this mode, it will start downloading and applying update without showing standard update dialog in addition to
- /// Forced mode behaviour.
- ///
- [XmlEnum("2")] ForcedDownload
-}
-
-///
-/// Main class that lets you auto update applications by setting some static fields and executing its Start method.
-///
-public static class AutoUpdater
-{
- ///
- /// A delegate type to handle how to exit the application after update is downloaded.
- ///
- public delegate void ApplicationExitEventHandler();
-
- ///
- /// A delegate type for hooking up update notifications.
- ///
- ///
- /// An object containing all the parameters received from AppCast XML file. If there will be an error
- /// while looking for the XML file then this object will be null.
- ///
- public delegate void CheckForUpdateEventHandler(UpdateInfoEventArgs args);
-
- ///
- /// A delegate type for hooking up parsing logic.
- ///
- /// An object containing the AppCast file received from server.
- public delegate void ParseUpdateInfoHandler(ParseUpdateInfoEventArgs args);
-
- private static bool _isWinFormsApplication;
-
- private static IWin32Window _owner;
-
- private static Timer _remindLaterTimer;
-
- internal static Uri BaseUri;
-
- internal static bool Running;
-
- ///
- /// URL of the xml file that contains information about latest version of the application.
- ///
- public static string AppCastURL;
-
- ///
- /// Set the Application Title shown in Update dialog. Although AutoUpdater.NET will get it automatically, you can set
- /// this property if you like to give custom Title.
- ///
- public static string AppTitle;
-
- ///
- /// Set Basic Authentication credentials to navigate to the change log URL.
- ///
- public static IAuthentication BasicAuthChangeLog;
-
- ///
- /// Set Basic Authentication credentials required to download the file.
- ///
- public static IAuthentication BasicAuthDownload;
-
- ///
- /// Set Basic Authentication credentials required to download the XML file.
- ///
- public static IAuthentication BasicAuthXML;
-
- ///
- /// Set this to true if you want to clear application directory before extracting update.
- ///
- public static bool ClearAppDirectory = false;
-
- ///
- /// Set it to folder path where you want to download the update file. If not provided then it defaults to Temp folder.
- ///
- public static string DownloadPath;
-
- ///
- /// If you are using a zip file as an update file, then you can set this value to a new executable path relative to the
- /// installation directory.
- ///
- public static string ExecutablePath;
-
- ///
- /// Login/password/domain for FTP-request
- ///
- public static NetworkCredential FtpCredentials;
-
- ///
- /// Set the User-Agent string to be used for HTTP web requests.
- ///
- public static string HttpUserAgent;
-
- ///
- /// Set this to change the icon shown on updater dialog.
- ///
- public static Bitmap Icon;
-
- ///
- /// If you are using a zip file as an update file then you can set this value to path where your app is installed. This
- /// is only necessary when your installation directory differs from your executable path.
- ///
- public static string InstallationPath;
-
- ///
- /// You can set this field to your current version if you don't want to determine the version from the assembly.
- ///
- public static Version InstalledVersion;
-
- ///
- /// If this is true users see dialog where they can set remind later interval otherwise it will take the interval from
- /// RemindLaterAt and RemindLaterTimeSpan fields.
- ///
- public static bool LetUserSelectRemindLater = true;
-
- ///
- /// Set this to true if you want to ignore previously assigned Remind Later and Skip settings. It will also hide Remind
- /// Later and Skip buttons.
- ///
- public static bool Mandatory;
-
- ///
- /// Opens the download URL in default browser if true. Very useful if you have portable application.
- ///
- public static bool OpenDownloadPage;
-
- ///
- /// Set this to an instance implementing the IPersistenceProvider interface for using a data storage method different
- /// from the default Windows Registry based one.
- ///
- public static IPersistenceProvider PersistenceProvider;
-
- ///
- /// Set Proxy server to use for all the web requests in AutoUpdater.NET.
- ///
- public static IWebProxy Proxy;
-
- ///
- /// Remind Later interval after user should be reminded of update.
- ///
- public static int RemindLaterAt = 2;
-
- ///
- /// Set if RemindLaterAt interval should be in Minutes, Hours or Days.
- ///
- public static RemindLaterFormat RemindLaterTimeSpan = RemindLaterFormat.Days;
-
- ///
- /// AutoUpdater.NET will report errors if this is true.
- ///
- public static bool ReportErrors = false;
-
- ///
- /// Set this to false if your application doesn't need administrator privileges to replace the old version.
- ///
- public static bool RunUpdateAsAdmin = true;
-
- ///
- /// If this is true users can see the Remind Later button.
- ///
- public static bool ShowRemindLaterButton = true;
-
- ///
- /// If this is true users can see the skip button.
- ///
- public static bool ShowSkipButton = true;
-
- ///
- /// Set this to true if you want to run update check synchronously.
- ///
- public static bool Synchronous = false;
-
- ///
- /// Modify TopMost property of all dialogs.
- ///
- public static bool TopMost = false;
-
- ///
- /// Set this if you want the default update form to have a different size.
- ///
- public static Size? UpdateFormSize = null;
-
- ///
- /// Set this to any of the available modes to change behaviour of the Mandatory flag.
- ///
- public static Mode UpdateMode;
-
- ///
- /// An event that developers can use to exit the application gracefully.
- ///
- public static event ApplicationExitEventHandler ApplicationExitEvent;
-
- ///
- /// An event that clients can use to be notified whenever the update is checked.
- ///
- public static event CheckForUpdateEventHandler CheckForUpdateEvent;
-
- ///
- /// An event that clients can use to be notified whenever the AppCast file needs parsing.
- ///
- public static event ParseUpdateInfoHandler ParseUpdateInfoEvent;
-
- ///
- /// Set the owner for all dialogs.
- ///
- /// WPF Window or Windows Form object to be used as owner for all dialogs.
- public static void SetOwner(object obj)
- {
- _owner = obj switch
- {
- Form form => form,
- Window window => new Wpf32Window(window),
- _ => _owner
- };
- }
-
- ///
- /// Start checking for new version of application and display a dialog to the user if update is available.
- ///
- /// Assembly to use for version checking.
- public static void Start(Assembly myAssembly = null)
- {
- Start(AppCastURL, myAssembly);
- }
-
- ///
- /// Start checking for new version of application via FTP and display a dialog to the user if update is available.
- ///
- /// FTP URL of the xml file that contains information about latest version of the application.
- /// Credentials required to connect to FTP server.
- /// Assembly to use for version checking.
- public static void Start(string appCast, NetworkCredential ftpCredentials, Assembly myAssembly = null)
- {
- FtpCredentials = ftpCredentials;
- Start(appCast, myAssembly);
- }
-
- ///
- /// Start checking for new version of application and display a dialog to the user if update is available.
- ///
- /// URL of the xml file that contains information about latest version of the application.
- /// Assembly to use for version checking.
- public static void Start(string appCast, Assembly myAssembly = null)
- {
- try
- {
- ServicePointManager.SecurityProtocol |= (SecurityProtocolType)192 |
- (SecurityProtocolType)768 | (SecurityProtocolType)3072;
- }
- catch (NotSupportedException)
- {
- }
-
- if (Mandatory && _remindLaterTimer != null)
- {
- _remindLaterTimer.Stop();
- _remindLaterTimer.Close();
- _remindLaterTimer = null;
- }
-
- if (Running || _remindLaterTimer != null)
- {
- return;
- }
-
- Running = true;
-
- AppCastURL = appCast;
-
- _isWinFormsApplication = Application.MessageLoop;
-
- if (!_isWinFormsApplication)
- {
- Application.EnableVisualStyles();
- }
-
- Assembly assembly = myAssembly ?? Assembly.GetEntryAssembly();
-
- if (Synchronous)
- {
- try
- {
- object result = CheckUpdate(assembly);
-
- if (StartUpdate(result))
- {
- return;
- }
-
- Running = false;
- }
- catch (Exception exception)
- {
- ShowError(exception);
- }
- }
- else
- {
- using var backgroundWorker = new BackgroundWorker();
-
- backgroundWorker.DoWork += (_, args) =>
- {
- var mainAssembly = args.Argument as Assembly;
-
- args.Result = CheckUpdate(mainAssembly);
- };
-
- backgroundWorker.RunWorkerCompleted += (_, args) =>
- {
- if (args.Error != null)
- {
- ShowError(args.Error);
- }
- else
- {
- if (!args.Cancelled && StartUpdate(args.Result))
- {
- return;
- }
-
- Running = false;
- }
- };
-
- backgroundWorker.RunWorkerAsync(assembly);
- }
- }
-
- private static object CheckUpdate(Assembly mainAssembly)
- {
- var companyAttribute =
- (AssemblyCompanyAttribute)GetAttribute(mainAssembly, typeof(AssemblyCompanyAttribute));
- string appCompany = companyAttribute != null ? companyAttribute.Company : "";
-
- if (string.IsNullOrEmpty(AppTitle))
- {
- var titleAttribute =
- (AssemblyTitleAttribute)GetAttribute(mainAssembly, typeof(AssemblyTitleAttribute));
- AppTitle = titleAttribute != null ? titleAttribute.Title : mainAssembly.GetName().Name;
- }
-
- string registryLocation = !string.IsNullOrEmpty(appCompany)
- ? $@"Software\{appCompany}\{AppTitle}\AutoUpdater"
- : $@"Software\{AppTitle}\AutoUpdater";
-
- PersistenceProvider ??= new RegistryPersistenceProvider(registryLocation);
-
- UpdateInfoEventArgs args;
- string xml = null;
-
- if (AppCastURL != null)
- {
- BaseUri = new Uri(AppCastURL);
- using MyWebClient client = GetWebClient(BaseUri, BasicAuthXML);
- xml = client.DownloadString(BaseUri);
- }
-
- if (ParseUpdateInfoEvent == null)
- {
- if (string.IsNullOrEmpty(xml))
- {
- throw new Exception("It is required to handle ParseUpdateInfoEvent when XML url is not specified.");
- }
-
- var xmlSerializer = new XmlSerializer(typeof(UpdateInfoEventArgs));
- var xmlTextReader = new XmlTextReader(new StringReader(xml)) { XmlResolver = null };
- args = (UpdateInfoEventArgs)xmlSerializer.Deserialize(xmlTextReader);
- }
- else
- {
- var parseArgs = new ParseUpdateInfoEventArgs(xml);
- ParseUpdateInfoEvent(parseArgs);
- args = parseArgs.UpdateInfo;
- }
-
- if (string.IsNullOrEmpty(args?.CurrentVersion) || string.IsNullOrEmpty(args.DownloadURL))
- {
- throw new MissingFieldException();
- }
-
- args.InstalledVersion = InstalledVersion ?? mainAssembly.GetName().Version;
- args.IsUpdateAvailable = new Version(args.CurrentVersion) > args.InstalledVersion;
-
- if (!Mandatory)
- {
- if (string.IsNullOrEmpty(args.Mandatory.MinimumVersion) ||
- args.InstalledVersion < new Version(args.Mandatory.MinimumVersion))
- {
- Mandatory = args.Mandatory.Value;
- UpdateMode = args.Mandatory.UpdateMode;
- }
- }
-
- if (Mandatory)
- {
- ShowRemindLaterButton = false;
- ShowSkipButton = false;
- }
- else
- {
- // Read the persisted state from the persistence provider.
- // This method makes the persistence handling independent from the storage method.
- Version skippedVersion = PersistenceProvider.GetSkippedVersion();
- if (skippedVersion != null)
- {
- var currentVersion = new Version(args.CurrentVersion);
- if (currentVersion <= skippedVersion)
- {
- return null;
- }
-
- if (currentVersion > skippedVersion)
- {
- // Update the persisted state. Its no longer makes sense to have this flag set as we are working on a newer application version.
- PersistenceProvider.SetSkippedVersion(null);
- }
- }
-
- DateTime? remindLaterAt = PersistenceProvider.GetRemindLater();
- if (remindLaterAt == null)
- {
- return args;
- }
-
- int compareResult = DateTime.Compare(DateTime.Now, remindLaterAt.Value);
-
- if (compareResult < 0)
- {
- return remindLaterAt.Value;
- }
- }
-
- return args;
- }
-
- private static bool StartUpdate(object result)
- {
- if (result is DateTime time)
- {
- SetTimer(time);
- }
- else
- {
- if (result is not UpdateInfoEventArgs args)
- {
- return false;
- }
-
- if (CheckForUpdateEvent != null)
- {
- CheckForUpdateEvent(args);
- }
- else
- {
- if (args.IsUpdateAvailable)
- {
- if (Mandatory && UpdateMode == Mode.ForcedDownload)
- {
- DownloadUpdate(args);
- Exit();
- }
- else
- {
- if (Thread.CurrentThread.GetApartmentState().Equals(ApartmentState.STA))
- {
- ShowUpdateForm(args);
- }
- else
- {
- var thread = new Thread(new ThreadStart(delegate { ShowUpdateForm(args); }));
- thread.CurrentCulture =
- thread.CurrentUICulture = CultureInfo.CurrentCulture;
- thread.SetApartmentState(ApartmentState.STA);
- thread.Start();
- thread.Join();
- }
- }
-
- return true;
- }
-
- if (ReportErrors)
- {
- MessageBox.Show(_owner,
- Resources.UpdateUnavailableMessage,
- Resources.UpdateUnavailableCaption,
- MessageBoxButtons.OK, MessageBoxIcon.Information);
- }
- }
- }
-
- return false;
- }
-
- private static void ShowError(Exception exception)
- {
- if (CheckForUpdateEvent != null)
- {
- CheckForUpdateEvent(new UpdateInfoEventArgs { Error = exception });
- }
- else
- {
- if (ReportErrors)
- {
- if (exception is WebException)
- {
- MessageBox.Show(_owner,
- Resources.UpdateCheckFailedMessage,
- Resources.UpdateCheckFailedCaption,
- MessageBoxButtons.OK, MessageBoxIcon.Error);
- }
- else
- {
- MessageBox.Show(_owner,
- exception.Message,
- exception.GetType().ToString(),
- MessageBoxButtons.OK, MessageBoxIcon.Error);
- }
- }
- }
-
- Running = false;
- }
-
- ///
- /// Detects and exits all instances of running assembly, including current.
- ///
- internal static void Exit()
- {
- var currentProcess = Process.GetCurrentProcess();
- foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
- {
- try
- {
- string processPath = process.MainModule?.FileName;
-
- // Get all instances of assembly except current
- if (process.Id == currentProcess.Id || currentProcess.MainModule?.FileName != processPath)
- {
- continue;
- }
-
- if (process.CloseMainWindow())
- {
- process.WaitForExit((int)TimeSpan.FromSeconds(10)
- .TotalMilliseconds); // Give some time to process message
- }
-
- if (process.HasExited)
- {
- continue;
- }
-
- process.Kill(); //TODO: Show UI message asking user to close program himself instead of silently killing it
- }
- catch (Exception)
- {
- // ignored
- }
- }
-
- if (ApplicationExitEvent != null)
- {
- ApplicationExitEvent();
- }
- else
- {
- if (_isWinFormsApplication)
- {
- WinFormsMethodInvoker methodInvoker = Application.Exit;
- methodInvoker.Invoke();
- }
- else if (System.Windows.Application.Current != null)
- {
- System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
- System.Windows.Application.Current.Shutdown()));
- }
- else
- {
- Environment.Exit(0);
- }
- }
- }
-
- private static Attribute GetAttribute(Assembly assembly, Type attributeType)
- {
- object[] attributes = assembly.GetCustomAttributes(attributeType, false);
- if (attributes.Length == 0)
- {
- return null;
- }
-
- return (Attribute)attributes[0];
- }
-
- internal static string GetUserAgent()
- {
- return string.IsNullOrEmpty(HttpUserAgent) ? "AutoUpdater.NET" : HttpUserAgent;
- }
-
- internal static void SetTimer(DateTime remindLater)
- {
- TimeSpan timeSpan = remindLater - DateTime.Now;
-
- SynchronizationContext context = SynchronizationContext.Current;
-
- _remindLaterTimer = new Timer
- {
- Interval = Math.Max(1, timeSpan.TotalMilliseconds),
- AutoReset = false
- };
-
- _remindLaterTimer.Elapsed += delegate
- {
- _remindLaterTimer = null;
- if (context != null)
- {
- try
- {
- context.Send(_ => Start(), null);
- }
- catch (InvalidAsynchronousStateException)
- {
- Start();
- }
- }
- else
- {
- Start();
- }
- };
-
- _remindLaterTimer.Start();
- }
-
- ///
- /// Opens the Download window that download the update and execute the installer when download completes.
- ///
- public static bool DownloadUpdate(UpdateInfoEventArgs args)
- {
- using var downloadDialog = new DownloadUpdateDialog(args);
-
- try
- {
- return downloadDialog.ShowDialog(_owner).Equals(DialogResult.OK);
- }
- catch (TargetInvocationException)
- {
- // ignored
- }
-
- return false;
- }
-
- ///
- /// Shows standard update dialog.
- ///
- public static void ShowUpdateForm(UpdateInfoEventArgs args)
- {
- using var updateForm = new UpdateForm(args);
-
- if (UpdateFormSize.HasValue)
- {
- updateForm.Size = UpdateFormSize.Value;
- }
-
- if (updateForm.ShowDialog(_owner).Equals(DialogResult.OK))
- {
- Exit();
- }
- }
-
- internal static MyWebClient GetWebClient(Uri uri, IAuthentication basicAuthentication)
- {
- var webClient = new MyWebClient
- {
- CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore)
- };
-
- if (Proxy != null)
- {
- webClient.Proxy = Proxy;
- }
-
- if (uri.Scheme.Equals(Uri.UriSchemeFtp))
- {
- webClient.Credentials = FtpCredentials;
- }
- else
- {
- basicAuthentication?.Apply(ref webClient);
-
- webClient.Headers[HttpRequestHeader.UserAgent] = HttpUserAgent;
- }
-
- return webClient;
- }
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Net.Cache;
+using System.Reflection;
+using System.Threading;
+using System.Windows;
+using System.Windows.Forms;
+using System.Xml;
+using System.Xml.Serialization;
+using AutoUpdaterDotNET.ChangelogViewers;
+using AutoUpdaterDotNET.Properties;
+using Application = System.Windows.Forms.Application;
+using MessageBox = System.Windows.Forms.MessageBox;
+using Size = System.Drawing.Size;
+using Timer = System.Timers.Timer;
+using WinFormsMethodInvoker = System.Windows.Forms.MethodInvoker;
+
+namespace AutoUpdaterDotNET;
+
+///
+/// Enum representing the remind later time span.
+///
+public enum RemindLaterFormat
+{
+ ///
+ /// Represents the time span in minutes.
+ ///
+ Minutes,
+
+ ///
+ /// Represents the time span in hours.
+ ///
+ Hours,
+
+ ///
+ /// Represents the time span in days.
+ ///
+ Days
+}
+
+///
+/// Enum representing the effect of Mandatory flag.
+///
+public enum Mode
+{
+ ///
+ /// In this mode, it ignores Remind Later and Skip values set previously and hide both buttons.
+ ///
+ [XmlEnum("0")] Normal,
+
+ ///
+ /// In this mode, it won't show close button in addition to Normal mode behaviour.
+ ///
+ [XmlEnum("1")] Forced,
+
+ ///
+ /// In this mode, it will start downloading and applying update without showing standard update dialog in addition to
+ /// Forced mode behaviour.
+ ///
+ [XmlEnum("2")] ForcedDownload
+}
+
+///
+/// Main class that lets you auto update applications by setting some static fields and executing its Start method.
+///
+public static class AutoUpdater
+{
+ ///
+ /// A delegate type to handle how to exit the application after update is downloaded.
+ ///
+ public delegate void ApplicationExitEventHandler();
+
+ ///
+ /// A delegate type for hooking up update notifications.
+ ///
+ ///
+ /// An object containing all the parameters received from AppCast XML file. If there will be an error
+ /// while looking for the XML file then this object will be null.
+ ///
+ public delegate void CheckForUpdateEventHandler(UpdateInfoEventArgs args);
+
+ ///
+ /// A delegate type for hooking up parsing logic.
+ ///
+ /// An object containing the AppCast file received from server.
+ public delegate void ParseUpdateInfoHandler(ParseUpdateInfoEventArgs args);
+
+ private static bool _isWinFormsApplication;
+
+ private static IWin32Window _owner;
+
+ private static Timer _remindLaterTimer;
+
+ internal static Uri BaseUri;
+
+ internal static bool Running;
+
+ ///
+ /// URL of the xml file that contains information about latest version of the application.
+ ///
+ public static string AppCastURL;
+
+ ///
+ /// Set the Application Title shown in Update dialog. Although AutoUpdater.NET will get it automatically, you can set
+ /// this property if you like to give custom Title.
+ ///
+ public static string AppTitle;
+
+ ///
+ /// Set Basic Authentication credentials to navigate to the change log URL.
+ ///
+ public static IAuthentication BasicAuthChangeLog;
+
+ ///
+ /// Set Basic Authentication credentials required to download the file.
+ ///
+ public static IAuthentication BasicAuthDownload;
+
+ ///
+ /// Set Basic Authentication credentials required to download the XML file.
+ ///
+ public static IAuthentication BasicAuthXML;
+
+ ///
+ /// Set this to true if you want to clear application directory before extracting update.
+ ///
+ public static bool ClearAppDirectory = false;
+
+ ///
+ /// Set it to folder path where you want to download the update file. If not provided then it defaults to Temp folder.
+ ///
+ public static string DownloadPath;
+
+ ///
+ /// If you are using a zip file as an update file, then you can set this value to a new executable path relative to the
+ /// installation directory.
+ ///
+ public static string ExecutablePath;
+
+ ///
+ /// Login/password/domain for FTP-request
+ ///
+ public static NetworkCredential FtpCredentials;
+
+ ///
+ /// Set the User-Agent string to be used for HTTP web requests.
+ ///
+ public static string HttpUserAgent;
+
+ ///
+ /// Set this to change the icon shown on updater dialog.
+ ///
+ public static Bitmap Icon;
+
+ ///
+ /// If you are using a zip file as an update file then you can set this value to path where your app is installed. This
+ /// is only necessary when your installation directory differs from your executable path.
+ ///
+ public static string InstallationPath;
+
+ ///
+ /// You can set this field to your current version if you don't want to determine the version from the assembly.
+ ///
+ public static Version InstalledVersion;
+
+ ///
+ /// If this is true users see dialog where they can set remind later interval otherwise it will take the interval from
+ /// RemindLaterAt and RemindLaterTimeSpan fields.
+ ///
+ public static bool LetUserSelectRemindLater = true;
+
+ ///
+ /// Set this to true if you want to ignore previously assigned Remind Later and Skip settings. It will also hide Remind
+ /// Later and Skip buttons.
+ ///
+ public static bool Mandatory;
+
+ ///
+ /// Opens the download URL in default browser if true. Very useful if you have portable application.
+ ///
+ public static bool OpenDownloadPage;
+
+ ///
+ /// Set this to an instance implementing the IPersistenceProvider interface for using a data storage method different
+ /// from the default Windows Registry based one.
+ ///
+ public static IPersistenceProvider PersistenceProvider;
+
+ ///
+ /// Set Proxy server to use for all the web requests in AutoUpdater.NET.
+ ///
+ public static IWebProxy Proxy;
+
+ ///
+ /// Remind Later interval after user should be reminded of update.
+ ///
+ public static int RemindLaterAt = 2;
+
+ ///
+ /// Set if RemindLaterAt interval should be in Minutes, Hours or Days.
+ ///
+ public static RemindLaterFormat RemindLaterTimeSpan = RemindLaterFormat.Days;
+
+ ///
+ /// AutoUpdater.NET will report errors if this is true.
+ ///
+ public static bool ReportErrors = false;
+
+ ///
+ /// Set this to false if your application doesn't need administrator privileges to replace the old version.
+ ///
+ public static bool RunUpdateAsAdmin = true;
+
+ ///
+ /// If this is true users can see the Remind Later button.
+ ///
+ public static bool ShowRemindLaterButton = true;
+
+ ///
+ /// If this is true users can see the skip button.
+ ///
+ public static bool ShowSkipButton = true;
+
+ ///
+ /// Set this to true if you want to run update check synchronously.
+ ///
+ public static bool Synchronous = false;
+
+ ///
+ /// Modify TopMost property of all dialogs.
+ ///
+ public static bool TopMost = false;
+
+ ///
+ /// Set this if you want the default update form to have a different size.
+ ///
+ public static Size? UpdateFormSize = null;
+
+ ///
+ /// Set this to any of the available modes to change behaviour of the Mandatory flag.
+ ///
+ public static Mode UpdateMode;
+
+ ///
+ /// Gets or sets whether to automatically load and register changelog viewer extensions from DLLs in the application directory.
+ /// Default is true.
+ ///
+ public static bool AutoLoadExtensions { get; set; } = true;
+
+ ///
+ /// Gets or sets the provider to use for displaying changelogs. If null, the factory will use the highest priority available provider.
+ ///
+ public static IChangelogViewerProvider ChangelogViewerProvider { get; set; }
+
+ ///
+ /// An event that developers can use to exit the application gracefully.
+ ///
+ public static event ApplicationExitEventHandler ApplicationExitEvent;
+
+ ///
+ /// An event that clients can use to be notified whenever the update is checked.
+ ///
+ public static event CheckForUpdateEventHandler CheckForUpdateEvent;
+
+ ///
+ /// An event that clients can use to be notified whenever the AppCast file needs parsing.
+ ///
+ public static event ParseUpdateInfoHandler ParseUpdateInfoEvent;
+
+ ///
+ /// Set the owner for all dialogs.
+ ///
+ /// WPF Window or Windows Form object to be used as owner for all dialogs.
+ public static void SetOwner(object obj)
+ {
+ _owner = obj switch
+ {
+ Form form => form,
+ Window window => new Wpf32Window(window),
+ _ => _owner
+ };
+ }
+
+ ///
+ /// Start checking for new version of application and display a dialog to the user if update is available.
+ ///
+ /// Assembly to use for version checking.
+ public static void Start(Assembly myAssembly = null)
+ {
+ Start(AppCastURL, myAssembly);
+ }
+
+ ///
+ /// Start checking for new version of application via FTP and display a dialog to the user if update is available.
+ ///
+ /// FTP URL of the xml file that contains information about latest version of the application.
+ /// Credentials required to connect to FTP server.
+ /// Assembly to use for version checking.
+ public static void Start(string appCast, NetworkCredential ftpCredentials, Assembly myAssembly = null)
+ {
+ FtpCredentials = ftpCredentials;
+ Start(appCast, myAssembly);
+ }
+
+ ///
+ /// Start checking for new version of application and display a dialog to the user if update is available.
+ ///
+ /// URL of the xml file that contains information about latest version of the application.
+ /// Assembly to use for version checking.
+ public static void Start(string appCast, Assembly myAssembly = null)
+ {
+ try
+ {
+ ServicePointManager.SecurityProtocol |= (SecurityProtocolType)192 |
+ (SecurityProtocolType)768 | (SecurityProtocolType)3072;
+ }
+ catch (NotSupportedException)
+ {
+ }
+
+ if (Mandatory && _remindLaterTimer != null)
+ {
+ _remindLaterTimer.Stop();
+ _remindLaterTimer.Close();
+ _remindLaterTimer = null;
+ }
+
+ if (Running || _remindLaterTimer != null)
+ {
+ return;
+ }
+
+ Running = true;
+
+ AppCastURL = appCast;
+
+ _isWinFormsApplication = Application.MessageLoop;
+
+ if (!_isWinFormsApplication)
+ {
+ Application.EnableVisualStyles();
+ }
+
+ Assembly assembly = myAssembly ?? Assembly.GetEntryAssembly();
+
+ if (Synchronous)
+ {
+ try
+ {
+ object result = CheckUpdate(assembly);
+
+ if (StartUpdate(result))
+ {
+ return;
+ }
+
+ Running = false;
+ }
+ catch (Exception exception)
+ {
+ ShowError(exception);
+ }
+ }
+ else
+ {
+ using var backgroundWorker = new BackgroundWorker();
+
+ backgroundWorker.DoWork += (_, args) =>
+ {
+ var mainAssembly = args.Argument as Assembly;
+
+ args.Result = CheckUpdate(mainAssembly);
+ };
+
+ backgroundWorker.RunWorkerCompleted += (_, args) =>
+ {
+ if (args.Error != null)
+ {
+ ShowError(args.Error);
+ }
+ else
+ {
+ if (!args.Cancelled && StartUpdate(args.Result))
+ {
+ return;
+ }
+
+ Running = false;
+ }
+ };
+
+ backgroundWorker.RunWorkerAsync(assembly);
+ }
+ }
+
+ private static object CheckUpdate(Assembly mainAssembly)
+ {
+ var companyAttribute =
+ (AssemblyCompanyAttribute)GetAttribute(mainAssembly, typeof(AssemblyCompanyAttribute));
+ string appCompany = companyAttribute != null ? companyAttribute.Company : "";
+
+ if (string.IsNullOrEmpty(AppTitle))
+ {
+ var titleAttribute =
+ (AssemblyTitleAttribute)GetAttribute(mainAssembly, typeof(AssemblyTitleAttribute));
+ AppTitle = titleAttribute != null ? titleAttribute.Title : mainAssembly.GetName().Name;
+ }
+
+ string registryLocation = !string.IsNullOrEmpty(appCompany)
+ ? $@"Software\{appCompany}\{AppTitle}\AutoUpdater"
+ : $@"Software\{AppTitle}\AutoUpdater";
+
+ PersistenceProvider ??= new RegistryPersistenceProvider(registryLocation);
+
+ UpdateInfoEventArgs args;
+ string xml = null;
+
+ if (AppCastURL != null)
+ {
+ BaseUri = new Uri(AppCastURL);
+ using MyWebClient client = GetWebClient(BaseUri, BasicAuthXML);
+ xml = client.DownloadString(BaseUri);
+ }
+
+ if (ParseUpdateInfoEvent == null)
+ {
+ if (string.IsNullOrEmpty(xml))
+ {
+ throw new Exception("It is required to handle ParseUpdateInfoEvent when XML url is not specified.");
+ }
+
+ var xmlSerializer = new XmlSerializer(typeof(UpdateInfoEventArgs));
+ var xmlTextReader = new XmlTextReader(new StringReader(xml)) { XmlResolver = null };
+ args = (UpdateInfoEventArgs)xmlSerializer.Deserialize(xmlTextReader);
+ }
+ else
+ {
+ var parseArgs = new ParseUpdateInfoEventArgs(xml);
+ ParseUpdateInfoEvent(parseArgs);
+ args = parseArgs.UpdateInfo;
+ }
+
+ if (string.IsNullOrEmpty(args?.CurrentVersion) || string.IsNullOrEmpty(args.DownloadURL))
+ {
+ throw new MissingFieldException();
+ }
+
+ args.InstalledVersion = InstalledVersion ?? mainAssembly.GetName().Version;
+ args.IsUpdateAvailable = new Version(args.CurrentVersion) > args.InstalledVersion;
+
+ if (!Mandatory)
+ {
+ if (string.IsNullOrEmpty(args.Mandatory.MinimumVersion) ||
+ args.InstalledVersion < new Version(args.Mandatory.MinimumVersion))
+ {
+ Mandatory = args.Mandatory.Value;
+ UpdateMode = args.Mandatory.UpdateMode;
+ }
+ }
+
+ if (Mandatory)
+ {
+ ShowRemindLaterButton = false;
+ ShowSkipButton = false;
+ }
+ else
+ {
+ // Read the persisted state from the persistence provider.
+ // This method makes the persistence handling independent from the storage method.
+ Version skippedVersion = PersistenceProvider.GetSkippedVersion();
+ if (skippedVersion != null)
+ {
+ var currentVersion = new Version(args.CurrentVersion);
+ if (currentVersion <= skippedVersion)
+ {
+ return null;
+ }
+
+ if (currentVersion > skippedVersion)
+ {
+ // Update the persisted state. Its no longer makes sense to have this flag set as we are working on a newer application version.
+ PersistenceProvider.SetSkippedVersion(null);
+ }
+ }
+
+ DateTime? remindLaterAt = PersistenceProvider.GetRemindLater();
+ if (remindLaterAt == null)
+ {
+ return args;
+ }
+
+ int compareResult = DateTime.Compare(DateTime.Now, remindLaterAt.Value);
+
+ if (compareResult < 0)
+ {
+ return remindLaterAt.Value;
+ }
+ }
+
+ return args;
+ }
+
+ private static bool StartUpdate(object result)
+ {
+ if (result is DateTime time)
+ {
+ SetTimer(time);
+ }
+ else
+ {
+ if (result is not UpdateInfoEventArgs args)
+ {
+ return false;
+ }
+
+ if (CheckForUpdateEvent != null)
+ {
+ CheckForUpdateEvent(args);
+ }
+ else
+ {
+ if (args.IsUpdateAvailable)
+ {
+ if (Mandatory && UpdateMode == Mode.ForcedDownload)
+ {
+ DownloadUpdate(args);
+ Exit();
+ }
+ else
+ {
+ if (Thread.CurrentThread.GetApartmentState().Equals(ApartmentState.STA))
+ {
+ ShowUpdateForm(args);
+ }
+ else
+ {
+ var thread = new Thread(new ThreadStart(delegate { ShowUpdateForm(args); }));
+ thread.CurrentCulture =
+ thread.CurrentUICulture = CultureInfo.CurrentCulture;
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Start();
+ thread.Join();
+ }
+ }
+
+ return true;
+ }
+
+ if (ReportErrors)
+ {
+ MessageBox.Show(_owner,
+ Resources.UpdateUnavailableMessage,
+ Resources.UpdateUnavailableCaption,
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void ShowError(Exception exception)
+ {
+ if (CheckForUpdateEvent != null)
+ {
+ CheckForUpdateEvent(new UpdateInfoEventArgs { Error = exception });
+ }
+ else
+ {
+ if (ReportErrors)
+ {
+ if (exception is WebException)
+ {
+ MessageBox.Show(_owner,
+ Resources.UpdateCheckFailedMessage,
+ Resources.UpdateCheckFailedCaption,
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ else
+ {
+ MessageBox.Show(_owner,
+ exception.Message,
+ exception.GetType().ToString(),
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+
+ Running = false;
+ }
+
+ ///
+ /// Detects and exits all instances of running assembly, including current.
+ ///
+ internal static void Exit()
+ {
+ var currentProcess = Process.GetCurrentProcess();
+ foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
+ {
+ try
+ {
+ string processPath = process.MainModule?.FileName;
+
+ // Get all instances of assembly except current
+ if (process.Id == currentProcess.Id || currentProcess.MainModule?.FileName != processPath)
+ {
+ continue;
+ }
+
+ if (process.CloseMainWindow())
+ {
+ process.WaitForExit((int)TimeSpan.FromSeconds(10)
+ .TotalMilliseconds); // Give some time to process message
+ }
+
+ if (process.HasExited)
+ {
+ continue;
+ }
+
+ process.Kill(); //TODO: Show UI message asking user to close program himself instead of silently killing it
+ }
+ catch (Exception)
+ {
+ // ignored
+ }
+ }
+
+ if (ApplicationExitEvent != null)
+ {
+ ApplicationExitEvent();
+ }
+ else
+ {
+ if (_isWinFormsApplication)
+ {
+ WinFormsMethodInvoker methodInvoker = Application.Exit;
+ methodInvoker.Invoke();
+ }
+ else if (System.Windows.Application.Current != null)
+ {
+ System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
+ System.Windows.Application.Current.Shutdown()));
+ }
+ else
+ {
+ Environment.Exit(0);
+ }
+ }
+ }
+
+ private static Attribute GetAttribute(Assembly assembly, Type attributeType)
+ {
+ object[] attributes = assembly.GetCustomAttributes(attributeType, false);
+ if (attributes.Length == 0)
+ {
+ return null;
+ }
+
+ return (Attribute)attributes[0];
+ }
+
+ internal static string GetUserAgent()
+ {
+ return string.IsNullOrEmpty(HttpUserAgent) ? "AutoUpdater.NET" : HttpUserAgent;
+ }
+
+ internal static void SetTimer(DateTime remindLater)
+ {
+ TimeSpan timeSpan = remindLater - DateTime.Now;
+
+ SynchronizationContext context = SynchronizationContext.Current;
+
+ _remindLaterTimer = new Timer
+ {
+ Interval = Math.Max(1, timeSpan.TotalMilliseconds),
+ AutoReset = false
+ };
+
+ _remindLaterTimer.Elapsed += delegate
+ {
+ _remindLaterTimer = null;
+ if (context != null)
+ {
+ try
+ {
+ context.Send(_ => Start(), null);
+ }
+ catch (InvalidAsynchronousStateException)
+ {
+ Start();
+ }
+ }
+ else
+ {
+ Start();
+ }
+ };
+
+ _remindLaterTimer.Start();
+ }
+
+ ///
+ /// Opens the Download window that download the update and execute the installer when download completes.
+ ///
+ public static bool DownloadUpdate(UpdateInfoEventArgs args)
+ {
+ using var downloadDialog = new DownloadUpdateDialog(args);
+
+ try
+ {
+ return downloadDialog.ShowDialog(_owner).Equals(DialogResult.OK);
+ }
+ catch (TargetInvocationException)
+ {
+ // ignored
+ }
+
+ return false;
+ }
+
+ ///
+ /// Shows standard update dialog.
+ ///
+ public static void ShowUpdateForm(UpdateInfoEventArgs args)
+ {
+ using var updateForm = new UpdateForm(args);
+
+ if (UpdateFormSize.HasValue)
+ {
+ updateForm.Size = UpdateFormSize.Value;
+ }
+
+ if (updateForm.ShowDialog(_owner).Equals(DialogResult.OK))
+ {
+ Exit();
+ }
+ }
+
+ internal static MyWebClient GetWebClient(Uri uri, IAuthentication basicAuthentication)
+ {
+ var webClient = new MyWebClient
+ {
+ CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore)
+ };
+
+ if (Proxy != null)
+ {
+ webClient.Proxy = Proxy;
+ }
+
+ if (uri.Scheme.Equals(Uri.UriSchemeFtp))
+ {
+ webClient.Credentials = FtpCredentials;
+ }
+ else
+ {
+ basicAuthentication?.Apply(ref webClient);
+
+ webClient.Headers[HttpRequestHeader.UserAgent] = HttpUserAgent;
+ }
+
+ return webClient;
+ }
}
\ No newline at end of file
diff --git a/AutoUpdater.NET/BasicAuthentication.cs b/AutoUpdater.NET/BasicAuthentication.cs
index 59fb7326..65b4ef2f 100644
--- a/AutoUpdater.NET/BasicAuthentication.cs
+++ b/AutoUpdater.NET/BasicAuthentication.cs
@@ -20,9 +20,9 @@ public BasicAuthentication(string username, string password)
Password = password;
}
- internal string Username { get; }
+ public string Username { get; }
- internal string Password { get; }
+ public string Password { get; }
///
public void Apply(ref MyWebClient webClient)
diff --git a/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs b/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs
new file mode 100644
index 00000000..d631b398
--- /dev/null
+++ b/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs
@@ -0,0 +1,102 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Collections.Generic;
+
+namespace AutoUpdaterDotNET.ChangelogViewers;
+
+internal static class ChangelogViewerFactory
+{
+ private static readonly List Providers =
+ [
+ new RichTextBoxViewerProvider(),
+ new WebBrowserViewerProvider()
+ ];
+
+ static ChangelogViewerFactory()
+ {
+ if (AutoUpdater.AutoLoadExtensions)
+ LoadExtensions();
+ }
+
+ public static void RegisterProvider(IChangelogViewerProvider provider)
+ {
+ if (provider == null)
+ throw new ArgumentNullException(nameof(provider));
+
+ var providerType = provider.GetType();
+ var existingProvider = Providers.FirstOrDefault(p => p.GetType() == providerType);
+ if (existingProvider != null)
+ {
+ if (existingProvider.Priority == provider.Priority)
+ return; // Same type and priority, nothing to do
+
+ // Remove existing provider to allow priority override
+ Providers.Remove(existingProvider);
+ }
+
+ Providers.Add(provider);
+ }
+
+ internal static IChangelogViewer Create(IChangelogViewerProvider provider = null)
+ {
+ provider ??= AutoUpdater.ChangelogViewerProvider;
+
+ if (provider != null)
+ {
+ if (!provider.IsAvailable)
+ throw new InvalidOperationException($"The specified viewer provider '{provider.GetType().Name}' is not available in this environment.");
+
+ return provider.CreateViewer();
+ }
+
+ // Get all available providers ordered by priority (highest first)
+ var availableProviders = Providers
+ .Where(p => p.IsAvailable)
+ .OrderByDescending(p => p.Priority);
+
+ var changelogViewerProvider = availableProviders.FirstOrDefault();
+
+ if (changelogViewerProvider == null)
+ throw new InvalidOperationException("No changelog viewers are available in this environment.");
+
+ return changelogViewerProvider.CreateViewer();
+ }
+
+ private static void LoadExtensions()
+ {
+ var extensions = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "AutoUpdater.NET.*.dll");
+ if (extensions.Length == 0) return;
+
+ var iProviderType = typeof(IChangelogViewerProvider);
+ foreach (var extension in extensions)
+ {
+ try
+ {
+ var assembly = Assembly.LoadFrom(extension);
+ var providers = assembly
+ .GetTypes()
+ .Where(t => t.IsClass && !t.IsAbstract && iProviderType.IsAssignableFrom(t));
+
+ foreach (var provider in providers)
+ {
+ try
+ {
+ var instance = (IChangelogViewerProvider)Activator.CreateInstance(provider);
+ if (instance?.IsAvailable == true)
+ RegisterProvider(instance);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Failed to create instance of provider {provider.FullName}: {ex.Message}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Failed to load extension {extension}: {ex.Message}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs b/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs
new file mode 100644
index 00000000..369f75bd
--- /dev/null
+++ b/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs
@@ -0,0 +1,34 @@
+using System.Windows.Forms;
+
+namespace AutoUpdaterDotNET.ChangelogViewers;
+
+///
+/// Interface for changelog viewer implementations
+///
+public interface IChangelogViewer
+{
+ ///
+ /// The Windows Forms control that displays the changelog
+ ///
+ Control Control { get; }
+
+ ///
+ /// Whether this viewer supports loading content from URLs directly
+ ///
+ bool SupportsUrl { get; }
+
+ ///
+ /// Load changelog content as HTML or plain text
+ ///
+ void LoadContent(string content);
+
+ ///
+ /// Load changelog content from a URL
+ ///
+ void LoadUrl(string url);
+
+ ///
+ /// Clean up any resources used by the viewer
+ ///
+ void Cleanup();
+}
\ No newline at end of file
diff --git a/AutoUpdater.NET/ChangelogViewers/IChangelogViewerProvider.cs b/AutoUpdater.NET/ChangelogViewers/IChangelogViewerProvider.cs
new file mode 100644
index 00000000..7f4e01b2
--- /dev/null
+++ b/AutoUpdater.NET/ChangelogViewers/IChangelogViewerProvider.cs
@@ -0,0 +1,22 @@
+namespace AutoUpdaterDotNET.ChangelogViewers;
+
+///
+/// Factory for creating changelog viewers
+///
+public interface IChangelogViewerProvider
+{
+ ///
+ /// Whether this provider can create a viewer in the current environment
+ ///
+ bool IsAvailable { get; }
+
+ ///
+ /// Priority of this provider (higher numbers = higher priority)
+ ///
+ int Priority { get; }
+
+ ///
+ /// Create a new changelog viewer instance
+ ///
+ IChangelogViewer CreateViewer();
+}
\ No newline at end of file
diff --git a/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs
new file mode 100644
index 00000000..2fc37a27
--- /dev/null
+++ b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs
@@ -0,0 +1,58 @@
+using System.Windows.Forms;
+
+namespace AutoUpdaterDotNET.ChangelogViewers;
+
+///
+/// A changelog viewer for displaying changelogs using a RichTextBox control.
+///
+public class RichTextBoxViewer : IChangelogViewer
+{
+ private readonly RichTextBox _richTextBox = new()
+ {
+ ReadOnly = true,
+ Dock = DockStyle.Fill,
+ BackColor = System.Drawing.SystemColors.Control,
+ BorderStyle = BorderStyle.Fixed3D
+ };
+
+ ///
+ public Control Control => _richTextBox;
+
+ ///
+ /// Always returns false
+ ///
+ public bool SupportsUrl => false;
+
+ ///
+ /// Loads the content into the RichTextBox control.
+ ///
+ /// The content to load.
+ public void LoadContent(string content)
+ {
+ _richTextBox.Text = content;
+ }
+
+ ///
+ /// Loads the RTF content into the RichTextBox control.
+ ///
+ /// The RTF content to load.
+ public void LoadContentRtf(string content)
+ {
+ _richTextBox.Rtf = content;
+ }
+
+ ///
+ /// Always throws a NotSupportedException
+ ///
+ ///
+ public void LoadUrl(string url)
+ {
+ throw new System.NotSupportedException("RichTextBox does not support loading from URL");
+ }
+
+ ///
+ public void Cleanup()
+ {
+ _richTextBox.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs
new file mode 100644
index 00000000..eb141712
--- /dev/null
+++ b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs
@@ -0,0 +1,20 @@
+namespace AutoUpdaterDotNET.ChangelogViewers;
+
+///
+/// Provides a text viewer that uses a RichTextBox control.
+///
+public class RichTextBoxViewerProvider(int priority = 0) : IChangelogViewerProvider
+{
+ ///
+ /// Gets whether this provider is available. Always returns true as RichTextBox is always available.
+ ///
+ public bool IsAvailable => true;
+
+ ///
+ public int Priority { get; } = priority;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public IChangelogViewer CreateViewer() => new RichTextBoxViewer();
+}
diff --git a/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs
new file mode 100644
index 00000000..aaa6486c
--- /dev/null
+++ b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs
@@ -0,0 +1,104 @@
+using System.IO;
+using System.Diagnostics;
+using System.Windows.Forms;
+using Microsoft.Win32;
+
+namespace AutoUpdaterDotNET.ChangelogViewers;
+
+///
+/// A changelog viewer for displaying changelogs using a WebBrowser control.
+///
+public class WebBrowserViewer : IChangelogViewer
+{
+ private const string EmulationKey = @"SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION";
+ private readonly int _emulationValue;
+ private readonly string _executableName;
+ private readonly WebBrowser _webBrowser;
+
+ ///
+ public Control Control => _webBrowser;
+
+
+ ///
+ public bool SupportsUrl => true;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public WebBrowserViewer()
+ {
+ _webBrowser = new WebBrowser
+ {
+ Dock = DockStyle.Fill,
+ ScriptErrorsSuppressed = true,
+ WebBrowserShortcutsEnabled = false,
+ IsWebBrowserContextMenuEnabled = false
+ };
+
+ _executableName = Path.GetFileName(
+ Process.GetCurrentProcess().MainModule?.FileName
+ ?? System.Reflection.Assembly.GetEntryAssembly()?.Location
+ ?? Application.ExecutablePath);
+
+ _emulationValue = _webBrowser.Version.Major switch
+ {
+ 11 => 11001,
+ 10 => 10001,
+ 9 => 9999,
+ 8 => 8888,
+ 7 => 7000,
+ _ => 0
+ };
+
+ SetupEmulation();
+ }
+
+ ///
+ public void LoadContent(string content) => _webBrowser.DocumentText = content;
+
+ ///
+ public void LoadUrl(string url)
+ {
+ if (AutoUpdater.BasicAuthChangeLog != null)
+ _webBrowser.Navigate(url, "", null, $"Authorization: {AutoUpdater.BasicAuthChangeLog}");
+ else
+ _webBrowser.Navigate(url);
+ }
+ private void SetupEmulation()
+ {
+ if (_emulationValue == 0)
+ return;
+
+ try
+ {
+ using var registryKey = Registry.CurrentUser.OpenSubKey(EmulationKey, true);
+ registryKey?.SetValue(_executableName, _emulationValue, RegistryValueKind.DWord);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+ private void RemoveEmulation()
+ {
+ if (_emulationValue == 0)
+ return;
+
+ try
+ {
+ using var registryKey = Registry.CurrentUser.OpenSubKey(EmulationKey, true);
+ registryKey?.DeleteValue(_executableName, false);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ ///
+ public void Cleanup()
+ {
+ _webBrowser.Dispose();
+ RemoveEmulation();
+ }
+}
\ No newline at end of file
diff --git a/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs
new file mode 100644
index 00000000..38ec5595
--- /dev/null
+++ b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs
@@ -0,0 +1,25 @@
+namespace AutoUpdaterDotNET.ChangelogViewers;
+
+///
+/// Provides a changelog viewer that uses the default WebBrowser control.
+///
+public class WebBrowserViewerProvider(int priority = 1) : IChangelogViewerProvider
+{
+ ///
+ /// Creates a new instance of the with default priority 1.
+ ///
+ public WebBrowserViewerProvider() : this(1) { }
+
+ ///
+ /// Gets whether this provider is available. Always returns true as WebBrowser is always available on Windows.
+ ///
+ public bool IsAvailable => true;
+
+ ///
+ public int Priority { get; } = priority;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public IChangelogViewer CreateViewer() => new WebBrowserViewer();
+}
diff --git a/AutoUpdater.NET/UpdateForm.Designer.cs b/AutoUpdater.NET/UpdateForm.Designer.cs
index 3d84968a..bfdd6296 100644
--- a/AutoUpdater.NET/UpdateForm.Designer.cs
+++ b/AutoUpdater.NET/UpdateForm.Designer.cs
@@ -9,19 +9,6 @@ sealed partial class UpdateForm
///
private System.ComponentModel.IContainer components = null;
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
#region Windows Form Designer generated code
///
@@ -31,7 +18,6 @@ protected override void Dispose(bool disposing)
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UpdateForm));
- this.webBrowser = new System.Windows.Forms.WebBrowser();
this.labelUpdate = new System.Windows.Forms.Label();
this.labelDescription = new System.Windows.Forms.Label();
this.labelReleaseNotes = new System.Windows.Forms.Label();
@@ -39,17 +25,10 @@ private void InitializeComponent()
this.buttonRemindLater = new System.Windows.Forms.Button();
this.pictureBoxIcon = new System.Windows.Forms.PictureBox();
this.buttonSkip = new System.Windows.Forms.Button();
- this.webView2 = new Microsoft.Web.WebView2.WinForms.WebView2();
+ this.pnlChangelog = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.pictureBoxIcon)).BeginInit();
- ((System.ComponentModel.ISupportInitialize)(this.webView2)).BeginInit();
this.SuspendLayout();
//
- // webBrowser
- //
- resources.ApplyResources(this.webBrowser, "webBrowser");
- this.webBrowser.Name = "webBrowser";
- this.webBrowser.ScriptErrorsSuppressed = true;
- //
// labelUpdate
//
resources.ApplyResources(this.labelUpdate, "labelUpdate");
@@ -97,14 +76,10 @@ private void InitializeComponent()
this.buttonSkip.UseVisualStyleBackColor = true;
this.buttonSkip.Click += new System.EventHandler(this.ButtonSkipClick);
//
- // webView2
+ // pnlChangelog
//
- this.webView2.AllowExternalDrop = true;
- this.webView2.CreationProperties = null;
- this.webView2.DefaultBackgroundColor = System.Drawing.Color.White;
- resources.ApplyResources(this.webView2, "webView2");
- this.webView2.Name = "webView2";
- this.webView2.ZoomFactor = 1D;
+ resources.ApplyResources(this.pnlChangelog, "pnlChangelog");
+ this.pnlChangelog.Name = "pnlChangelog";
//
// UpdateForm
//
@@ -118,8 +93,7 @@ private void InitializeComponent()
this.Controls.Add(this.buttonUpdate);
this.Controls.Add(this.buttonSkip);
this.Controls.Add(this.buttonRemindLater);
- this.Controls.Add(this.webView2);
- this.Controls.Add(this.webBrowser);
+ this.Controls.Add(this.pnlChangelog);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
@@ -128,7 +102,6 @@ private void InitializeComponent()
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.UpdateForm_FormClosed);
this.Load += new System.EventHandler(this.UpdateFormLoad);
((System.ComponentModel.ISupportInitialize)(this.pictureBoxIcon)).EndInit();
- ((System.ComponentModel.ISupportInitialize)(this.webView2)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
@@ -139,11 +112,10 @@ private void InitializeComponent()
private System.Windows.Forms.Button buttonRemindLater;
private System.Windows.Forms.Button buttonUpdate;
private System.Windows.Forms.Button buttonSkip;
- private System.Windows.Forms.WebBrowser webBrowser;
private System.Windows.Forms.Label labelUpdate;
private System.Windows.Forms.Label labelDescription;
private System.Windows.Forms.Label labelReleaseNotes;
private System.Windows.Forms.PictureBox pictureBoxIcon;
- private Microsoft.Web.WebView2.WinForms.WebView2 webView2;
+ private System.Windows.Forms.Panel pnlChangelog;
}
}
\ No newline at end of file
diff --git a/AutoUpdater.NET/UpdateForm.cs b/AutoUpdater.NET/UpdateForm.cs
index cc33d13d..d3c27824 100644
--- a/AutoUpdater.NET/UpdateForm.cs
+++ b/AutoUpdater.NET/UpdateForm.cs
@@ -1,254 +1,172 @@
-using System;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Drawing;
-using System.Globalization;
-using System.IO;
-using System.Windows.Forms;
-using Microsoft.Web.WebView2.Core;
-using Microsoft.Win32;
-using static System.Reflection.Assembly;
-
-namespace AutoUpdaterDotNET;
-
-internal sealed partial class UpdateForm : Form
-{
- private readonly UpdateInfoEventArgs _args;
-
- public UpdateForm(UpdateInfoEventArgs args)
- {
- _args = args;
- InitializeComponent();
- InitializeBrowserControl();
- TopMost = AutoUpdater.TopMost;
-
- if (AutoUpdater.Icon != null)
- {
- pictureBoxIcon.Image = AutoUpdater.Icon;
- Icon = Icon.FromHandle(AutoUpdater.Icon.GetHicon());
- }
-
- buttonSkip.Visible = AutoUpdater.ShowSkipButton;
- buttonRemindLater.Visible = AutoUpdater.ShowRemindLaterButton;
- var resources = new ComponentResourceManager(typeof(UpdateForm));
- Text = string.Format(resources.GetString("$this.Text", CultureInfo.CurrentCulture)!,
- AutoUpdater.AppTitle, _args.CurrentVersion);
- labelUpdate.Text = string.Format(resources.GetString("labelUpdate.Text", CultureInfo.CurrentCulture)!,
- AutoUpdater.AppTitle);
- labelDescription.Text =
- string.Format(resources.GetString("labelDescription.Text", CultureInfo.CurrentCulture)!,
- AutoUpdater.AppTitle, _args.CurrentVersion, _args.InstalledVersion);
-
- if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.Forced)
- {
- ControlBox = false;
- }
- }
-
- private async void InitializeBrowserControl()
- {
- if (string.IsNullOrEmpty(_args.ChangelogURL))
- {
- int reduceHeight = labelReleaseNotes.Height + webBrowser.Height;
- labelReleaseNotes.Hide();
- webBrowser.Hide();
- webView2.Hide();
- Height -= reduceHeight;
- }
- else
- {
- var webView2RuntimeFound = false;
- try
- {
- string availableBrowserVersion = CoreWebView2Environment.GetAvailableBrowserVersionString(null);
- var requiredMinBrowserVersion = "86.0.616.0";
- if (!string.IsNullOrEmpty(availableBrowserVersion)
- && CoreWebView2Environment.CompareBrowserVersions(availableBrowserVersion,
- requiredMinBrowserVersion) >= 0)
- {
- webView2RuntimeFound = true;
- }
- }
- catch (Exception)
- {
- // ignored
- }
-
- if (webView2RuntimeFound)
- {
- webBrowser.Hide();
- webView2.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted;
- await webView2.EnsureCoreWebView2Async(
- await CoreWebView2Environment.CreateAsync(null, Path.GetTempPath()));
- }
- else
- {
- UseLatestIE();
- if (null != AutoUpdater.BasicAuthChangeLog)
- {
- webBrowser.Navigate(_args.ChangelogURL, "", null,
- $"Authorization: {AutoUpdater.BasicAuthChangeLog}");
- }
- else
- {
- webBrowser.Navigate(_args.ChangelogURL);
- }
- }
- }
- }
-
- private void WebView_CoreWebView2InitializationCompleted(object sender,
- CoreWebView2InitializationCompletedEventArgs e)
- {
- if (!e.IsSuccess)
- {
- if (AutoUpdater.ReportErrors)
- {
- MessageBox.Show(this, e.InitializationException.Message, e.InitializationException.GetType().ToString(),
- MessageBoxButtons.OK, MessageBoxIcon.Error);
- }
-
- return;
- }
-
- webView2.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
- webView2.CoreWebView2.Settings.IsStatusBarEnabled = false;
- webView2.CoreWebView2.Settings.AreDevToolsEnabled = Debugger.IsAttached;
- webView2.CoreWebView2.Settings.UserAgent = AutoUpdater.GetUserAgent();
- webView2.CoreWebView2.Profile.ClearBrowsingDataAsync();
- webView2.Show();
- webView2.BringToFront();
- if (null != AutoUpdater.BasicAuthChangeLog)
- {
- webView2.CoreWebView2.BasicAuthenticationRequested += delegate(
- object _,
- CoreWebView2BasicAuthenticationRequestedEventArgs args)
- {
- args.Response.UserName = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Username;
- args.Response.Password = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Password;
- };
- }
-
- webView2.CoreWebView2.Navigate(_args.ChangelogURL);
- }
-
- private void UseLatestIE()
- {
- int ieValue = webBrowser.Version.Major switch
- {
- 11 => 11001,
- 10 => 10001,
- 9 => 9999,
- 8 => 8888,
- 7 => 7000,
- _ => 0
- };
-
- if (ieValue == 0)
- {
- return;
- }
-
- try
- {
- using RegistryKey registryKey =
- Registry.CurrentUser.OpenSubKey(
- @"SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION",
- true);
- registryKey?.SetValue(
- Path.GetFileName(Process.GetCurrentProcess().MainModule?.FileName ??
- GetEntryAssembly()?.Location ?? Application.ExecutablePath),
- ieValue,
- RegistryValueKind.DWord);
- }
- catch (Exception)
- {
- // ignored
- }
- }
-
- private void UpdateFormLoad(object sender, EventArgs e)
- {
- var labelSize = new Size(webBrowser.Width, 0);
- labelDescription.MaximumSize = labelUpdate.MaximumSize = labelSize;
- }
-
- private void ButtonUpdateClick(object sender, EventArgs e)
- {
- if (AutoUpdater.OpenDownloadPage)
- {
-
- var processStartInfo = new ProcessStartInfo(_args.DownloadURL);
-#if NETCOREAPP
- // for .NET Core, UseShellExecute must be set to true, otherwise
- // opening URLs via Process.Start() fails
- processStartInfo.UseShellExecute = true;
-#endif
- Process.Start(processStartInfo);
-
- DialogResult = DialogResult.OK;
- }
- else
- {
- if (AutoUpdater.DownloadUpdate(_args))
- {
- DialogResult = DialogResult.OK;
- }
- }
- }
-
- private void ButtonSkipClick(object sender, EventArgs e)
- {
- AutoUpdater.PersistenceProvider.SetSkippedVersion(new Version(_args.CurrentVersion));
- }
-
- private void ButtonRemindLaterClick(object sender, EventArgs e)
- {
- if (AutoUpdater.LetUserSelectRemindLater)
- {
- using var remindLaterForm = new RemindLaterForm();
- DialogResult dialogResult = remindLaterForm.ShowDialog(this);
-
- switch (dialogResult)
- {
- case DialogResult.OK:
- AutoUpdater.RemindLaterTimeSpan = remindLaterForm.RemindLaterFormat;
- AutoUpdater.RemindLaterAt = remindLaterForm.RemindLaterAt;
- break;
- case DialogResult.Abort:
- ButtonUpdateClick(sender, e);
- return;
- default:
- return;
- }
- }
-
- AutoUpdater.PersistenceProvider.SetSkippedVersion(null);
-
- DateTime remindLaterDateTime = AutoUpdater.RemindLaterTimeSpan switch
- {
- RemindLaterFormat.Days => DateTime.Now + TimeSpan.FromDays(AutoUpdater.RemindLaterAt),
- RemindLaterFormat.Hours => DateTime.Now + TimeSpan.FromHours(AutoUpdater.RemindLaterAt),
- RemindLaterFormat.Minutes => DateTime.Now + TimeSpan.FromMinutes(AutoUpdater.RemindLaterAt),
- _ => DateTime.Now
- };
-
- AutoUpdater.PersistenceProvider.SetRemindLater(remindLaterDateTime);
- AutoUpdater.SetTimer(remindLaterDateTime);
-
- DialogResult = DialogResult.Cancel;
- }
-
- private void UpdateForm_FormClosed(object sender, FormClosedEventArgs e)
- {
- AutoUpdater.Running = false;
- }
-
- private void UpdateForm_FormClosing(object sender, FormClosingEventArgs e)
- {
- if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.Forced)
- {
- AutoUpdater.Exit();
- }
- }
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Globalization;
+using System.Windows.Forms;
+using AutoUpdaterDotNET.ChangelogViewers;
+
+namespace AutoUpdaterDotNET;
+
+internal sealed partial class UpdateForm : Form
+{
+ private readonly UpdateInfoEventArgs _args;
+ private IChangelogViewer _changelogViewer;
+
+ public UpdateForm(UpdateInfoEventArgs args)
+ {
+ _args = args;
+ InitializeComponent();
+ InitializeChangelogViewer();
+ TopMost = AutoUpdater.TopMost;
+
+ if (AutoUpdater.Icon != null)
+ {
+ pictureBoxIcon.Image = AutoUpdater.Icon;
+ Icon = Icon.FromHandle(AutoUpdater.Icon.GetHicon());
+ }
+
+ buttonSkip.Visible = AutoUpdater.ShowSkipButton;
+ buttonRemindLater.Visible = AutoUpdater.ShowRemindLaterButton;
+ var resources = new ComponentResourceManager(typeof(UpdateForm));
+ Text = string.Format(resources.GetString("$this.Text", CultureInfo.CurrentCulture)!,
+ AutoUpdater.AppTitle, _args.CurrentVersion);
+ labelUpdate.Text = string.Format(resources.GetString("labelUpdate.Text", CultureInfo.CurrentCulture)!,
+ AutoUpdater.AppTitle);
+ labelDescription.Text =
+ string.Format(resources.GetString("labelDescription.Text", CultureInfo.CurrentCulture)!,
+ AutoUpdater.AppTitle, _args.CurrentVersion, _args.InstalledVersion);
+
+ if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.Forced)
+ {
+ ControlBox = false;
+ }
+ }
+
+ private void InitializeChangelogViewer()
+ {
+ if (string.IsNullOrEmpty(_args.ChangelogURL) && string.IsNullOrEmpty(_args.ChangelogText))
+ {
+ var reduceHeight = labelReleaseNotes.Height + pnlChangelog.Height;
+ labelReleaseNotes.Hide();
+ Height -= reduceHeight;
+ return;
+ }
+
+ // Create and configure the new viewer
+ _changelogViewer = ChangelogViewerFactory.Create(AutoUpdater.ChangelogViewerProvider);
+ var viewerControl = _changelogViewer.Control;
+ viewerControl.Dock = DockStyle.Fill;
+ pnlChangelog.Controls.Add(viewerControl);
+
+ if (!string.IsNullOrEmpty(_args.ChangelogText))
+ {
+ _changelogViewer.LoadContent(_args.ChangelogText);
+ }
+ else if (_changelogViewer.SupportsUrl && !string.IsNullOrEmpty(_args.ChangelogURL))
+ {
+ _changelogViewer.LoadUrl(_args.ChangelogURL);
+ }
+ else
+ {
+ labelReleaseNotes.Hide();
+ viewerControl.Hide();
+ Height -= (labelReleaseNotes.Height + viewerControl.Height);
+ }
+ }
+
+ private void UpdateFormLoad(object sender, EventArgs e)
+ {
+ var labelSize = new Size(pnlChangelog.Width, 0);
+ labelDescription.MaximumSize = labelUpdate.MaximumSize = labelSize;
+ }
+
+ private void ButtonUpdateClick(object sender, EventArgs e)
+ {
+ if (AutoUpdater.OpenDownloadPage)
+ {
+
+ var processStartInfo = new ProcessStartInfo(_args.DownloadURL);
+#if NETCOREAPP
+ // for .NET Core, UseShellExecute must be set to true, otherwise
+ // opening URLs via Process.Start() fails
+ processStartInfo.UseShellExecute = true;
+#endif
+ Process.Start(processStartInfo);
+
+ DialogResult = DialogResult.OK;
+ }
+ else
+ {
+ if (AutoUpdater.DownloadUpdate(_args))
+ {
+ DialogResult = DialogResult.OK;
+ }
+ }
+ }
+
+ private void ButtonSkipClick(object sender, EventArgs e)
+ {
+ AutoUpdater.PersistenceProvider.SetSkippedVersion(new Version(_args.CurrentVersion));
+ }
+
+ private void ButtonRemindLaterClick(object sender, EventArgs e)
+ {
+ if (AutoUpdater.LetUserSelectRemindLater)
+ {
+ using var remindLaterForm = new RemindLaterForm();
+ DialogResult dialogResult = remindLaterForm.ShowDialog(this);
+
+ switch (dialogResult)
+ {
+ case DialogResult.OK:
+ AutoUpdater.RemindLaterTimeSpan = remindLaterForm.RemindLaterFormat;
+ AutoUpdater.RemindLaterAt = remindLaterForm.RemindLaterAt;
+ break;
+ case DialogResult.Abort:
+ ButtonUpdateClick(sender, e);
+ return;
+ default:
+ return;
+ }
+ }
+
+ AutoUpdater.PersistenceProvider.SetSkippedVersion(null);
+
+ DateTime remindLaterDateTime = AutoUpdater.RemindLaterTimeSpan switch
+ {
+ RemindLaterFormat.Days => DateTime.Now + TimeSpan.FromDays(AutoUpdater.RemindLaterAt),
+ RemindLaterFormat.Hours => DateTime.Now + TimeSpan.FromHours(AutoUpdater.RemindLaterAt),
+ RemindLaterFormat.Minutes => DateTime.Now + TimeSpan.FromMinutes(AutoUpdater.RemindLaterAt),
+ _ => DateTime.Now
+ };
+
+ AutoUpdater.PersistenceProvider.SetRemindLater(remindLaterDateTime);
+ AutoUpdater.SetTimer(remindLaterDateTime);
+
+ DialogResult = DialogResult.Cancel;
+ }
+
+ private void UpdateForm_FormClosed(object sender, FormClosedEventArgs e)
+ {
+ AutoUpdater.Running = false;
+ }
+
+ private void UpdateForm_FormClosing(object sender, FormClosingEventArgs e)
+ {
+ if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.Forced)
+ {
+ AutoUpdater.Exit();
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _changelogViewer?.Cleanup();
+ components?.Dispose();
+ }
+ base.Dispose(disposing);
+ }
}
\ No newline at end of file
diff --git a/AutoUpdater.NET/UpdateForm.resx b/AutoUpdater.NET/UpdateForm.resx
index 58a31560..38729b0d 100644
--- a/AutoUpdater.NET/UpdateForm.resx
+++ b/AutoUpdater.NET/UpdateForm.resx
@@ -125,37 +125,37 @@
-
+
Top, Bottom, Left, Right
-
+
94, 120
-
+
2, 2, 2, 2
-
+
23, 23
-
+
538, 432
-
+
4
-
- webBrowser
+
+ pnlChangelog
-
- System.Windows.Forms.WebBrowser, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
+
$this
-
+
8
@@ -395,39 +395,6 @@
5
-
-
- Top, Bottom, Left, Right
-
-
- 94, 120
-
-
- 2, 2, 2, 2
-
-
- 23, 23
-
-
- 538, 432
-
-
- 3
-
-
- False
-
-
- webView2
-
-
- Microsoft.Web.WebView2.WinForms.WebView2, Microsoft.Web.WebView2.WinForms, Version=1.0.1210.39, Culture=neutral, PublicKeyToken=2a8ab48044d2601e
-
-
- $this
-
-
- 7
diff --git a/AutoUpdater.NET/UpdateInfoEventArgs.cs b/AutoUpdater.NET/UpdateInfoEventArgs.cs
index ba326437..b795a653 100644
--- a/AutoUpdater.NET/UpdateInfoEventArgs.cs
+++ b/AutoUpdater.NET/UpdateInfoEventArgs.cs
@@ -49,6 +49,12 @@ public string ChangelogURL
set => _changelogURL = value;
}
+ ///
+ /// Returns text specifying changes in the new update.
+ ///
+ [XmlElement("changelogText")]
+ public string ChangelogText { get; set; }
+
///
/// Returns newest version of the application available to download.
///
diff --git a/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec b/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec
index f71cf5ca..e377d25f 100644
--- a/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec
+++ b/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec
@@ -2,7 +2,7 @@
Autoupdater.NET.Official
- 1.9.3.0
+ 1.9.5.1
AutoUpdater.NET
rbsoft
false
@@ -14,27 +14,15 @@
functionality to their WinForms or WPF application projects.
https://github.com/ravibpatel/AutoUpdater.NET/releases
- Copyright © 2012-2024 RBSoft
+ Copyright © 2012-2025 RBSoft
autoupdate updater c# vb wpf winforms
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/AutoUpdaterTest/AutoUpdaterTest.csproj b/AutoUpdaterTest/AutoUpdaterTest.csproj
index c9eb2b55..6efb2d89 100644
--- a/AutoUpdaterTest/AutoUpdaterTest.csproj
+++ b/AutoUpdaterTest/AutoUpdaterTest.csproj
@@ -2,7 +2,7 @@
WinExe
- net8.0-windows
+ net462;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows
enable
true
latest
@@ -11,6 +11,8 @@
+
+
@@ -38,5 +40,6 @@
+
diff --git a/AutoUpdaterTest/MainWindow.xaml.cs b/AutoUpdaterTest/MainWindow.xaml.cs
index 4e3e35ff..d3187815 100644
--- a/AutoUpdaterTest/MainWindow.xaml.cs
+++ b/AutoUpdaterTest/MainWindow.xaml.cs
@@ -25,33 +25,6 @@ public MainWindow()
private void ButtonCheckForUpdate_Click(object sender, RoutedEventArgs e)
{
- // Uncomment following lines to handle parsing logic of custom AppCast file.
- // AutoUpdater.ParseUpdateInfoEvent += args =>
- // {
- // dynamic? json = JsonConvert.DeserializeObject(args.RemoteData);
- // if (json != null)
- // {
- // args.UpdateInfo = new UpdateInfoEventArgs
- // {
- // CurrentVersion = json.version,
- // ChangelogURL = json.changelog,
- // DownloadURL = json.url,
- // Mandatory = new Mandatory
- // {
- // Value = json.mandatory.value,
- // UpdateMode = json.mandatory.mode,
- // MinimumVersion = json.mandatory.minVersion
- // },
- // CheckSum = new CheckSum
- // {
- // Value = json.checksum.value,
- // HashingAlgorithm = json.checksum.hashingAlgorithm
- // }
- // };
- // }
- // };
- // AutoUpdater.Start("https://rbsoft.org/updates/AutoUpdaterTest.json");
-
// Uncomment following line to run update process without admin privileges.
// AutoUpdater.RunUpdateAsAdmin = false;
@@ -200,6 +173,45 @@ private void ButtonCheckForUpdate_Click(object sender, RoutedEventArgs e)
// Uncomment following line to change the Icon shown on the updater dialog.
AutoUpdater.Icon = Resource.Icon;
+ // Uncomment following line to change the Changelog viewer type.
+ //AutoUpdater.ChangelogViewerProvider = new RichTextBoxViewerProvider();
+
+ // Uncomment following lines to handle parsing logic of custom AppCast file.
+ //AutoUpdater.HttpUserAgent = "AutoUpdaterTest";
+ //AutoUpdater.ParseUpdateInfoEvent += p =>
+ //{
+ // var json = JsonConvert.DeserializeObject(p.RemoteData);
+ // if (json == null) return;
+ // p.UpdateInfo = new UpdateInfoEventArgs();
+ // p.UpdateInfo.CurrentVersion = json.tag_name.Value.TrimStart('v');
+ // p.UpdateInfo.ChangelogText = json.body;
+ // if (AutoUpdater.ChangelogViewerProvider is WebBrowserViewerProvider or WebView2ViewerProvider)
+ // {
+ // p.UpdateInfo.ChangelogText = json.body.Value.Replace("\r\n", "
").Replace("\n", "
");
+ // p.UpdateInfo.ChangelogText = $"{p.UpdateInfo.ChangelogText}";
+ // }
+ // //e.UpdateInfo.ChangelogURL = json.html_url;
+
+ // foreach (var asset in json.assets)
+ // {
+ // // Get the matched runtime & architecture asset
+ // //var split = asset.name.Value.Split('_');
+ // //if (split.Length < 4)
+ // // continue;
+
+ // //var arch = split[2].Split('.')[0];
+ // //var runtime = split[3].Replace(".zip", "");
+
+ // //if ("NetFW4.8.1" != runtime || arch != "x64")
+ // // continue;
+
+ // var matchedAsset = asset.browser_download_url;
+ // p.UpdateInfo.DownloadURL = matchedAsset;
+ // break;
+ // }
+ //};
+ //AutoUpdater.Start("https://api.github.com/repos/ravibpatel/AutoUpdater.NET/releases/latest");
+
AutoUpdater.Start("https://rbsoft.org/updates/AutoUpdaterTest.xml");
}
}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index f8d69934..1bf48718 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2012-2024 RBSoft
+Copyright (c) 2012-2025 RBSoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index a4b24c69..8e49b115 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,14 @@ desktop application projects.
PM> Install-Package Autoupdater.NET.Official
````
+````powershell
+PM> Install-Package Autoupdater.NET.Official.Markdown
+````
+
+````powershell
+PM> Install-Package Autoupdater.NET.Official.WebView2
+````
+
## Supported .NET versions
* .NET Framework 4.6.2 or above
@@ -47,6 +55,7 @@ software. You need to create XML file like below and then you need to upload it
-
2.0.0.0
https://rbsoft.org/downloads/AutoUpdaterTest.zip
+
https://github.com/ravibpatel/AutoUpdater.NET/releases
false
@@ -58,6 +67,8 @@ There are two things you need to provide in XML file as you can see above.
X.X.X.X format.
* url (Required): You need to provide URL of the latest version installer file or zip file between url tags.
AutoUpdater.NET downloads the file provided here and install it when user press the Update button.
+* changelogText (Optional): You can provide the change log of your application between changelogText tags. If you
+ provide changelogText it will take precedence over changelog URL.
* changelog (Optional): You need to provide URL of the change log of your application between changelog tags. If you
don't provide the URL of the changelog then update dialog won't show the change log.
* mandatory (Optional): You can set this to true if you don't want user to skip this version. This will ignore Remind
@@ -376,6 +387,99 @@ You can create your own PersistenceProvider by
implementing [IPersistenceProvider](https://github.com/ravibpatel/AutoUpdater.NET/blob/master/AutoUpdater.NET/IPersistenceProvider.cs)
interface.
+## Changelog Viewers
+
+AutoUpdater.NET provides multiple options for displaying changelogs:
+
+### Core Package (Autoupdater.NET.Official)
+
+The core package includes two basic changelog viewers:
+* **WebBrowser Viewer** (Default, Priority 1): Uses the default WebBrowser control
+* **RichTextBox Viewer** (Priority 0): Simple text-only viewer, used as a last resort
+
+### Additional Packages
+
+#### Markdown Package (Autoupdater.NET.Official.Markdown)
+
+````powershell
+PM> Install-Package Autoupdater.NET.Official.Markdown
+````
+
+Adds support for rendering Markdown changelogs with:
+* Syntax highlighting
+* Tables
+* Lists
+* And other Markdown features
+* Priority 2 (higher than WebBrowser)
+
+#### WebView2 Package (Autoupdater.NET.Official.WebView2)
+
+````powershell
+PM> Install-Package Autoupdater.NET.Official.WebView2
+````
+
+Adds modern web rendering support using Microsoft Edge WebView2:
+* Better performance
+* Modern web standards support
+* Enhanced security
+* Priority 3 (highest priority when available)
+
+### Priority System
+
+The viewers are selected based on their priority and availability:
+1. WebView2 (Priority 3) - If WebView2 runtime is available
+2. Markdown (Priority 2) - If Markdown package is installed
+3. WebBrowser (Priority 1) - Always available on Windows
+4. RichTextBox (Priority 0) - Fallback option
+
+You can bypass the priority system and use a specific viewer directly:
+````csharp
+// Use RichTextBox viewer regardless of other providers
+AutoUpdater.ChangelogViewerProvider = new RichTextBoxViewerProvider();
+````
+
+### Custom Viewers
+
+You can even implement your own changelog viewer by:
+1. Implementing the `IChangelogViewer` interface
+2. Creating a provider that implements `IChangelogViewerProvider`
+3. Registering your provider using `ChangelogViewerFactory.RegisterProvider`
+
+Example:
+````csharp
+public class CustomViewer : IChangelogViewer
+{
+ public Control Control => /* your viewer control */;
+ public bool SupportsUrl => true;
+
+ public void LoadContent(string content)
+ {
+ // Implementation
+ }
+
+ public void LoadUrl(string url)
+ {
+ // Implementation
+ }
+
+ public void Cleanup()
+ {
+ // Cleanup resources
+ }
+}
+
+public class CustomViewerProvider : IChangelogViewerProvider
+{
+ public bool IsAvailable => true;
+ public int Priority => 100; // Higher than built-in viewers
+
+ public IChangelogViewer CreateViewer() => new CustomViewer();
+}
+
+// Register your custom viewer
+ChangelogViewerFactory.RegisterProvider(new CustomViewerProvider());
+````
+
## Check updates frequently
You can call Start method inside Timer to check for updates frequently.
@@ -505,6 +609,7 @@ update dialog.
* IsUpdateAvailable (bool) : If update is available then returns true otherwise false.
* DownloadURL (string) : Download URL of the update file..
+* ChangelogText (string) : Text of the changelog.
* ChangelogURL (string) : URL of the webpage specifying changes in the new update.
* CurrentVersion (Version) : Newest version of the application available to download.
* InstalledVersion (Version) : Version of the application currently installed on the user's PC.
@@ -525,6 +630,7 @@ private void AutoUpdaterOnParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
args.UpdateInfo = new UpdateInfoEventArgs
{
CurrentVersion = json.version,
+ // ChangelogText = json.changelog,
ChangelogURL = json.changelog,
DownloadURL = json.url,
Mandatory = new Mandatory
@@ -548,6 +654,7 @@ private void AutoUpdaterOnParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
{
"version":"2.0.0.0",
"url":"https://rbsoft.org/downloads/AutoUpdaterTest.zip",
+ // "changelogText":"Update to version 2.0.0.0...",
"changelog":"https://github.com/ravibpatel/AutoUpdater.NET/releases",
"mandatory":{
"value":true,
@@ -581,7 +688,7 @@ You can follow below steps to build the project on your local development enviro
```
* Build ZipExtractor project in "Release" configuration to create the executable in Resources folder. While compiling it
- for .NET Core 3.1 or above, you have to use publish command instead of build as
+ for .NET Core 3.1 or above then you have to use publish command instead of build as
shown [here](https://learn.microsoft.com/en-us/dotnet/core/tutorials/publishing-with-visual-studio?pivots=dotnet-7-0)
and copy the resulting executable to "AutoUpdater.NET/Resources" folder.
* Visual Studio 2022 doesn't allow building .NET Framework 4.5 by default, so if you are using Visual Studio 2022 then
diff --git a/ZipExtractor/ZipExtractor.csproj b/ZipExtractor/ZipExtractor.csproj
index b27d1ac7..4e9704c6 100644
--- a/ZipExtractor/ZipExtractor.csproj
+++ b/ZipExtractor/ZipExtractor.csproj
@@ -8,7 +8,7 @@
ZipExtractor
RBSoft
ZipExtractor
- Copyright © 2012-2024 RBSoft
+ Copyright © 2012-2025 RBSoft
1.5.4.0
1.5.4.0
1.5.4.0
diff --git a/build.bat b/build.bat
index ea6aea42..ffdec46b 100644
--- a/build.bat
+++ b/build.bat
@@ -3,26 +3,42 @@ msbuild "ZipExtractor\ZipExtractor.csproj" /p:Configuration=Release /verbosity:m
:: .NET Framework 4.6.2
msbuild "AutoUpdater.NET\AutoUpdater.NET.csproj" /p:OutputPath=build\lib\net462;TargetFramework=net462;Configuration=Release /verbosity:minimal
+msbuild "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" /p:OutputPath=build\lib\net462;TargetFramework=net462;Configuration=Release /verbosity:minimal
+msbuild "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" /p:OutputPath=build\lib\net462;TargetFramework=net462;Configuration=Release /verbosity:minimal
:: .NET Core 3.1
dotnet publish --configuration Release --framework netcoreapp3.1 "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\netcoreapp3.1"
+dotnet publish --configuration Release --framework netcoreapp3.1 "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\netcoreapp3.1"
+dotnet publish --configuration Release --framework netcoreapp3.1 "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\netcoreapp3.1"
:: .NET 5.0
dotnet publish --configuration Release --framework net5.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net5.0-windows7.0"
+dotnet publish --configuration Release --framework net5.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net5.0-windows7.0"
+dotnet publish --configuration Release --framework net5.0-windows "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\net5.0-windows7.0"
:: .NET 6.0
dotnet publish --configuration Release --framework net6.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net6.0-windows7.0"
+dotnet publish --configuration Release --framework net6.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net6.0-windows7.0"
+dotnet publish --configuration Release --framework net6.0-windows "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\net6.0-windows7.0"
:: .NET 7.0
dotnet publish --configuration Release --framework net7.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net7.0-windows7.0"
+dotnet publish --configuration Release --framework net7.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net7.0-windows7.0"
+dotnet publish --configuration Release --framework net7.0-windows "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\net7.0-windows7.0"
:: .NET 8.0
dotnet publish --configuration Release --framework net8.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net8.0-windows7.0"
+dotnet publish --configuration Release --framework net8.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net8.0-windows7.0"
+dotnet publish --configuration Release --framework net8.0-windows "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\net8.0-windows7.0"
:: Remove unnecessary files
-Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET\build\lib\* -include runtimes,Microsoft.Web.WebView2*,AutoUpdater.NET.deps.json -Recurse"
+Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET\build\lib\* -include *.deps.json -Recurse"
+Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET.Markdown\build\lib\* -include *.deps.json,*.resources.dll,AutoUpdater.NET.dll,AutoUpdater.NET.xml,AutoUpdater.NET.pdb,Markdig.dll -Recurse"
+Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET.WebView2\build\lib\* -include runtimes,*.deps.json,*.resources.dll,Microsoft.Web.WebView2*,AutoUpdater.NET.dll,AutoUpdater.NET.xml,AutoUpdater.NET.pdb -Recurse"
-:: Create NuGet package
+:: Create NuGet packages
nuget pack AutoUpdater.NET\build\Autoupdater.NET.Official.nuspec -Verbosity detailed -OutputDirectory AutoUpdater.NET\build
+nuget pack AutoUpdater.NET.WebView2\build\Autoupdater.NET.WebView2.nuspec -Verbosity detailed -OutputDirectory AutoUpdater.NET.WebView2\build
+nuget pack AutoUpdater.NET.Markdown\build\Autoupdater.NET.Markdown.nuspec -Verbosity detailed -OutputDirectory AutoUpdater.NET.Markdown\build
pause
\ No newline at end of file