Feature Suggestion - Hash XML Arguement

Mar 20, 2015 at 4:19 PM
Edited Mar 20, 2015 at 4:20 PM
This is an amazing library and is very simple to implement!

I would like to suggest a new argument where the hash of the file to download could be manually embedded into the xml like so.

<hash>2EF7BA3F51791B1BDC996C0C02C3C66FA15FE6CF5E8A9042554F4A196CB96DD9</hash>

In this example my hash is an SHA-256 format.
So the purpose of doing this would be that the file could be verified before actually installing using code something like this. (see link) http://pastebin.com/uPWeTqbw

This would allow for better manual handling and security of the download/update process.
Especially in the way of generic installers that might just be named "Setup.exe" and could even be inadvertently overridden.

For my usage of the library I have a small program that does the following.
Check for update, if there is an update then check if the file exists locally.
If the file exists locally check its hash, if the hash matches install the update silently.
if the file does not exist or the hash does not match, download and verify the file.

Finally I would also suggest that the UpdateInfoEventArgs provide that exact path that the download would be written to, for easier manual download handling.
Coordinator
Mar 21, 2015 at 5:15 AM
Nice Idea. I am thinking of adding functionality that allows developers to add there own custom tags in XML and read that data without changing the source code in library. Developers can use those additional data in in CheckForUpdateEvent to do what they want with it.
Jun 15, 2015 at 2:54 PM
Hi, i just implemented this feature in my autoupdater version. But, how can i redo the download automatically if hash is not correct?
Jun 15, 2015 at 5:15 PM
If you look at my sample here http://pastebin.com/uPWeTqbw
Under the "// Hash mismatch logic here" line, you can manually invoke the download.
After that you can manually verify the hash again, but keep in mind you should ensure that
the comparison is not case sensitive.

I would create a couple of methods.

bool DownloadUpdate()
{
// logic to download update.
// http://www.csharp-examples.net/download-files/
return success;
}

string GetFileHash(filePath)
{
string fileHash = string.Empty;
 if (File.Exists(filePath))
{
        using (FileStream s = new FileStream(filePath, FileMode.Open))
        {
                SHA256 mySHA256 = SHA256Managed.Create();
                s.Position = 0;
                byte[] hashValue = mySHA256.ComputeHash(s);
                fileHash = BitConverter.ToString(hashValue).ToUpper().Replace("-", String.Empty);
        }
}

return fileHash;
}
Jun 15, 2015 at 7:56 PM
Edited Jun 15, 2015 at 7:58 PM
That's the code i wrote: (autoupdater.cs)
      private void OnDownloadComplete(object sender, AsyncCompletedEventArgs e)
        {
            if (!e.Cancelled)
            {
                string md5result = GetChecksum(_tempPath); // some logic from mine, for check the hash (md5 mode)
                if (AutoUpdater.MD5 != string.Empty && AutoUpdater.MD5 != md5result) // if logic exists and it is different from expected result
                {
                    if (System.Windows.Forms.DialogResult.Yes == MessageBox.Show(this, "Error in downloaded file" + Environment.NewLine + "Would you try to downlad the file again?", "Retry?", MessageBoxButtons.YesNo))  // Ask for retry
                    {
                        var uri = new Uri(_downloadURL);
                        _webClient.DownloadFileAsync(uri, _tempPath); //  <----- HERE IS THE PROBLEM. It's expected to redownload the file, 
                    }                                                                                //           but it closes, and launch bad downloaded file.
                    else
                    {
                        Environment.Exit(0);
                    }
                }
                else // Its's OK. Continue with original code
                {
                    var processStartInfo = new ProcessStartInfo { FileName = _tempPath, UseShellExecute = true };
                    Process.Start(processStartInfo);
                    if (AutoUpdater.IsWinFormsApplication)
                    {
                        Application.Exit();
                    }
                    else
                    {
                        Environment.Exit(0);
                    }
                }
            }
        }
Jun 15, 2015 at 8:15 PM
Your problem is that you are using DownloadFileAsync() which is non blocking.
Try using DownloadFile() instead. https://msdn.microsoft.com/en-us/library/ms144194%28v=vs.110%29.aspx
That will allow the download to complete before it continues.

If this results in your interface being unresponsive,
then consider putting the entire event handler on a new thread or using ThreadPool.QueueUserWorkItem.
http://www.dotnetperls.com/threadpool
Jun 22, 2015 at 9:09 AM
I know, but DownloadFileAsync() is the method used in original code
Jun 30, 2015 at 3:54 PM
This is how I resolved the problem.

Now if hash don't equal to original, show a box with a retry question. If answer Yes, then try to download it again.
It happens until download is ok or user answer No.

The GetChecksum function is a routine that return the MD5 hash of a file. You can use the hash logic you need.
        private void OnDownloadComplete(object sender, AsyncCompletedEventArgs e)
        {
            if (!e.Cancelled)
            {
                // Calculate MD5 hash from downloaded file.
                string md5result = GetChecksum(_tempPath);  
                // If exists, check hash  (property added by me to AutoUpdater and saved in the update.xml file loaded)
                if (AutoUpdater.MD5 != string.Empty && AutoUpdater.MD5 != md5result)  
                {
                    if (System.Windows.Forms.DialogResult.Yes == MessageBox.Show(this, "Download error", "Retry?", MessageBoxButtons.YesNo))
                    {
                        // Cancel and Dispose the WebClient
                        _webClient.CancelAsync();
                        _webClient.DownloadProgressChanged -= OnDownloadProgressChanged;
                        _webClient.DownloadFileCompleted -= OnDownloadComplete;
                        _webClient.Dispose();

                       // And create it again, and go download ...
                        var uri = new Uri(_downloadURL);
                        _webClient = new WebClient();
                        _webClient.DownloadProgressChanged += OnDownloadProgressChanged;
                        _webClient.DownloadFileCompleted += OnDownloadComplete;
                        _webClient.DownloadFileAsync(uri, _tempPath);
                    }
                    else
                    {
                        this.Close();
                    }
                }
                else
                {
                    var processStartInfo = new ProcessStartInfo { FileName = _tempPath, UseShellExecute = true };
                    Process.Start(processStartInfo);
                    if (AutoUpdater.IsWinFormsApplication)
                    {
                        Application.Exit();
                    }
                    else
                    {
                        Environment.Exit(0);
                    }
                }
            }
        }