diff --git a/Application/SystemHandler.cs b/Application/SystemHandler.cs
index 4daef5be..0ba99237 100644
--- a/Application/SystemHandler.cs
+++ b/Application/SystemHandler.cs
@@ -1,9 +1,21 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
 using System.IO;
 using TweetLib.Core.Application;
 
 namespace TweetDuck.Application{
     class SystemHandler : IAppSystemHandler{
+        void IAppSystemHandler.OpenAssociatedProgram(string path){
+            try{
+                using(Process.Start(new ProcessStartInfo{
+                    FileName = path,
+                    ErrorDialog = true
+                })){}
+            }catch(Exception e){
+                Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e);
+            }
+        }
+
         void IAppSystemHandler.OpenFileExplorer(string path){
             if (File.Exists(path)){
                 using(Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")){}
diff --git a/Browser/FormBrowser.cs b/Browser/FormBrowser.cs
index c33af784..87b04f5c 100644
--- a/Browser/FormBrowser.cs
+++ b/Browser/FormBrowser.cs
@@ -44,7 +44,7 @@ public bool IsWaiting{
             }
         }
 
-        public string UpdateInstallerPath { get; private set; }
+        public UpdateInstaller UpdateInstaller { get; private set; }
         private bool ignoreUpdateCheckError;
         
         public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
@@ -233,7 +233,7 @@ private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){
         }
 
         private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e){
-            if (isLoaded && UpdateInstallerPath == null){
+            if (isLoaded && UpdateInstaller == null){
                 updateBridge.Cleanup();
             }
         }
@@ -311,7 +311,7 @@ void OnFinished(){
                 UpdateDownloadStatus status = update.DownloadStatus;
 
                 if (status == UpdateDownloadStatus.Done){
-                    UpdateInstallerPath = update.InstallerPath;
+                    UpdateInstaller = new UpdateInstaller(update.InstallerPath);
                     ForceClose();
                 }
                 else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: " + (update.DownloadError?.Message ?? "unknown error") + "\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
diff --git a/Program.cs b/Program.cs
index e064f383..1a849c31 100644
--- a/Program.cs
+++ b/Program.cs
@@ -192,14 +192,10 @@ private static void Main(){
             Resources.Initialize(mainForm);
             Win.Application.Run(mainForm);
 
-            if (mainForm.UpdateInstallerPath != null){
+            if (mainForm.UpdateInstaller != null){
                 ExitCleanup();
 
-                // ProgramPath has a trailing backslash
-                string updaterArgs = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\"" + ProgramPath + "\" /RUNARGS=\"" + Arguments.GetCurrentForInstallerCmd() + "\"" + (IsPortable ? " /PORTABLE=1" : "");
-                bool runElevated = !IsPortable || !FileUtils.CheckFolderWritePermission(ProgramPath);
-
-                if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
+                if (mainForm.UpdateInstaller.Launch()){
                     Win.Application.Exit();
                 }
                 else{
diff --git a/TweetDuck.csproj b/TweetDuck.csproj
index 3db0655f..2f69abf8 100644
--- a/TweetDuck.csproj
+++ b/TweetDuck.csproj
@@ -54,6 +54,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Plugins\PluginSchemeFactory.cs" />
+    <Compile Include="Updates\UpdateInstaller.cs" />
     <Compile Include="Version.cs" />
     <Compile Include="Configuration\Arguments.cs" />
     <Compile Include="Configuration\ConfigManager.cs" />
diff --git a/Updates/UpdateInstaller.cs b/Updates/UpdateInstaller.cs
new file mode 100644
index 00000000..ede6f1be
--- /dev/null
+++ b/Updates/UpdateInstaller.cs
@@ -0,0 +1,37 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using TweetDuck.Configuration;
+using TweetLib.Core.Utils;
+
+namespace TweetDuck.Updates{
+    sealed class UpdateInstaller{
+        public string Path { get; }
+
+        public UpdateInstaller(string path){
+            this.Path = path;
+        }
+
+        public bool Launch(){
+            // ProgramPath has a trailing backslash
+            string arguments = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\"" + Program.ProgramPath + "\" /RUNARGS=\"" + Arguments.GetCurrentForInstallerCmd() + "\"" + (Program.IsPortable ? " /PORTABLE=1" : "");
+            bool runElevated = !Program.IsPortable || !FileUtils.CheckFolderWritePermission(Program.ProgramPath);
+
+            try{
+                using(Process.Start(new ProcessStartInfo{
+                    FileName = Path,
+                    Arguments = arguments,
+                    Verb = runElevated ? "runas" : string.Empty,
+                    ErrorDialog = true
+                })){
+                    return true;
+                }
+            }catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
+                return false;
+            }catch(Exception e){
+                Program.Reporter.HandleException("Update Installer Error", "Could not launch update installer.", true, e);
+                return false;
+            }
+        }
+    }
+}
diff --git a/Utils/BrowserUtils.cs b/Utils/BrowserUtils.cs
index 4ddd365d..5e0b0910 100644
--- a/Utils/BrowserUtils.cs
+++ b/Utils/BrowserUtils.cs
@@ -9,6 +9,7 @@
 using TweetDuck.Configuration;
 using TweetDuck.Dialogs;
 using TweetDuck.Management;
+using TweetLib.Core;
 using TweetLib.Core.Features.Twitter;
 
 namespace TweetDuck.Utils{
@@ -105,7 +106,7 @@ public static void OpenExternalBrowser(string url){
                         string browserPath = Config.BrowserPath;
 
                         if (browserPath == null || !File.Exists(browserPath)){
-                            WindowsUtils.OpenAssociatedProgram(url);
+                            App.SystemHandler.OpenAssociatedProgram(url);
                         }
                         else{
                             string quotedUrl = '"' + url + '"';
diff --git a/Utils/TwitterUtils.cs b/Utils/TwitterUtils.cs
index a41876d8..1316df23 100644
--- a/Utils/TwitterUtils.cs
+++ b/Utils/TwitterUtils.cs
@@ -9,6 +9,7 @@
 using TweetDuck.Browser.Data;
 using TweetDuck.Dialogs;
 using TweetDuck.Management;
+using TweetLib.Core;
 using TweetLib.Core.Features.Twitter;
 using TweetLib.Core.Utils;
 using Cookie = CefSharp.Cookie;
@@ -44,7 +45,7 @@ public static void ViewImage(string url, ImageQuality quality){
                 string ext = Path.GetExtension(path);
 
                 if (ImageUrl.ValidExtensions.Contains(ext)){
-                    WindowsUtils.OpenAssociatedProgram(path);
+                    App.SystemHandler.OpenAssociatedProgram(path);
                 }
                 else{
                     FormMessage.Error("Image Download", "Invalid file extension " + ext, FormMessage.OK);
diff --git a/Utils/WindowsUtils.cs b/Utils/WindowsUtils.cs
index 574c8d2c..38366919 100644
--- a/Utils/WindowsUtils.cs
+++ b/Utils/WindowsUtils.cs
@@ -1,7 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
 using System.IO;
 using System.Threading;
 using Microsoft.Win32;
@@ -18,24 +16,6 @@ private static bool OSVersionEquals(int major, int minor){
             return ver.Major == major && ver.Minor == minor;
         }
 
-        public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
-            try{
-                using(Process.Start(new ProcessStartInfo{
-                    FileName = file,
-                    Arguments = arguments,
-                    Verb = runElevated ? "runas" : string.Empty,
-                    ErrorDialog = true
-                })){
-                    return true;
-                }
-            }catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
-                return false;
-            }catch(Exception e){
-                Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + file, true, e);
-                return false;
-            }
-        }
-
         public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){
             for(int waited = 0; waited < timeoutMillis; waited += timeStepMillis){
                 if (test()){
diff --git a/lib/TweetLib.Core/Application/IAppSystemHandler.cs b/lib/TweetLib.Core/Application/IAppSystemHandler.cs
index 066b83dc..64a0400f 100644
--- a/lib/TweetLib.Core/Application/IAppSystemHandler.cs
+++ b/lib/TweetLib.Core/Application/IAppSystemHandler.cs
@@ -1,5 +1,6 @@
 namespace TweetLib.Core.Application{
     public interface IAppSystemHandler{
+        void OpenAssociatedProgram(string path);
         void OpenFileExplorer(string path);
     }
 }