diff options
| author | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-12-15 16:52:30 +0200 |
|---|---|---|
| committer | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-12-15 16:52:30 +0200 |
| commit | 66be0fdd579050375421ad54dc25cf35bac0845c (patch) | |
| tree | 429ebbf390862a2cc1dcad40aecb96a1399c7ab0 | |
| parent | 97513f619e9778e8409831db04b29b0dcf53f1b9 (diff) | |
| download | Tango-66be0fdd579050375421ad54dc25cf35bac0845c.tar.gz Tango-66be0fdd579050375421ad54dc25cf35bac0845c.zip | |
Implemented several fallbacks to PPC update failure !
9 files changed, 343 insertions, 43 deletions
diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs index fd91bfd50..a9d4e7c3a 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs @@ -46,6 +46,11 @@ namespace Tango.PPC.Common.Application event EventHandler SystemRestartRequired; /// <summary> + /// Occurs when the updater utility has failed to perform the last update. + /// </summary> + event EventHandler UpdaterFailed; + + /// <summary> /// Occurs when the application has encountered an error when initializing. /// </summary> event EventHandler<Exception> ApplicationInitializationError; @@ -66,6 +71,11 @@ namespace Tango.PPC.Common.Application bool IsAfterUpdate { get; } /// <summary> + /// Gets a value indicating whether the updater utility has failed to perform the last update. + /// </summary> + bool IsUpdateFailed { get; } + + /// <summary> /// Shutdown the application. /// </summary> void ShutDown(); diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/IMachineUpdateManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/IMachineUpdateManager.cs index 77646da40..7c835165f 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/IMachineUpdateManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/IMachineUpdateManager.cs @@ -95,5 +95,11 @@ namespace Tango.PPC.Common.MachineUpdate /// </summary> /// <returns></returns> Task<PackageRunnerResult> RunPostUpdatePackages(); + + /// <summary> + /// Restores the last database backup. + /// </summary> + /// <returns></returns> + Task RestoreLastDatabaseBackup(); } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs index 85fe6b0ad..c115f4f5b 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs @@ -129,6 +129,11 @@ namespace Tango.PPC.Common.MachineUpdate private void _app_manager_ApplicationReady(object sender, EventArgs e) { _checkForUpdateTimer.Start(); + + if (!_app_manager.IsUpdateFailed) + { + ClearLastDatabaseBackup(); + } } private void _packageRunner_PackageProgress(object sender, PackageProgressEventArgs e) @@ -327,14 +332,14 @@ namespace Tango.PPC.Common.MachineUpdate } } - try - { - Directory.Delete(backupsFolder, true); - } - catch (Exception ex) - { - LogManager.Log(ex, $"Error deleting backups folder '{backupsFolder}'."); - } + //try + //{ + // Directory.Delete(backupsFolder, true); + //} + //catch (Exception ex) + //{ + // LogManager.Log(ex, $"Error deleting backups folder '{backupsFolder}'."); + //} if (!result.RequiresBinariesUpdate) { @@ -604,6 +609,26 @@ namespace Tango.PPC.Common.MachineUpdate return logsString; } + private void ClearLastDatabaseBackup() + { + Task.Factory.StartNew(() => + { + try + { + var lastBackupFile = SettingsManager.Default.GetOrCreate<PPCSettings>().LastDatabaseBackupFile; + + if (File.Exists(lastBackupFile)) + { + File.Delete(lastBackupFile); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error removing last database backup file."); + } + }); + } + #endregion #region Public Methods @@ -733,6 +758,27 @@ namespace Tango.PPC.Common.MachineUpdate //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("Initializing database manager..."); + DbManager db = DbManager.FromDataSource(localDataSource); + + //Create Database Backup + UpdateProgress("Updating Database", "Creating database backup..."); + try + { + Directory.CreateDirectory(backupsFolder); + dbBackupFile = $"{backupsFolder}\\{Path.GetRandomFileName()}.bak"; + _settings.LastDatabaseBackupFile = dbBackupFile; + _settings.Save(); + LogManager.Log($"Creating database backup to '{dbBackupFile}'..."); + await Task.Factory.StartNew(() => db.Backup(localDataSource.Catalog, dbBackupFile)); + performDatabaseRollback = true; + LogManager.Log("Database backup created successfully."); + } + catch (Exception ex) + { + throw LogManager.Log(ex, "Update manager error while trying to create a database backup."); + } + //Run pre-update packages. try { @@ -763,8 +809,6 @@ namespace Tango.PPC.Common.MachineUpdate 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)) @@ -772,24 +816,6 @@ namespace Tango.PPC.Common.MachineUpdate throw new InvalidProgramException("Database tango does not exists."); } - UpdateProgress("Updating Database", "Creating database backup..."); - - //Create Database Backup - try - { - Directory.CreateDirectory(backupsFolder); - dbBackupFile = $"{backupsFolder}\\{Path.GetRandomFileName()}.bak"; - LogManager.Log($"Creating database backup to '{dbBackupFile}'..."); - await Task.Factory.StartNew(() => db.Backup(localDataSource.Catalog, dbBackupFile)); - performDatabaseRollback = true; - LogManager.Log("Database backup created successfully."); - } - catch (Exception ex) - { - throw LogManager.Log(ex, "Update manager error while trying to create a database backup."); - } - - LogManager.Log("Disposing database manager."); db.Dispose(); @@ -1621,6 +1647,40 @@ namespace Tango.PPC.Common.MachineUpdate } } + public Task RestoreLastDatabaseBackup() + { + return Task.Factory.StartNew(() => + { + LogManager.Log("Rolling back database changes..."); + UpdateProgress("Rollback", "Rolling back database changes..."); + + var localDataSource = SettingsManager.Default.GetOrCreate<CoreSettings>().DataSource; + var lastBackupFile = SettingsManager.Default.GetOrCreate<PPCSettings>().LastDatabaseBackupFile; + + using (DbManager db = DbManager.FromDataSource(localDataSource)) + { + try + { + db.Restore(localDataSource.Catalog, lastBackupFile); + LogManager.Log("Database restored successfully."); + } + catch (Exception ex) + { + LogManager.Log(ex, "Could not rollback the database after a failed updater."); + throw ex; + } + finally + { + try + { + File.Delete(lastBackupFile); + } + catch { } + } + } + }); + } + #endregion #region Protected Methods diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs index 29ad1399c..aacbe8901 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs @@ -282,6 +282,12 @@ namespace Tango.PPC.Common public bool BypassInternetConnectivityCheck { get; set; } /// <summary> + /// Gets or sets the last database backup file that was generated before application update. + /// If updater utility was successful, this file should be deleted. Otherwise should be restored. + /// </summary> + public String LastDatabaseBackupFile { get; set; } + + /// <summary> /// Gets the machine service address. /// </summary> /// <returns></returns> diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs index af3473a98..83790a56f 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs @@ -64,6 +64,11 @@ namespace Tango.PPC.UI.PPCApplication public event EventHandler SystemRestartRequired; /// <summary> + /// Occurs when the updater utility has failed to perform the last update. + /// </summary> + public event EventHandler UpdaterFailed; + + /// <summary> /// Occurs when the application has started. /// </summary> public event EventHandler ApplicationStarted; @@ -140,6 +145,11 @@ namespace Tango.PPC.UI.PPCApplication /// </summary> public bool IsAfterUpdate { get; private set; } + /// <summary> + /// Gets a value indicating whether the updater utility has failed to perform the last update. + /// </summary> + public bool IsUpdateFailed { get; private set; } + private bool _isScreenLocked; /// <summary> /// Gets or sets a value indicating whether the screen is currently locked. @@ -264,6 +274,18 @@ namespace Tango.PPC.UI.PPCApplication } } + if (App.StartupArgs.Contains("-update_failed")) + { + LogManager.Log("Application started with '-update_failed' startup arguments. The updater utility has failed."); + + IsUpdateFailed = true; + + settings.ApplicationState = ApplicationStates.Ready; + settings.Save(); + UpdaterFailed?.Invoke(this, new EventArgs()); + return; + } + if (settings.ApplicationState == ApplicationStates.Ready) { LogManager.Log("Initializing ObservablesStaticCollections..."); @@ -616,7 +638,13 @@ namespace Tango.PPC.UI.PPCApplication } LogManager.Log($"Executing '{updaterPath}' with arguments '{arguments}'..."); - Process.Start(updaterPath, arguments); + + Process p = new Process(); + p.StartInfo.FileName = updaterPath; + p.StartInfo.Arguments = arguments; + p.StartInfo.LoadUserProfile = true; + p.StartInfo.UseShellExecute = true; + p.Start(); LogManager.Log("Terminating application..."); Environment.Exit(0); diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModels/MachineUpdateViewVM.cs b/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModels/MachineUpdateViewVM.cs index 613c70809..5fe153ee9 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModels/MachineUpdateViewVM.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModels/MachineUpdateViewVM.cs @@ -17,8 +17,11 @@ using Tango.Explorer; using Tango.Integration.ExternalBridge; using Tango.PMR.FirmwareUpgrade; using Tango.PPC.Common; +using Tango.PPC.Common.Application; using Tango.PPC.Common.ExternalBridge; using Tango.PPC.Common.MachineUpdate; +using Tango.PPC.Common.Navigation; +using Tango.PPC.Common.Notifications; using Tango.PPC.Common.Publish; using Tango.PPC.Common.Web; using Tango.PPC.Shared.RemoteUpgrade; @@ -121,7 +124,7 @@ namespace Tango.PPC.UI.ViewModels #region Constructors - public MachineUpdateViewVM(IMachineUpdateManager machineUpdateManager, IPPCExternalBridgeService externalBridge) + public MachineUpdateViewVM(IMachineUpdateManager machineUpdateManager, IPPCExternalBridgeService externalBridge, IPPCApplicationManager applicationManager, INavigationManager navigationManager, INotificationProvider notificationProvider) { MachineUpdateManager = machineUpdateManager; externalBridge.RegisterRequestHandler(this); @@ -142,6 +145,11 @@ namespace Tango.PPC.UI.ViewModels }); machineUpdateManager.UpdateAvailable += MachineUpdateManager_UpdateAvailable; + + NavigationManager = navigationManager; + NotificationProvider = notificationProvider; + ApplicationManager = applicationManager; + ApplicationManager.UpdaterFailed += ApplicationManager_UpdaterFailed; } #endregion @@ -565,6 +573,30 @@ namespace Tango.PPC.UI.ViewModels #endregion + #region Updater Failed + + private void ApplicationManager_UpdaterFailed(object sender, EventArgs e) + { + InvokeUI(async () => + { + try + { + await NavigationManager.NavigateTo(NavigationView.MachineUpdateView); + await NavigateTo(MachineUpdateView.UpdateProgressView); + await MachineUpdateManager.RestoreLastDatabaseBackup(); + await Task.Delay(5000); + ApplicationManager.Restart(); + } + catch (Exception ex) + { + await NotificationProvider.ShowError($"Could not restore the application to its previous state.\n{ex.FlattenMessage()}"); + ApplicationManager.Restart(); + } + }); + } + + #endregion + #region External Bridge Handler [ExternalBridgeRequestHandlerMethod(typeof(StartRemoteApplicationUpgradeRequest), RequestHandlerLoggingMode.LogRequestName)] diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Updater/IdentityUtils.cs b/Software/Visual_Studio/PPC/Tango.PPC.Updater/IdentityUtils.cs new file mode 100644 index 000000000..3a43be6f9 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Updater/IdentityUtils.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Updater +{ + public static class IdentityUtils + { + [DllImport("advapi32.dll", SetLastError = true)] + static extern bool GetTokenInformation(IntPtr tokenHandle, TokenInformationClass tokenInformationClass, IntPtr tokenInformation, int tokenInformationLength, out int returnLength); + + /// <summary> + /// Passed to <see cref="GetTokenInformation"/> to specify what + /// information about the token to return. + /// </summary> + enum TokenInformationClass + { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin, + TokenElevationType, + TokenLinkedToken, + TokenElevation, + TokenHasRestrictions, + TokenAccessInformation, + TokenVirtualizationAllowed, + TokenVirtualizationEnabled, + TokenIntegrityLevel, + TokenUiAccess, + TokenMandatoryPolicy, + TokenLogonSid, + MaxTokenInfoClass + } + + /// <summary> + /// The elevation type for a user token. + /// </summary> + enum TokenElevationType + { + TokenElevationTypeDefault = 1, + TokenElevationTypeFull, + TokenElevationTypeLimited + } + + public static bool IsElevated() + { + var identity = WindowsIdentity.GetCurrent(); + if (identity == null) throw new InvalidOperationException("Couldn't get the current user identity"); + var principal = new WindowsPrincipal(identity); + + // Check if this user has the Administrator role. If they do, return immediately. + // If UAC is on, and the process is not elevated, then this will actually return false. + //if (principal.IsInRole(WindowsBuiltInRole.Administrator)) return true; + + //// If we're not running in Vista onwards, we don't have to worry about checking for UAC. + //if (Environment.OSVersion.Platform != PlatformID.Win32NT || Environment.OSVersion.Version.Major < 6) + //{ + // // Operating system does not support UAC; skipping elevation check. + // return false; + //} + + int tokenInfLength = Marshal.SizeOf(typeof(int)); + IntPtr tokenInformation = Marshal.AllocHGlobal(tokenInfLength); + + try + { + var token = identity.Token; + var result = GetTokenInformation(token, TokenInformationClass.TokenElevationType, tokenInformation, tokenInfLength, out tokenInfLength); + + if (!result) + { + var exception = Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()); + throw new InvalidOperationException("Couldn't get token information", exception); + } + + var elevationType = (TokenElevationType)Marshal.ReadInt32(tokenInformation); + + switch (elevationType) + { + case TokenElevationType.TokenElevationTypeDefault: + // TokenElevationTypeDefault - User is not using a split token, so they cannot elevate. + return false; + case TokenElevationType.TokenElevationTypeFull: + // TokenElevationTypeFull - User has a split token, and the process is running elevated. Assuming they're an administrator. + return true; + case TokenElevationType.TokenElevationTypeLimited: + // TokenElevationTypeLimited - User has a split token, but the process is not running elevated. Assuming they're an administrator. + return false; + default: + // Unknown token elevation type. + return false; + } + } + finally + { + if (tokenInformation != IntPtr.Zero) Marshal.FreeHGlobal(tokenInformation); + } + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Updater/MainWindow.xaml.cs b/Software/Visual_Studio/PPC/Tango.PPC.Updater/MainWindow.xaml.cs index 6615ededf..b821b332b 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Updater/MainWindow.xaml.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Updater/MainWindow.xaml.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -69,6 +70,22 @@ namespace Tango.PPC.Updater { try { + if (!IdentityUtils.IsElevated()) + { + ShowError("The updater utility is not running under elevated permissions and cannot perform.\nThe process will restart."); + var exeName = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; + ProcessStartInfo startInfo = new ProcessStartInfo(exeName); + startInfo.Arguments = String.Join(" ", App.StartupArgs); + startInfo.Verb = "runas"; + Process.Start(startInfo); + Environment.Exit(0); + return; + } + } + catch { } + + try + { Init(); EnsureTangoIsDown(); RemoveOldDLLFiles(); @@ -78,30 +95,42 @@ namespace Tango.PPC.Updater DoEvents(); Thread.Sleep(1000); StartTango(true); + Exit(); } catch (Exception ex) { - ShowError($"Update failed.\n{ex.Message}"); - StartTango(false); + if (ShowErrorRetry($"Update failed.\n{ex.Message}")) + { + Update(); + } + else + { + txtStatus.Text = "Update failed. Restoring previous application state..."; + DoEvents(); + Thread.Sleep(1000); + StartTango(false); + Exit(); + } } - finally + } + + private void Exit() + { + try { - try + foreach (var file in Directory.GetFiles(_sourceFolder, "*.*", SearchOption.AllDirectories)) { - foreach (var file in Directory.GetFiles(_sourceFolder, "*.*", SearchOption.AllDirectories)) + try { - try - { - File.Delete(file); - } - catch { } + File.Delete(file); } + catch { } } - catch { } + } + catch { } - Environment.Exit(0); - } + Environment.Exit(0); } private void Init() @@ -137,6 +166,10 @@ namespace Tango.PPC.Updater { p.StartInfo.Arguments = "-update_ok"; } + else + { + p.StartInfo.Arguments = "-update_failed"; + } p.Start(); } @@ -248,5 +281,11 @@ namespace Tango.PPC.Updater { MessageBox.Show(error, "Tango Update", MessageBoxButton.OK, MessageBoxImage.Error); } + + private bool ShowErrorRetry(String error) + { + var result = MessageBox.Show(error + "\n" + "Press yes to retry.", "Tango Update", MessageBoxButton.YesNo, MessageBoxImage.Error); + return result == MessageBoxResult.Yes; + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Updater/Tango.PPC.Updater.csproj b/Software/Visual_Studio/PPC/Tango.PPC.Updater/Tango.PPC.Updater.csproj index 3f232c4d2..ec40d5887 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Updater/Tango.PPC.Updater.csproj +++ b/Software/Visual_Studio/PPC/Tango.PPC.Updater/Tango.PPC.Updater.csproj @@ -67,6 +67,7 @@ <DependentUpon>App.xaml</DependentUpon> <SubType>Code</SubType> </Compile> + <Compile Include="IdentityUtils.cs" /> <Compile Include="MainWindow.xaml.cs"> <DependentUpon>MainWindow.xaml</DependentUpon> <SubType>Code</SubType> |
