浏览代码

Merge branch 'release/1.1.29'

Antony Male 4 年之前
父节点
当前提交
1ec7e4fadd

+ 9 - 1
CHANGELOG.md

@@ -1,10 +1,18 @@
 Changelog
 =========
 
+v1.1.19
+-------
+
+ - Don't crash in rare cases trying to detect if Intel Xe Graphics in use (#626)
+ - Make it clearer that balloon settings are on the Folders tab (#613)
+ - Don't show device connectivity balloons by default
+ - Uninstaller: try to shut down running SyncTrayzor instances (#516)
+
 v1.1.28
 -------
 
- - Work around Intel X2 Graphics driver bug which causes Syncthing's UI to appear blank (#606)
+ - Work around Intel Xe Graphics driver bug which causes Syncthing's UI to appear blank (#606)
  - Fix chocolatey package (#614)
 
 v1.1.27

+ 2 - 7
chocolatey/synctrayzor.nuspec

@@ -8,7 +8,7 @@
     <version>0.0.0</version>
     <authors>Antony Male</authors>
     <owners>Antony Male</owners>
-    <summary>Windows tray utility / filesystem watcher / launcher for syncthing</summary>
+    <summary>Windows tray utility / launcher for Syncthing</summary>
     <description>SyncTrayzor is a little tray utility for [Syncthing](http://syncthing.net/) on Windows.
 It hosts and wraps Syncthing, making it behave more like a native Windows application and less like a command-line utility with a web browser interface.
 
@@ -23,11 +23,6 @@ Features include:
     - One of your folders is out of sync
     - Folders finish syncing
     - Devices connect / disconnect
- - Can watch your folders for changes, so you don't have to poll them frequently:
-    - Syncthing on its own has to poll your folders, in order to see if any files have changed.
-    - SyncTrayzor will watch your folders for changes, and alert Syncthing the second anything changes.
-    - This means you can increase the polling interval in Syncthing, avoiding the resource usage of high-frequency polling, but still have any changes propagated straight away.
-    - Folder watching respects the ignores configured in Syncthing.
  - Has a tool to help you resolve file conflicts
  - Contains translations for many languages 
     </description>
@@ -43,7 +38,7 @@ Features include:
     <iconUrl>https://cdn.statically.io/gh/canton7/SyncTrayzor/develop/SyncTrayzor.png</iconUrl>
     <dependencies>
       <!-- Chocolatey 0.9.9 required in order to access the chocolateyPackageName and chocolateyPackageVersion environment variables -->
-      <dependency id="chocolatey" version="0.9.9" />
+      <dependency id="chocolatey" version="0.9.10" />
       <!-- Make sure that .Net 4.7.2 or better is installed -->
       <dependency id="dotnetfx" version="4.7.2" />
     </dependencies>

+ 75 - 8
chocolatey/tools/chocolateyinstall.ps1

@@ -1,13 +1,80 @@
 $ErrorActionPreference = 'Stop'; # stop on all errors
+
+# Copied from https://github.com/chocolatey-community/chocolatey-coreteampackages/blob/a2371345f182da74589897055e3b3703deb0cce3/extensions/chocolatey-core.extension/extensions/Get-UninstallRegistryKey.ps1
+function Get-UninstallRegistryKeySilent {
+    param(
+        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
+        [ValidateNotNullOrEmpty()]
+        [string] $SoftwareName,
+        [parameter(ValueFromRemainingArguments = $true)]
+        [Object[]] $IgnoredArguments
+    )
+    Write-Debug "Running 'Get-UninstallRegistryKey' for `'$env:ChocolateyPackageName`' with SoftwareName:`'$SoftwareName`'";
+
+    $ErrorActionPreference = 'Stop'
+    $local_key       = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
+    $machine_key     = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
+    $machine_key6432 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
+
+    Write-Verbose "Retrieving all uninstall registry keys"
+    [array]$keys = Get-ChildItem -Path @($machine_key6432, $machine_key, $local_key) -ea 0
+    Write-Debug "Registry uninstall keys on system: $($keys.Count)"
+
+    Write-Debug "Error handling check: `'Get-ItemProperty`' fails if a registry key is encoded incorrectly."
+    [int]$maxAttempts = $keys.Count
+    for ([int]$attempt = 1; $attempt -le $maxAttempts; $attempt++)
+    {
+        $success = $false
+
+        $keyPaths = $keys | Select-Object -ExpandProperty PSPath
+        try {
+            [array]$foundKey = Get-ItemProperty -Path $keyPaths -ea 0 | ? { $_.DisplayName -like $SoftwareName }
+            $success = $true
+        } catch {
+            Write-Debug "Found bad key."
+            foreach ($key in $keys){ try{ Get-ItemProperty $key.PsPath > $null } catch { $badKey = $key.PsPath }}
+            Write-Verbose "Skipping bad key: $badKey"
+            [array]$keys = $keys | ? { $badKey -NotContains $_.PsPath }
+        }
+
+        if ($success) { break; }
+        if ($attempt -eq 10) {
+            Write-Warning "Found more than 10 bad registry keys. Run command again with `'--verbose --debug`' for more info."
+            Write-Debug "Each key searched should correspond to an installed program. It is very unlikely to have more than a few programs with incorrectly encoded keys, if any at all. This may be indicative of one or more corrupted registry branches."
+        }
+    }
+
+    Write-Debug "Found $($foundKey.Count) uninstall registry key(s) with SoftwareName:`'$SoftwareName`'";
+    return $foundKey
+}
+
 $toolsDir              = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
+$file                  = (Join-Path $toolsDir 'SyncTrayzorSetup-x86.exe')
+$file64                = (Join-Path $toolsDir 'SyncTrayzorSetup-x64.exe')
+
+$packageArgs = @{
+    packageName   = 'SyncTrayzor'
+    file          = $file
+    file64        = $file64
+    silentArgs    = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /SkipDotNetInstall'
+    fileType      = 'exe'
+    validExitCodes = @(0)
+}
 
-$packageName= 'SyncTrayzor'
-$file       = (Join-Path $toolsDir 'SyncTrayzorSetup-x86.exe')
-$file64     = (Join-Path $toolsDir 'SyncTrayzorSetup-x64.exe')
-$silentArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /SkipDotNetInstall'
-$fileType   = 'exe'
-$validExitCodes = @(0)
+# Version 1.1.28 had a bug which installed x86 on x64 machines. Forcefully transitioning someone from
+# x86 to x64 is risky, as it potentially loses their config, Syncthing version, etc.
+# Therefore if they've got the x86 version and not the x64 version, upgrade using the x86 version to
+# avoid breaking them.
+if ((Get-OSArchitectureWidth -compare 64) -and ($env:chocolateyForceX86 -ne $true) -and (Get-UninstallRegistryKeySilent -SoftwareName 'SyncTrayzor (x86)*')) {
+    if (Get-UninstallRegistryKeySilent -SoftwareName 'SyncTrayzor (x64)*') {
+        Write-Host -ForegroundColor green "Both the 32-bit and 64-bit versions of SyncTrayzor are installed. Upgrading the 64-bit version."
+    } else {
+        Write-Host -ForegroundColor green "You have the 32-bit version of SyncTrayzor installed, so upgrading this. If you intended to install the 64-bit version, please uninstall the 32-bit version first."
+        # null out the 64 bit file parameter, so only the 32 bit file is available to install
+        $packageArgs['file64'] = $null
+    }
+}
 
-Install-ChocolateyInstallPackage $packageName $fileType $silentArgs $file $file64 -validExitCodes $validExitCodes
+Install-ChocolateyInstallPackage @packageArgs
 
-Remove-Item -Force -ea 0 $file, $file64
+Remove-Item -Force -ea 0 $file, $file64

+ 0 - 34
chocolatey/tools/chocolateyuninstall.ps1

@@ -1,34 +0,0 @@
-$ErrorActionPreference = 'Stop'; # stop on all errors
-
-$packageName = 'SyncTrayzor'
-$softwareName = 'SyncTrayzor*' #part or all of the Display Name as you see it in Programs and Features. It should be enough to be unique
-$installerType = 'exe' 
-$silentArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-'
-$validExitCodes = @(0)
-
-$uninstalled = $false
-$local_key     = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
-$machine_key   = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
-$machine_key6432 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
-
-[array]$key = Get-ItemProperty -Path @($machine_key6432,$machine_key, $local_key) `
-                        -ErrorAction SilentlyContinue `
-         | ? { $_.DisplayName -like "$softwareName" }
-
-if ($key.Count -eq 1) {
-  $key | % { 
-    $file = "$($_.UninstallString)"
-    Uninstall-ChocolateyPackage -PackageName $packageName `
-                                -FileType $installerType `
-                                -SilentArgs "$silentArgs" `
-                                -ValidExitCodes $validExitCodes `
-                                -File "$file"
-  }
-} elseif ($key.Count -eq 0) {
-  Write-Warning "$packageName has already been uninstalled by other means."
-} elseif ($key.Count -gt 1) {
-  Write-Warning "$key.Count matches found!"
-  Write-Warning "To prevent accidental data loss, no programs will be uninstalled."
-  Write-Warning "Please alert package maintainer the following keys were matched:"
-  $key | % {Write-Warning "- $_.DisplayName"}
-}

+ 5 - 0
installer/common.iss

@@ -324,9 +324,14 @@ begin
   end;
 end;
 
+[UninstallRun]
+Filename: "{app}\SyncTrayzor.exe"; Parameters: "--shutdown"; RunOnceId: "ShutdownSyncTrayzor"; Flags: skipifdoesntexist
+
 [UninstallDelete]
 Type: files; Name: "{app}\ProcessRunner.exe.old"
 Type: files; Name: "{app}\InstallCount.txt"
+; Not sure why this gets left behind, but it does. Clean it up...
+Type: filesandordirs; Name: "{app}\locales"
 Type: filesandordirs; Name: "{userappdata}\{#AppDataFolder}"
 Type: filesandordirs; Name: "{localappdata}\{#AppDataFolder}"
 

+ 3 - 2
server/version_check.php

@@ -65,7 +65,7 @@ function get_with_wildcard($src, $value, $default = null)
 }
 
 $versions = [
-   '1.1.28' => [
+   '1.1.29' => [
       'base_url' => 'https://github.com/canton7/SyncTrayzor/releases/download',
       'installed' => [
          'direct_download_url' => [
@@ -82,7 +82,7 @@ $versions = [
       'sha1sum_download_url' => "{base_url}/v{version}/sha1sum.txt.asc",
       'sha512sum_download_url' => "{base_url}/v{version}/sha512sum.txt.asc",
       'release_page_url' => 'https://github.com/canton7/SyncTrayzor/releases/tag/v{version}',
-      'release_notes' => "Add a workaround for an Intel Xe graphics driver bug which causes Syncthing's UI to appear blank.\n\nIf you are not affected by this, you do not need to upgrade.",
+      'release_notes' => "- Don't crash in rare cases trying to detect if Intel Xe Graphics in use (#626)\n- Make it clearer that balloon settings are on the Folders tab (#613)\n- Don't show device connectivity balloons by default\n- Uninstaller: try to shut down running SyncTrayzor instances (#516)",
    ],
    '1.1.21' => [
       'base_url' => 'https://synctrayzor.antonymale.co.uk/download',
@@ -106,6 +106,7 @@ $versions = [
 ];
 
 $upgrades = [
+   // Currently no need to upgrade people on 1.1.28 or 1.1.29
    '1.1.27' => ['to' => 'latest', 'formatter' => '5'],
    '1.1.26' => ['to' => 'latest', 'formatter' => '5'],
    '1.1.25' => ['to' => 'latest', 'formatter' => '5'],

+ 1 - 1
src/SyncTrayzor/App.config

@@ -36,7 +36,7 @@
         <ShowTrayIconOnlyOnClose>false</ShowTrayIconOnlyOnClose>
         <MinimizeToTray>false</MinimizeToTray>
         <CloseToTray>true</CloseToTray>
-        <ShowDeviceConnectivityBalloons>true</ShowDeviceConnectivityBalloons>
+        <ShowDeviceConnectivityBalloons>false</ShowDeviceConnectivityBalloons>
         <ShowSynchronizedBalloonEvenIfNothingDownloaded>false</ShowSynchronizedBalloonEvenIfNothingDownloaded>
         <SyncthingAddress>localhost:8384</SyncthingAddress>
         <StartSyncthingAutomatically>true</StartSyncthingAutomatically>

+ 22 - 1
src/SyncTrayzor/Bootstrapper.cs

@@ -119,6 +119,21 @@ namespace SyncTrayzor
             {
                 try
                 {
+                    if (this.options.Shutdown)
+                    {
+                        client.Shutdown();
+                        // Give it some time to shut down
+                        var elapsed = Stopwatch.StartNew();
+                        while (elapsed.Elapsed < TimeSpan.FromSeconds(10) &&
+                            this.Container.Get<IIpcCommsClientFactory>().TryCreateClient() != null)
+                        {
+                            Thread.Sleep(100);
+                        }
+                        // Wait another half-second -- it seems it can take the browser process a little longer to exit
+                        Thread.Sleep(500);
+                        Environment.Exit(0);
+                    }
+
                     if (this.options.StartSyncthing || this.options.StopSyncthing)
                     {
                         if (this.options.StartSyncthing)
@@ -142,6 +157,12 @@ namespace SyncTrayzor
                     logger.Error(e, $"Failed to talk to {client}: {e.Message}. Pretending that it doesn't exist...");
                 }
             }
+            
+            // If we got this far, there probably isn't another instance running, and we should just shut down
+            if (this.options.Shutdown)
+            {
+                Environment.Exit(0);
+            }
 
             var configurationProvider = this.Container.Get<IConfigurationProvider>();
             configurationProvider.Initialize(AppSettings.Instance.DefaultUserConfiguration);
@@ -346,7 +367,7 @@ namespace SyncTrayzor
             this.exiting = true;
 
             // Try and be nice and close SyncTrayzor gracefully, before the Dispose call on SyncthingProcessRunning kills it dead
-            this.Container.Get<ISyncthingManager>().StopAsync().Wait(500);
+            this.Container.Get<ISyncthingManager>().StopAndWaitAsync().Wait(2000);
         }
 
         public override void Dispose()

+ 6 - 6
src/SyncTrayzor/NotifyIcon/NotifyIconViewModel.cs

@@ -153,21 +153,21 @@ namespace SyncTrayzor.NotifyIcon
         }
 
         public bool CanStop => this.SyncthingState == SyncthingState.Running;
-        public void Stop()
+        public async void Stop()
         {
-            this.syncthingManager.StopAsync();
+            await this.syncthingManager.StopAsync();
         }
 
         public bool CanRestart => this.SyncthingState == SyncthingState.Running;
-        public void Restart()
+        public async void Restart()
         {
-            this.syncthingManager.RestartAsync();
+            await this.syncthingManager.RestartAsync();
         }
 
         public bool CanRescanAll => this.SyncthingState == SyncthingState.Running;
-        public void RescanAll()
+        public async void RescanAll()
         {
-            this.syncthingManager.ScanAsync(null, null);
+            await this.syncthingManager.ScanAsync(null, null);
         }
 
         public void Exit()

+ 3 - 3
src/SyncTrayzor/Pages/Settings/SettingsView.xaml

@@ -65,7 +65,8 @@
 
                         <GroupBox DockPanel.Dock="Top" Header="{l:Loc SettingsView_Section_TrayIcon}">
                             <DockPanel>
-                                <CheckBox DockPanel.Dock="Top" IsChecked="{Binding ShowTrayIconOnlyOnClose.Value}" Content="{l:Loc SettingsView_OnlyShowTrayIconOnClose}"/>
+                                <TextBlock DockPanel.Dock="Top" Margin="0,0,0,5" TextWrapping="Wrap"
+                                           Text="{l:Loc SettingsView_Alerts_SeeFolders}"/>
                                 <Grid DockPanel.Dock="Top">
                                     <CheckBox IsChecked="{Binding ShowSynchronizedBalloonEvenIfNothingDownloaded.Value}"
                                               Width="{Binding Parent.ActualWidth, RelativeSource={RelativeSource Self}}"
@@ -73,8 +74,7 @@
                                 </Grid>
                                 <CheckBox DockPanel.Dock="Top" IsChecked="{Binding ShowDeviceConnectivityBalloons.Value}" Content="{l:Loc SettingsView_ShowDeviceConnectivityBalloons}"/>
                                 <CheckBox DockPanel.Dock="Top" IsChecked="{Binding ShowDeviceOrFolderRejectedBalloons.Value}" Content="{l:Loc SettingsView_ShowDeviceOrFolderRejectedBalloons}"/>
-                                <TextBlock DockPanel.Dock="Top" Margin="0,5,0,0" TextWrapping="Wrap"
-                                           Text="{l:Loc SettingsView_Alerts_SeeFolders}"/>
+                                <CheckBox DockPanel.Dock="Top" IsChecked="{Binding ShowTrayIconOnlyOnClose.Value}" Content="{l:Loc SettingsView_OnlyShowTrayIconOnClose}"/>
                                 <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,5,0,0">
                                     <Label Padding="0,0,5,0" Target="{Binding ElementName=IconAnimationModeSelect}" VerticalAlignment="Center" Content="{l:Loc SettingsView_AnimateTrayIcon}"/>
                                     <ComboBox x:Name="IconAnimationModeSelect" ItemsSource="{Binding IconAnimationModes}" SelectedValuePath="Value" SelectedValue="{Binding IconAnimationMode.Value}"/>

+ 4 - 0
src/SyncTrayzor/Pages/Settings/SettingsViewModel.cs

@@ -307,8 +307,12 @@ namespace SyncTrayzor.Pages.Settings
             }
 
             this.FolderSettings.AddRange(folderSettings.OrderBy(x => x.FolderLabel));
+
             this.IsAnyFolderWatchEnabledInSyncthing = this.FolderSettings.Any(x => !x.IsWatchAllowed);
 
+            this.UpdateAreAllFoldersWatched();
+            this.UpdateAreAllFoldersNotified();
+
             this.NotifyOfPropertyChange(nameof(this.FolderSettings));
         }
 

+ 4 - 4
src/SyncTrayzor/Pages/ShellViewModel.cs

@@ -93,15 +93,15 @@ namespace SyncTrayzor.Pages
         }
 
         public bool CanStop => this.SyncthingState == SyncthingState.Running;
-        public void Stop()
+        public async void Stop()
         {
-            this.syncthingManager.StopAsync();
+            await this .syncthingManager.StopAsync();
         }
 
         public bool CanRestart => this.SyncthingState == SyncthingState.Running;
-        public void Restart()
+        public async void Restart()
         {
-            this.syncthingManager.RestartAsync();
+            await this.syncthingManager.RestartAsync();
         }
 
         public bool CanRefreshBrowser => this.SyncthingState == SyncthingState.Running;

+ 1 - 1
src/SyncTrayzor/Properties/Resources.Designer.cs

@@ -910,7 +910,7 @@ namespace SyncTrayzor.Properties {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to See the &apos;Folders&apos; tab for per-folder notification settings..
+        ///   Looks up a localized string similar to Disable balloon messages entirely on the &apos;Folders&apos; tab.
         /// </summary>
         public static string SettingsView_Alerts_SeeFolders {
             get {

+ 3 - 3
src/SyncTrayzor/Properties/Resources.resx

@@ -307,7 +307,7 @@ This risks corrupting Syncthing's database. ONLY do this if you are unable to st
 
 Please read the log to determine the cause.
 
-If "FATAL: Cannot open database" appears, please close any other open instances of Syncthing. If SyncTrayzor crashed previously, there may still be zombine Syncthing processes alive. Please use the menu option "Syncthing -&gt; Kill all Syncthing processes" to stop them, then use "Syncthing -&gt; Start" to start Syncthing again.</value>
+If "FATAL: Cannot open database" appears, please close any other open instances of Syncthing. If SyncTrayzor crashed previously, there may still be zombie Syncthing processes alive. Please use the menu option "Syncthing -&gt; Kill all Syncthing processes" to stop them, then use "Syncthing -&gt; Start" to start Syncthing again.</value>
     <comment>Content of the dialog shown when Syncthing couldn't be started (and exited with an error). Instructs the user on how to proceed.
 
 "FATAL: Cannot Open Database" should remain in English
@@ -873,7 +873,7 @@ Please donate to my charity fundraising campaign.</value>
     <value>Metered Networks</value>
   </data>
   <data name="SettingsView_Alerts_SeeFolders" xml:space="preserve">
-    <value>See the 'Folders' tab for per-folder notification settings.</value>
+    <value>Disable balloon messages entirely on the 'Folders' tab</value>
   </data>
   <data name="ViewerView_BrowseToFolder" xml:space="preserve">
     <value>Browse</value>
@@ -1002,4 +1002,4 @@ Please donate to my charity fundraising campaign.</value>
   <data name="BarAlertsView_IntelXeGraphics_DismissLink" xml:space="preserve">
     <value>Dismiss</value>
   </data>
-</root>
+</root>

+ 9 - 0
src/SyncTrayzor/Services/CommandLineOptionsParser.cs

@@ -1,7 +1,9 @@
 using Mono.Options;
 using Stylet;
 using System.IO;
+using System.Runtime.InteropServices.ComTypes;
 using System.Windows;
+using System.Windows.Forms;
 
 namespace SyncTrayzor.Services
 {
@@ -9,6 +11,7 @@ namespace SyncTrayzor.Services
     {
         private readonly IWindowManager windowManager;
 
+        public bool Shutdown { get; private set; }
         public bool StartMinimized { get; private set; }
         public bool StartSyncthing { get; private set; }
         public bool StopSyncthing { get; private set; }
@@ -30,6 +33,7 @@ namespace SyncTrayzor.Services
             bool show = false;
 
             var options = new OptionSet()
+                .Add("shutdown", "\nIf another SyncTrayzor process is running, tell it to shutdown.", v => this.Shutdown = true)
                 .Add("start-syncthing", "\nIf another SyncTrayzor process is running, tell it to start Syncthing. Otherwise, launch with Syncthing started regardless of configuration.", v => this.StartSyncthing = true)
                 .Add("stop-syncthing", "\nIf another SyncTrayzor process is running, tell it to stop Syncthing. Otherwise, launch with Syncthing stopped regardless of configuration.", v => this.StopSyncthing = true)
                 .Add("noautostart", null, v => this.StopSyncthing = true, hidden: true)
@@ -47,6 +51,11 @@ namespace SyncTrayzor.Services
                 return false;
             }
 
+            if (this.Shutdown && (this.StartSyncthing || this.StopSyncthing || minimized || show))
+            {
+                this.windowManager.ShowMessageBox("--shutdown may not be used with any other options", "Error", icon: MessageBoxImage.Error);
+                return false;
+            }
             if (this.StartSyncthing && this.StopSyncthing)
             {
                 this.windowManager.ShowMessageBox("--start-syncthing and --stop-syncthing may not be used together", "Error", icon: MessageBoxImage.Error);

+ 1 - 1
src/SyncTrayzor/Services/Config/Configuration.cs

@@ -81,7 +81,7 @@ namespace SyncTrayzor.Services.Config
             this.MinimizeToTray = false;
             this.CloseToTray = true;
             this.ShowSynchronizedBalloonEvenIfNothingDownloaded = false;
-            this.ShowDeviceConnectivityBalloons = true;
+            this.ShowDeviceConnectivityBalloons = false;
             this.ShowDeviceOrFolderRejectedBalloons = true;
             this.SyncthingAddress = "localhost:8384";
             this.StartSyncthingAutomatically = true;

+ 16 - 9
src/SyncTrayzor/Services/GraphicsCardDetector.cs

@@ -20,21 +20,28 @@ namespace SyncTrayzor.Services
         }
 
         private static bool GetIsIntelXe()
-        { 
-            var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_VideoController");
-            foreach (ManagementObject obj in searcher.Get())
+        {
+            // ManagedObjectEnumerator.MoveNext can throw a COMException if WMI isn't started.
+            // We also want to ignore other potential problems. If we hit something, assume they're not
+            // using Intel Xe Graphics.
+            try
             {
-                if (obj["CurrentBitsPerPixel"] != null && obj["CurrentHorizontalResolution"] != null)
+                var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_VideoController");
+                foreach (ManagementObject obj in searcher.Get())
                 {
-                    string name = obj["Name"]?.ToString();
-                    if (name.IndexOf("Intel", StringComparison.OrdinalIgnoreCase) >= 0 &&
-                        name.IndexOf(" Xe ", StringComparison.OrdinalIgnoreCase) >= 0)
+                    if (obj["CurrentBitsPerPixel"] != null && obj["CurrentHorizontalResolution"] != null)
                     {
-                        logger.Info($"Graphics card: {name}");
-                        return true;
+                        string name = obj["Name"]?.ToString();
+                        if (name.IndexOf("Intel", StringComparison.OrdinalIgnoreCase) >= 0 &&
+                            name.IndexOf(" Xe ", StringComparison.OrdinalIgnoreCase) >= 0)
+                        {
+                            logger.Info($"Graphics card: {name}");
+                            return true;
+                        }
                     }
                 }
             }
+            catch { }
 
             return false;
         }

+ 6 - 0
src/SyncTrayzor/Services/Ipc/IpcCommsClient.cs

@@ -14,6 +14,7 @@ namespace SyncTrayzor.Services.Ipc
 
     public interface IIpcCommsClient
     {
+        void Shutdown();
         void ShowMainWindow();
         void StartSyncthing();
         void StopSyncthing();
@@ -30,6 +31,11 @@ namespace SyncTrayzor.Services.Ipc
             this.pid = pid;
         }
 
+        public void Shutdown()
+        {
+            this.SendCommand("Shutdown");
+        }
+
         public void ShowMainWindow()
         {
             this.SendCommand("ShowMainWindow");

+ 12 - 1
src/SyncTrayzor/Services/Ipc/IpcCommsServer.cs

@@ -20,15 +20,17 @@ namespace SyncTrayzor.Services.Ipc
         private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
 
         private readonly ISyncthingManager syncthingManager;
+        private readonly IApplicationState applicationState;
         private readonly IApplicationWindowState windowState;
 
         private CancellationTokenSource cts;
 
         public string PipeName =>  $"SyncTrayzor-{Process.GetCurrentProcess().Id}";
 
-        public IpcCommsServer(ISyncthingManager syncthingManager, IApplicationWindowState windowState)
+        public IpcCommsServer(ISyncthingManager syncthingManager, IApplicationState applicationState, IApplicationWindowState windowState)
         {
             this.syncthingManager = syncthingManager;
+            this.applicationState = applicationState;
             this.windowState = windowState;
         }
 
@@ -101,6 +103,10 @@ namespace SyncTrayzor.Services.Ipc
         {
             switch (command)
             {
+                case "Shutdown":
+                    this.Shutdown();
+                    return "OK";
+
                 case "ShowMainWindow":
                     this.ShowMainWindow();
                     return "OK";
@@ -118,6 +124,11 @@ namespace SyncTrayzor.Services.Ipc
             }
         }
 
+        private void Shutdown()
+        {
+            this.applicationState.Shutdown();
+        }
+
         private void ShowMainWindow()
         {
             this.windowState.EnsureInForeground();