#define TRACE using System; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net; using System.Reflection; using System.Threading; namespace Microsoft.Samples.AppUpdater { public class AppDownloader { internal delegate void NotifyUpdateFileInfoEventHandler(object sender, NotifyUpdateFileInfoEventArgs e); internal delegate void UpdateCompleteEventHandler(object sender, UpdateCompleteEventArgs e); private AppUpdater AppMan; private Thread UpdaterThread; private AppStartConfig Config; private UpdateLog Log; private UpdateCompleteEventArgs UpdateEventArgs; private NotifyUpdateFileInfoEventArgs UpdateFileInfoEventArgs; private AppKeys Keys; private bool _ValidateAssemblies; private bool _SynchronizationDownload; private int _SecondsBetweenDownloadRetry = 60; private int _DownloadRetryAttempts = 3; private int _UpdateRetryAttempts = 2; [Description("Specifies whether or not downloaded assemblies must be signed with valid public keys inorder to be downloaded.")] [DefaultValue(false)] public bool ValidateAssemblies { get { return _ValidateAssemblies; } set { _ValidateAssemblies = value; } } [Description("Specifies whether or not downloaded assemblies must be Synchronize.")] [DefaultValue(false)] public bool SynchronizationDownload { get { return _SynchronizationDownload; } set { _SynchronizationDownload = value; } } [Description("Seconds between download retry attempts.")] [DefaultValue(60)] public int SecondsBetweenDownloadRetry { get { return _SecondsBetweenDownloadRetry; } set { _SecondsBetweenDownloadRetry = value; } } [DefaultValue(3)] [Description("Number of times to retry downloads when an error is encountered.")] public int DownloadRetryAttempts { get { return _DownloadRetryAttempts; } set { _DownloadRetryAttempts = value; } } [DefaultValue(2)] [Description("Number of times times to retry the app update.")] public int UpdateRetryAttempts { get { return _UpdateRetryAttempts; } set { _UpdateRetryAttempts = value; } } [Browsable(false)] public UpdateState State => AppMan.Manifest.State; internal event NotifyUpdateFileInfoEventHandler OnNotifyUpdateFileInfo; internal event UpdateCompleteEventHandler OnUpdateComplete; public AppDownloader(AppUpdater appMan) { AppMan = appMan; Log = new UpdateLog(); UpdateEventArgs = new UpdateCompleteEventArgs(); UpdateFileInfoEventArgs = new NotifyUpdateFileInfoEventArgs(); } public void Start() { if (!SynchronizationDownload) { if (UpdaterThread == null) { UpdaterThread = new Thread(RunThread); } else if (!UpdaterThread.IsAlive) { UpdaterThread = new Thread(RunThread); } UpdaterThread.Name = "Updater Thread"; if (!UpdaterThread.IsAlive) { UpdaterThread.Start(); } } else { RunThread(); } } public void Stop() { if (UpdaterThread != null && UpdaterThread.IsAlive) { UpdaterThread.Abort(); UpdaterThread = null; } } public void RunThread() { string directoryName = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); directoryName = Path.Combine(Directory.GetParent(directoryName).FullName, "AppStart.config"); Config = AppStartConfig.Load(directoryName); try { if (AppMan.Manifest.State.Phase == UpdatePhases.Complete) { AppMan.Manifest.State.Phase = UpdatePhases.Scavenging; AppMan.Manifest.State.UpdateFailureCount = 0; AppMan.Manifest.State.UpdateFailureEncoutered = false; AppMan.Manifest.State.DownloadDestination = CreateTempDirectory(); if (AppMan.ChangeDetectionMode == ChangeDetectionModes.ServerManifestCheck) { ServerManifest serverManifest = new ServerManifest(); serverManifest.Load(AppMan.UpdateUrl); AppMan.Manifest.State.DownloadSource = serverManifest.ApplicationUrl; } else { AppMan.Manifest.State.DownloadSource = AppMan.UpdateUrl; } AppMan.Manifest.Update(); } if (AppMan.Manifest.State.Phase == UpdatePhases.Scavenging) { Scavenge(); AppMan.Manifest.State.Phase = UpdatePhases.Downloading; AppMan.Manifest.Update(); } if (AppMan.Manifest.State.Phase == UpdatePhases.Downloading) { Download(); AppMan.Manifest.State.Phase = UpdatePhases.Validating; AppMan.Manifest.Update(); } if (AppMan.Manifest.State.Phase == UpdatePhases.Validating) { Validate(); AppMan.Manifest.State.Phase = UpdatePhases.Merging; AppMan.Manifest.Update(); } if (AppMan.Manifest.State.Phase == UpdatePhases.Merging) { MergeDirectory(AppDomain.CurrentDomain.BaseDirectory, AppMan.Manifest.State.DownloadDestination); AppMan.Manifest.State.Phase = UpdatePhases.Finalizing; AppMan.Manifest.Update(); } if (AppMan.Manifest.State.Phase == UpdatePhases.Finalizing) { FinalizeUpdate(); } AppMan.Manifest.State.Phase = UpdatePhases.Complete; AppMan.Manifest.State.UpdateFailureCount = 0; AppMan.Manifest.State.UpdateFailureEncoutered = false; AppMan.Manifest.State.DownloadSource = ""; AppMan.Manifest.State.DownloadDestination = ""; AppMan.Manifest.State.NewVersionDirectory = ""; AppMan.Manifest.Update(); } catch (ThreadAbortException) { Thread.ResetAbort(); return; } catch (Exception failureException) { UpdateEventArgs.FailureException = failureException; } if (AppMan.Manifest.State.Phase != 0) { HandleUpdateFailure(); } else { HandleUpdateSuccess(); } } private void HandleUpdateSuccess() { try { Log.AddSuccess(GetFileVersion(Config.AppExePath)); } catch (Exception) { } if (this.OnUpdateComplete != null) { UpdateEventArgs.UpdateSucceeded = true; UpdateEventArgs.NewVersion = new Version(GetFileVersion(Config.AppExePath)); if (UpdateEventArgs.ErrorMessage == "") { UpdateEventArgs.ErrorMessage = "Unknown Error"; } this.OnUpdateComplete(this, UpdateEventArgs); } } private void HandleUpdateFailure() { try { Log.AddError(UpdateEventArgs.FailureException.ToString()); } catch (Exception) { } AppMan.Manifest.State.UpdateFailureEncoutered = true; AppMan.Manifest.State.UpdateFailureCount++; AppMan.Manifest.Update(); if (AppMan.Manifest.State.UpdateFailureCount >= UpdateRetryAttempts) { AppMan.Manifest.State.Phase = UpdatePhases.Complete; AppMan.Manifest.State.UpdateFailureEncoutered = false; AppMan.Manifest.State.UpdateFailureCount = 0; AppMan.Manifest.State.DownloadSource = ""; AppMan.Manifest.State.DownloadDestination = ""; AppMan.Manifest.Update(); } if (this.OnUpdateComplete != null) { UpdateEventArgs.UpdateSucceeded = false; if (UpdateEventArgs.ErrorMessage == "") { UpdateEventArgs.ErrorMessage = "Unknown Error"; } this.OnUpdateComplete(this, UpdateEventArgs); } } private void Download() { bool flag = true; int num = 0; int num2 = 0; WebFileLoader.NotifyUpdateFileInfoEventHandler value = this.OnNotifyUpdateFileInfo.Invoke; WebFileLoader.OnNotifyUpdateFileInfo += value; while (flag) { Thread.Sleep(TimeSpan.FromSeconds(num2)); num2 = SecondsBetweenDownloadRetry; num++; Trace.WriteLine("APPMANAGER: Attempting to download update from: " + AppMan.Manifest.State.DownloadSource); try { int fileTotal = WebFileLoader.CopyDirectory(AppMan.Manifest.State.DownloadSource, AppMan.Manifest.State.DownloadDestination, 0, bReport: true); if (this.OnNotifyUpdateFileInfo != null) { UpdateFileInfoEventArgs.FileTotal = fileTotal; UpdateFileInfoEventArgs.FileName = ""; UpdateFileInfoEventArgs.CurrentFile = -1; this.OnNotifyUpdateFileInfo(this, UpdateFileInfoEventArgs); } WebFileLoader.CopyDirectory(AppMan.Manifest.State.DownloadSource, AppMan.Manifest.State.DownloadDestination, fileTotal, bReport: true); flag = false; } catch (WebException ex) { if (num >= DownloadRetryAttempts) { UpdateEventArgs.ErrorMessage = "Download of a new update from '" + AppMan.Manifest.State.DownloadSource + "' failed with the network error: " + ex.Message; WebFileLoader.OnNotifyUpdateFileInfo -= value; throw ex; } } catch (IOException ex2) { if (num >= DownloadRetryAttempts) { UpdateEventArgs.ErrorMessage = "Saving the new update to disk at '" + AppMan.Manifest.State.DownloadDestination + "' failed with the following error: " + ex2.Message; WebFileLoader.OnNotifyUpdateFileInfo -= value; throw ex2; } } catch (Exception ex3) { if (num >= DownloadRetryAttempts) { UpdateEventArgs.ErrorMessage = "Update failed with the following error: '" + ex3.Message + "'"; WebFileLoader.OnNotifyUpdateFileInfo -= value; throw ex3; } } finally { WebFileLoader.OnNotifyUpdateFileInfo -= value; } } } private void Validate() { if (ValidateAssemblies) { Keys = new AppKeys(AppMan.Manifest.State.DownloadSource); Keys.InitializeKeyCheck(); try { ValidateDirectory(AppMan.Manifest.State.DownloadDestination); } catch (Exception) { Keys.UnInitializeKeyCheck(); HardDirectoryDelete(AppMan.Manifest.State.DownloadDestination); AppMan.Manifest.State.UpdateFailureCount = UpdateRetryAttempts; AppMan.Manifest.Update(); throw; } Keys.UnInitializeKeyCheck(); } } private void ValidateDirectory(string source) { try { DirectoryInfo directoryInfo = new DirectoryInfo(source); FileInfo[] files = directoryInfo.GetFiles(); FileInfo[] array = files; foreach (FileInfo fileInfo in array) { if (!Keys.ValidateAssembly(fileInfo.FullName)) { throw new Exception("Invalid assembly: " + fileInfo.FullName); } } DirectoryInfo[] directories = directoryInfo.GetDirectories(); DirectoryInfo[] array2 = directories; foreach (DirectoryInfo directoryInfo2 in array2) { ValidateDirectory(Path.Combine(source, directoryInfo2.Name)); } } catch (Exception ex) { UpdateEventArgs.ErrorMessage = ex.Message; throw; } } private void Scavenge() { DirectoryInfo directoryInfo = new DirectoryInfo(GetParentFolder(AppDomain.CurrentDomain.BaseDirectory)); DirectoryInfo[] directories = directoryInfo.GetDirectories(); DirectoryInfo[] array = directories; foreach (DirectoryInfo directoryInfo2 in array) { if (MakeValidPath(directoryInfo2.FullName).ToLower(new CultureInfo("en-US")) != AppDomain.CurrentDomain.BaseDirectory.ToLower(new CultureInfo("en-US")) && MakeValidPath(directoryInfo2.FullName).ToLower(new CultureInfo("en-US")) != Config.AppPath.ToLower(new CultureInfo("en-US")) && directoryInfo2.Name.ToLower(new CultureInfo("en-US")) != "bin") { try { HardDirectoryDelete(MakeValidPath(directoryInfo2.FullName)); } catch (Exception) { } } } } private void MergeDirectory(string source, string destination) { try { DirectoryInfo directoryInfo = new DirectoryInfo(source); if (!Directory.Exists(destination)) { Directory.CreateDirectory(destination); DirectoryInfo directoryInfo2 = new DirectoryInfo(destination); directoryInfo2.Attributes = directoryInfo.Attributes; } FileInfo[] files = directoryInfo.GetFiles(); FileInfo[] array = files; foreach (FileInfo fileInfo in array) { if (!File.Exists(Path.Combine(destination, fileInfo.Name)) && !isManifestFile(fileInfo.Name)) { fileInfo.CopyTo(Path.Combine(destination, fileInfo.Name), overwrite: true); } } DirectoryInfo[] directories = directoryInfo.GetDirectories(); DirectoryInfo[] array2 = directories; foreach (DirectoryInfo directoryInfo3 in array2) { MergeDirectory(Path.Combine(source, directoryInfo3.Name), Path.Combine(destination, directoryInfo3.Name)); } } catch (Exception ex) { UpdateEventArgs.ErrorMessage = "Copy of user files from the current app directory '" + source + "' to the new app directory'" + destination + "' failed with the following error: " + ex.Message; throw; } } private void FinalizeUpdate() { try { if (AppMan.Manifest.State.NewVersionDirectory == "") { AppMan.Manifest.State.NewVersionDirectory = CreateNewVersionDirectory(); AppMan.Manifest.Update(); } } catch (Exception) { UpdateEventArgs.ErrorMessage = "Failed to create the New Version Directory, using temp download directory as final destination."; AppMan.Manifest.State.NewVersionDirectory = AppMan.Manifest.State.DownloadDestination; AppMan.Manifest.Update(); } try { if (AppMan.Manifest.State.NewVersionDirectory.ToLower(new CultureInfo("en-US")) != AppMan.Manifest.State.DownloadDestination.ToLower(new CultureInfo("en-US"))) { CopyDirectory(AppMan.Manifest.State.DownloadDestination, AppMan.Manifest.State.NewVersionDirectory); } } catch (Exception ex2) { UpdateEventArgs.ErrorMessage = "Failed to copy the downloaded update at: '" + AppMan.Manifest.State.DownloadDestination + "' to the new version directory at: '" + AppMan.Manifest.State.NewVersionDirectory + "'"; throw ex2; } try { char[] trimChars = new char[1] { '\\' }; Config.AppFolderName = Path.GetFileName(AppMan.Manifest.State.NewVersionDirectory.TrimEnd(trimChars)); Config.Udpate(); } catch (Exception ex3) { UpdateEventArgs.ErrorMessage = "Failed to write to 'AppStart.config'"; throw ex3; } try { if (AppMan.Manifest.State.NewVersionDirectory.ToLower(new CultureInfo("en-US")) != AppMan.Manifest.State.DownloadDestination.ToLower(new CultureInfo("en-US"))) { HardDirectoryDelete(AppMan.Manifest.State.DownloadDestination); } } catch (Exception) { } } private string CreateTempDirectory() { int num = 0; Random random = new Random(); string str = ""; string parentFolder = GetParentFolder(AppDomain.CurrentDomain.BaseDirectory); string text; do { text = parentFolder + "AppUpdate" + str + "\\"; str = "_" + random.Next(10000).ToString(); num++; } while (num <= 50 && Directory.Exists(text)); if (num >= 50) { UpdateEventArgs.ErrorMessage = "Failed to created temporary download directory in: '" + parentFolder + "'"; throw new Exception("Failed to create temporary download Directory after 1000 attempts"); } try { Directory.CreateDirectory(text); return text; } catch (Exception ex) { throw ex; } } private string CreateNewVersionDirectory() { string downloadDestination = AppMan.Manifest.State.DownloadDestination; downloadDestination += Config.AppExeName; string fileVersion = GetFileVersion(downloadDestination); string parentFolder = GetParentFolder(AppMan.Manifest.State.DownloadDestination); string text = ""; int num = 1; bool flag = false; string str = ""; do { text = parentFolder + fileVersion + str + "\\"; str = "_" + num.ToString(); num++; try { if (!Directory.Exists(text)) { Directory.CreateDirectory(text); flag = true; } } catch (Exception) { flag = false; } } while (num <= 999 && !flag); if (num >= 999) { text = AppMan.Manifest.State.DownloadDestination; } return text; } private string MakeValidPath(string source) { if (source.EndsWith("\\")) { return source; } return source + "\\"; } private void CopyDirectory(string source, string destination) { try { DirectoryInfo directoryInfo = new DirectoryInfo(source); if (!Directory.Exists(destination)) { Directory.CreateDirectory(destination); DirectoryInfo directoryInfo2 = new DirectoryInfo(destination); directoryInfo2.Attributes = directoryInfo.Attributes; } FileInfo[] files = directoryInfo.GetFiles(); FileInfo[] array = files; foreach (FileInfo fileInfo in array) { if (!File.Exists(Path.Combine(destination, fileInfo.Name))) { fileInfo.CopyTo(Path.Combine(destination, fileInfo.Name), overwrite: true); } } DirectoryInfo[] directories = directoryInfo.GetDirectories(); DirectoryInfo[] array2 = directories; foreach (DirectoryInfo directoryInfo3 in array2) { CopyDirectory(Path.Combine(source, directoryInfo3.Name), Path.Combine(destination, directoryInfo3.Name)); } } catch (Exception ex) { throw ex; } } private void HardDirectoryDelete(string source) { string directoryName = Path.GetDirectoryName(source); string text = Path.GetDirectoryName(directoryName) + "\\" + Config.SetupVersion + "\\"; StreamWriter streamWriter = new StreamWriter("TestFile.txt", append: true); streamWriter.WriteLine("ParentPath: " + text + "\r\n"); streamWriter.Close(); if (!(source == text)) { try { if (Directory.Exists(source)) { DirectoryInfo directoryInfo = new DirectoryInfo(source); directoryInfo.Attributes = FileAttributes.Normal; FileInfo[] files = directoryInfo.GetFiles(); FileInfo[] array = files; foreach (FileInfo fileInfo in array) { fileInfo.Attributes = FileAttributes.Normal; } DirectoryInfo[] directories = directoryInfo.GetDirectories(); DirectoryInfo[] array2 = directories; foreach (DirectoryInfo directoryInfo2 in array2) { HardDirectoryDelete(Path.Combine(source, directoryInfo2.Name)); } directoryInfo.Delete(recursive: true); } } catch (Exception ex) { throw ex; } } } private string GetFileVersion(string filePath) { AssemblyName assemblyName = AssemblyName.GetAssemblyName(filePath); return assemblyName.Version.ToString(); } private string GetParentFolder(string filePath) { DirectoryInfo directoryInfo = new DirectoryInfo(filePath.Trim('\\')); return directoryInfo.Parent.FullName + "\\"; } private bool isManifestFile(string name) { string fileName = Path.GetFileName(AppMan.Manifest.FilePath); if (fileName.ToLower(new CultureInfo("en-US")) == name.ToLower(new CultureInfo("en-US"))) { return true; } return false; } } }