aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs
blob: 69df627fb395a34704381a49ad1f72c13793eba9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Tango.Logging
{
    /// <summary>
    /// Contains <see cref="String"/> extension methods.
    /// </summary>
    public static class StringExtensions
    {
        /// <summary>
        /// Converts the object to string. If the object is null, will return null.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <returns></returns>
        public static String ToStringSafe(this object obj)
        {
            return obj != null ? obj.ToString() : String.Empty;
        }
    }
}
82'>282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
using FluentFTP;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tango.Core;
using Tango.Core.DB;
using Tango.Core.Helpers;
using Tango.Core.IO;
using Tango.Integration.Operation;
using Tango.PMR.Synchronization;
using Tango.PPC.Common.Application;
using Tango.PPC.Common.Connection;
using Tango.Settings;
using Tango.SharedUI.Helpers;
using Tango.SQLExaminer;
using Tango.Transport.Web;

namespace Tango.PPC.Common.MachineUpdate
{
    public class MachineUpdateManager : ExtendedObject, IMachineUpdateManager
    {
        private IPPCApplicationManager _app_manager;
        private IMachineProvider _machineProvider;

        #region Events

        /// <summary>
        /// Occurs when there is a text log message available.
        /// </summary>
        public event EventHandler<string> ProgressLog;

        /// <summary>
        /// Occurs when the update has made some progress.
        /// </summary>
        public event EventHandler<MachineUpdateProgress> Progress;

        #endregion

        #region Properties

