aboutsummaryrefslogtreecommitdiffstats
path: root/Software
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2020-12-15 16:52:30 +0200
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2020-12-15 16:52:30 +0200
commit66be0fdd579050375421ad54dc25cf35bac0845c (patch)
tree429ebbf390862a2cc1dcad40aecb96a1399c7ab0 /Software
parent97513f619e9778e8409831db04b29b0dcf53f1b9 (diff)
downloadTango-66be0fdd579050375421ad54dc25cf35bac0845c.tar.gz
Tango-66be0fdd579050375421ad54dc25cf35bac0845c.zip
Implemented several fallbacks to PPC update failure !
Diffstat (limited to 'Software')
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs10
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/IMachineUpdateManager.cs6
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs116
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs6
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs30
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModels/MachineUpdateViewVM.cs34
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.Updater/IdentityUtils.cs118
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.Updater/MainWindow.xaml.cs65
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.Updater/Tango.PPC.Updater.csproj1
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>