        private MachineUpdateProgress _status;
        public MachineUpdateProgress Status
        {
            get { return _status; }
            private set { _status = value; RaisePropertyChangedAuto(); }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MachineUpdateManager"/> class.
        /// </summary>
        /// <param name="applicationManager">The application manager.</param>
        public MachineUpdateManager(IPPCApplicationManager applicationManager, IMachineProvider machineProvider)
        {
            _machineProvider = machineProvider;
            _app_manager = applicationManager;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Performs a machine setup using the specified serial number and machine service address.
        /// </summary>
        /// <param name="serialNumber">The serial number.</param>
        /// <param name="machineServiceAddress">The machine service address.</param>
        /// <returns></returns>
        public async Task<MachineUpdateResult> Update(string serialNumber, string machineServiceAddress)
        {
            TaskCompletionSource<MachineUpdateResult> result = new TaskCompletionSource<MachineUpdateResult>();

            try
            {
                LogManager.Log($"Starting machine update for serial number {serialNumber}...");

                //Connecting to machine...
                LogManager.Log("Verifying machine connection and state...");

                UpdateProgress("Verifying machine state", "Initializing...");

                await Task.Delay(1000);

                IMachineOperator op = _machineProvider.MachineOperator;
                if (op.State != Transport.TransportComponentState.Connected)
                {
                    throw new InvalidOperationException("Could not perform an update while the machine is not connected.");
                }
                if (op.Status != MachineStatuses.ReadyToDye)
                {
                    throw new InvalidOperationException($"Could not perform an update while the machine is in a {op.Status} status.");
                }

                //Connect to machine service and get matching packages for this machine.
                UpdateProgress("Downloading software package", "Connecting to machine service...");

                LogManager.Log($"Connecting to machine service on {machineServiceAddress}...");

                DownloadUpdateRequest request = new DownloadUpdateRequest();
                request.SerialNumber = serialNumber;

                DownloadUpdateResponse update_response = null;

                using (var http = new WebTransportClient())
                {
                    update_response = await http.PostJson<DownloadUpdateRequest, DownloadUpdateResponse>(machineServiceAddress + "/api/PPC/MachineUpdate", request);
                }

                LogManager.Log($"Machine update response received: {Environment.NewLine}{update_response.ToJsonString()}");

                //Create temporary folders for packages.
                var _newPackageTempFolder = TemporaryManager.CreateFolder();
                _newPackageTempFolder.Persist = true;

                LogManager.Log($"Temporary package folder created: {_newPackageTempFolder}.");

                //Download software package.
                var tempFile = TemporaryManager.CreateFile(".zip");

                LogManager.Log($"Temporary package zip file created: {tempFile}.");

                LogManager.Log("Downloading software package...");

                long fileSize = 0;
                UpdateProgress("Downloading software package", "Downloading...", false);

                using (FileStreamWrapper fs = new FileStreamWrapper(tempFile.Path, FileMode.Create, (current) =>
                {
                    UpdateProgress("Downloading software package", "Downloading...", false, current, fileSize);
                }))
                {
                    LogManager.Log($"Connecting to storage blob with address {update_response.BlobAddress}");
                    CloudBlockBlob blob = new CloudBlockBlob(new Uri(update_response.BlobAddress));
                    LogManager.Log("Fetching blob attributes...");
                    blob.FetchAttributes();
                    fileSize = blob.Properties.Length;
                    LogManager.Log("Download size: " + fileSize + " bytes.");
                    LogManager.Log("Starting blob download...");
                    blob.DownloadToStream(fs);
                }

                UpdateProgress("Downloading software package", "Extracting package...");

                LogManager.Log("Extracting downloaded zip file...");
                //Extract software package.
                ZipFile.ExtractToDirectory(tempFile, _newPackageTempFolder);


                LogManager.Log("Copying latest updater utility to application path...");
                //Copy new updater utility to app path.
                File.Copy(Path.Combine(_newPackageTempFolder, "Tango.PPC.Updater.exe"), Path.Combine(PathHelper.GetStartupPath(), "Tango.PPC.Updater.exe"), true);


                //Synchronize database
                UpdateProgress("Updating Database", "Initializing...");

                var localDataSource = SettingsManager.Default.GetOrCreate<CoreSettings>().DataSource;

                LogManager.Log($"Synchronizing database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'...");

                UpdateProgress("Updating Database", "Connecting to local database...");
                LogManager.Log("Initializing database manager...");
                DbManager db = DbManager.FromDataSource(localDataSource);

                LogManager.Log("Checking Tango database exists on the local machine...");
                if (!db.Exists(localDataSource.Catalog))
                {
                    throw new InvalidProgramException("Database tango does not exists.");
                }

                LogManager.Log("Disposing database manager.");
                db.Dispose();

                LogManager.Log($"Initializing {nameof(ExaminerSequenceConfigurationRunner)}...");

                UpdateProgress("Updating Database", "Initializing provisioning sequence...");

                ExaminerSequenceConfigurationRunner runner = new ExaminerSequenceConfigurationRunner(
                    Path.Combine(_newPackageTempFolder, "Update Scripts", "config.xml"),
                    Path.Combine(_newPackageTempFolder, "Update Scripts"),
                        update_response.DataSource,
                        localDataSource,
                        serialNumber);

                runner.Log += (x, msg) =>
                {
                    LogManager.Log(msg);
                    ProgressLog?.Invoke(this, msg);
                };

                runner.ScriptExecuting += (x, item) =>
                {
                    LogManager.Log($"Executing script {item.ToString()}...");
                    UpdateProgress("Updating Database", item.Name + "...");
                };

                LogManager.Log("Starting synchronization process...");

                try
                {
                    await runner.Run();
                    LogManager.Log("Synchronization completed successfully!");
                    UpdateProgress("Updating Database", "Database synchronization completed successfully.");
                }
                catch (Exception ex)
                {
                    throw LogManager.Log(ex, "Setup manager error while trying to synchronize database.");
                }

                //Updating firmware
                UpdateProgress("Updating Firmware", "Connecting to firmware device...");
                LogManager.Log("");
                LogManager.Log("-------------------------------------------------------------------------");
                LogManager.Log("Updating Firmware...");

                UpdateProgress("Updating Firmware", "Loading firmware package...");
                var tfpPath = Path.Combine(_newPackageTempFolder, "firmware_package.tfp");
                var stream = new FileStream(tfpPath, FileMode.Open);
                var handler = await op.UpgradeFirmware(stream);
                handler.Failed += (_, ex) =>
                {
                    stream.Dispose();
                    result.SetException(ex);
                };
                handler.Completed += (_, __) =>
                {
                    UpdateProgress("Updating Firmware", "Firmware update completed successfully.");
                    stream.Dispose();
                    result.SetResult(new MachineUpdateResult()
                    {
                        UpdatePackagePath = _newPackageTempFolder,
                    });
                };
                handler.Canceled += (_, __) =>
                {
                    stream.Dispose();
                    result.SetException(new Exception("The operation has been canceled."));
                };
                handler.Progress += (_, e) =>
                {
                    UpdateProgress("Updating Firmware", e.Message, false, e.Current, e.Total);
                };
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "An error occurred in machine update.");
                result.SetException(ex);
            }

            return await result.Task;
        }

        /// <summary>
        /// Checks if any update are available for the specified machine serial number.
        /// </summary>
        /// <param name="serialNumber">The serial number.</param>
        /// <param name="machineServiceAddress">The machine service address.</param>
        /// <returns></returns>
        public Task<CheckForUpdateResponse> CheckForUpdate(string serialNumber, string machineServiceAddress)
        {
            return Task.Factory.StartNew<CheckForUpdateResponse>(() =>
            {
                LogManager.Log($"Connecting to machine service on {machineServiceAddress}...");
                LogManager.Log($"Checking if updates available...");

                CheckForUpdateRequest request = new CheckForUpdateRequest();
                request.SerialNumber = serialNumber;
                request.Version = _app_manager.Version.ToString();

                CheckForUpdateResponse update_response = null;

                using (var http = new WebTransportClient())
                {
                    update_response = http.PostJson<CheckForUpdateRequest, CheckForUpdateResponse>(machineServiceAddress + "/api/PPC/CheckForUpdate", request).Result;
                }

                LogManager.Log($"Check for update response received: {Environment.NewLine}{update_response.ToJsonString()}");

                return update_response;
            });
        }

        /// <summary>
        /// Updates all the "overwrite-able" database tables.
        /// </summary>
        /// <param name="serialNumber">The serial number.</param>
        /// <param name="machineServiceAddress">The machine service address.</param>
        /// <returns></returns>
        public Task UpdateDB(DbCompareResult dbCompareResult)
        {
            return Task.Factory.StartNew(() =>
            {
                LogManager.Log("Starting database update...");

                LogManager.Log("Looking for OverrideData script on application path...");

                String config_file = Path.Combine(PathHelper.GetStartupPath(), "Update Scripts", "OverrideData.xml");

                if (!File.Exists(config_file))
                {
                    config_file = Path.Combine(PathHelper.GetStartupPath(), "Provision Scripts", "OverrideData.xml");
                }

                if (!File.Exists(config_file))
                {
                    throw LogManager.Log(new FileNotFoundException("Could not locate OverrideData.xml file on application folder."));
                }

                UpdateDBResponse update_response = dbCompareResult.UpdateDBResponse;

                var localDataSource = SettingsManager.Default.GetOrCreate<CoreSettings>().DataSource;

                LogManager.Log($"Overriding database static tables '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'...");

                ExaminerConfigurationBuilder builder = new ExaminerConfigurationBuilder(config_file);
                builder.SetSource(update_response.DataSource);
                builder.SetTarget(localDataSource);
                builder.Synchronize();

                var config = builder.Build();

                ExaminerProcess process = new ExaminerProcess(config, ExaminerProcessType.Data);
                process.Progress += (x, msg) =>
                {
                    LogManager.Log(msg);
                };

                LogManager.Log("Starting synchronization process...");

                try
                {
                    var result = process.Execute().Result;

                    if (result.ExitCode != ExaminerProcessExitCode.Success)
                    {
                        throw LogManager.Log(new InvalidDataException(String.Format("OverrideData script has terminated with exit code '{0}'.", result.ExitCode)));
                    }

                    LogManager.Log("Synchronization completed successfully!");
                }
                catch (Exception ex)
                {
                    throw LogManager.Log(ex, "Setup manager error while trying to update the database.");
                }
            });
        }

        /// <summary>
        /// Checks whether it is necessary to updates all the "overwrite-able" database tables.
        /// </summary>
        /// <param name="serialNumber">The serial number.</param>
        /// <param name="machineServiceAddress">The machine service address.</param>
        /// <returns></returns>
        public Task<DbCompareResult> UpdateDBCheck(string serialNumber, string machineServiceAddress)
        {
            return Task.Factory.StartNew<DbCompareResult>(() =>
            {
                LogManager.Log($"Checking if database update is required for serial number {serialNumber}...");

                LogManager.Log("Looking for OverrideData script on application path...");

                String config_file = Path.Combine(PathHelper.GetStartupPath(), "Update Scripts", "OverrideData.xml");

                if (!File.Exists(config_file))
                {
                    config_file = Path.Combine(PathHelper.GetStartupPath(), "Provision Scripts", "OverrideData.xml");
                }

                if (!File.Exists(config_file))
                {
                    throw LogManager.Log(new FileNotFoundException("Could not locate OverrideData.xml file on application folder."));
                }

                LogManager.Log($"Connecting to machine service on {machineServiceAddress}...");

                UpdateDBRequest request = new UpdateDBRequest();
                request.SerialNumber = serialNumber;

                UpdateDBResponse update_response = null;

                using (var http = new WebTransportClient())
                {
                    update_response = http.PostJson<UpdateDBRequest, UpdateDBResponse>(machineServiceAddress + "/api/PPC/UpdateDB", request).Result;
                }

                LogManager.Log($"Update DB response received: {Environment.NewLine}{update_response.ToJsonString()}");

                var localDataSource = SettingsManager.Default.GetOrCreate<CoreSettings>().DataSource;

                LogManager.Log($"Comparing database static tables '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'...");

                var report_file = TemporaryManager.CreateFile(".xml");

                ExaminerConfigurationBuilder builder = new ExaminerConfigurationBuilder(config_file);
                builder.SetSource(update_response.DataSource);
                builder.SetTarget(localDataSource);
                builder.SetReportFile(report_file);

                var config = builder.Build();

                ExaminerProcess process = new ExaminerProcess(config, ExaminerProcessType.Data);
                process.Progress += (x, msg) =>
                {
                    LogManager.Log(msg);
                };

                LogManager.Log("Starting comparison process...");
                LogManager.Log("Generating report on " + report_file);

                try
                {
                    var result = process.Execute().Result;

                    if (result.ExitCode != ExaminerProcessExitCode.Success)
                    {
                        throw LogManager.Log(new InvalidDataException(String.Format("OverrideData script has terminated with exit code '{0}'.", result.ExitCode)));
                    }

                    LogManager.Log("Comparison completed successfully!");
                    LogManager.Log("Loading report file...");

                    ExaminerDataReport report = ExaminerDataReport.FromFile(report_file);
                    report_file.Delete();

                    LogManager.Log("Comparison summary: \n" + report.Totals.ToJsonString());

                    return new DbCompareResult()
                    {
                        RequiresUpdate = report.HasDifferences,
                        UpdateDBResponse = update_response,
                    };
                }
                catch (Exception ex)
                {
                    throw LogManager.Log(ex, "Update manager error while trying to compare the database.");
                }
            });
        }

        /// <summary>
        /// Performs a machine update using the specified software update package path.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public Task<MachineUpdateResult> Update(string fileName)
        {
            return Task.Factory.StartNew<MachineUpdateResult>(() =>
            {
                LogManager.Log($"Starting machine update from update package '{fileName}'...");

                //Create temporary folders for packages.
                var _newPackageTempFolder = TemporaryManager.CreateFolder();
                _newPackageTempFolder.Persist = true;

                LogManager.Log("Extracting downloaded zip file...");
                //Extract software package.
                ZipFile.ExtractToDirectory(fileName, _newPackageTempFolder);

                LogManager.Log("Copying latest updater utility to application path...");
                //Copy new updater utility to app path.
                File.Copy(Path.Combine(_newPackageTempFolder, "Tango.PPC.Updater.exe"), Path.Combine(PathHelper.GetStartupPath(), "Tango.PPC.Updater.exe"), true);

                LogManager.Log("Update operation completed!");

                return new MachineUpdateResult()
                {
                    UpdatePackagePath = _newPackageTempFolder,
                };
            });
        }

        /// <summary>
        /// Gets the update package file information.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <returns></returns>
        public Task<UpdatePackageFile> GetUpdatePackageFileInfo(string filePath)
        {
            return Task.Factory.StartNew<UpdatePackageFile>(() =>
            {
                UpdatePackageFile file = new UpdatePackageFile();
                var tempFolder = TemporaryManager.CreateFolder();

                using (Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile(filePath))
                {
                    var appEntry = zip.Entries.SingleOrDefault(x => x.FileName == "Tango.PPC.UI.exe");
                    appEntry.Extract(tempFolder);
                }

                FileVersionInfo info = FileVersionInfo.GetVersionInfo(Path.Combine(tempFolder, "Tango.PPC.UI.exe"));
                file.Version = Version.Parse(info.ProductVersion);

                tempFolder.Delete();

                return file;
            });
        }

        #endregion

        #region Protected Methods

        protected virtual void UpdateProgress(String name, String message = "", bool isIntermediate = true, double progress = 0, double total = 0)
        {
            InvokeUINow(() =>
            {
                Status = new MachineUpdateProgress()
                {
                    Name = name,
                    Message = message,
                    IsIntermediate = isIntermediate,
                    Progress = progress,
                    Total = total,
                };

                Progress?.Invoke(this, Status);
            });

            UIHelper.DoEvents();
        }

        #endregion
    }
}