Skip to content

Conversation

@K97i
Copy link
Collaborator

@K97i K97i commented Sep 23, 2025

One of the feature requests was to make specify possible to run on command line without the GUI. While it probably would've been better to just convert this project back into a CLI that calls the GUI when needed, this is probably the easier method given the project's activity.

When run on the command line with -h as one of the arguments, specify will open a separate console window where the debug log will be shown (couldn't be bothered to bring back the original TUI, but as a proof of concept, it works). It then waits for the user with the output text found in the GUI version to exit the app.

A limitation of this is because of the task-based nature of the collection system, I couldn't figure out how to display Upload Minidumps prompt properly. In code, I disabled Minidump collection entirely.

private void Application_Startup(object sender, StartupEventArgs e)

[DllImport("Kernel32.dll")]
public static extern bool AllocConsole();

Check notice

Code scanning / CodeQL

Unmanaged code Note

Minimise the use of unmanaged code.

Copilot Autofix

AI about 1 month ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

public static extern bool AllocConsole();

[DllImport("kernel32.dll")]
private static extern bool FreeConsole();

Check notice

Code scanning / CodeQL

Unmanaged code Note

Minimise the use of unmanaged code.

Copilot Autofix

AI about 1 month ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

{
Utils.GetWmi("Win32_OperatingSystem");
Settings.Headless = true;
AllocConsole();

Check notice

Code scanning / CodeQL

Calls to unmanaged code Note

Replace this call with a call to managed code if possible.

Copilot Autofix

AI about 1 month ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Comment on lines +44 to +48
catch (Exception ex)
{
Console.WriteLine($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}");
Environment.Exit(-1);
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

Copilot Autofix

AI about 1 month ago

To fix the problem, the catch clause on line 44 (in the headless block) should be replaced with more specific catches for the exceptions that are expected from Utils.GetWmi("Win32_OperatingSystem"). For WMI operations, this would be System.Management.ManagementException and potentially System.UnauthorizedAccessException. Any other exception type could be caught in a last generic catch if desired, possibly with a different message or log. You should:

  • Replace catch (Exception ex) with specific catches for ManagementException and UnauthorizedAccessException.
  • Optionally, add a final catch (Exception ex) just to log and exit, but with a distinct message.
  • Add the required imports (using System.Management;) at the top if not already present (you haven't shown it).

All these changes are within client/Frontend/App.xaml.cs, and specifically for lines around the block containing

42:                 Utils.GetWmi("Win32_OperatingSystem");
44:             catch (Exception ex)
45:             {
46:                 Console.WriteLine($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}");
47:                 Environment.Exit(-1);
48:             }

You may also need to add the import for System.Management near the top for ManagementException.

Suggested changeset 1
client/Frontend/App.xaml.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/client/Frontend/App.xaml.cs b/client/Frontend/App.xaml.cs
--- a/client/Frontend/App.xaml.cs
+++ b/client/Frontend/App.xaml.cs
@@ -3,6 +3,7 @@
 using System;
 using System.Linq;
 using System.Runtime.InteropServices;
+using System.Management;
 
 namespace specify_client;
 
@@ -41,9 +42,19 @@
             {
                 Utils.GetWmi("Win32_OperatingSystem");
             }
+            catch (ManagementException mex)
+            {
+                Console.WriteLine($"Specify is unable to communicate with the Windows Management Instrumentation (ManagementException).\n{mex}");
+                Environment.Exit(-1);
+            }
+            catch (UnauthorizedAccessException uex)
+            {
+                Console.WriteLine($"Specify does not have permission to access the Windows Management Instrumentation.\n{uex}");
+                Environment.Exit(-1);
+            }
             catch (Exception ex)
             {
-                Console.WriteLine($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}");
+                Console.WriteLine($"An unexpected error occurred while communicating with the Windows Management Instrumentation.\n{ex}");
                 Environment.Exit(-1);
             }
 
EOF
@@ -3,6 +3,7 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Management;

namespace specify_client;

@@ -41,9 +42,19 @@
{
Utils.GetWmi("Win32_OperatingSystem");
}
catch (ManagementException mex)
{
Console.WriteLine($"Specify is unable to communicate with the Windows Management Instrumentation (ManagementException).\n{mex}");
Environment.Exit(-1);
}
catch (UnauthorizedAccessException uex)
{
Console.WriteLine($"Specify does not have permission to access the Windows Management Instrumentation.\n{uex}");
Environment.Exit(-1);
}
catch (Exception ex)
{
Console.WriteLine($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}");
Console.WriteLine($"An unexpected error occurred while communicating with the Windows Management Instrumentation.\n{ex}");
Environment.Exit(-1);
}

Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +81 to +84
catch (Exception ex)
{
System.IO.File.WriteAllText(@"specify_hardfail.log", $"{ex}");
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

Copilot Autofix

AI about 1 month ago

To fix the problem, we should catch only specific exception types in the try block around the await Program.Main(); call. Most likely, failures here will be due to application-level errors rather than critical system faults. Common exception types to handle are InvalidOperationException, IOException, and (as a fallback) perhaps allow unhandled exceptions to crash the process. If necessary, catching the base Exception (but not non-catchable exceptions like OutOfMemoryException, StackOverflowException) can be acceptable only if we immediately terminate the application afterward. You can either avoid the generic Exception catch clause or add logic to rethrow when a critical exception is encountered.

The best fix is to replace catch (Exception ex) with catches for specific exception types and, optionally, a fallback catch for other exceptions except for fatal ones, which should be rethrown. Changes are made only in client/Frontend/App.xaml.cs, lines 81-84.

Suggested changeset 1
client/Frontend/App.xaml.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/client/Frontend/App.xaml.cs b/client/Frontend/App.xaml.cs
--- a/client/Frontend/App.xaml.cs
+++ b/client/Frontend/App.xaml.cs
@@ -78,8 +78,21 @@
             {
                 await Program.Main();
             }
-            catch (Exception ex)
+            catch (System.IO.IOException ex)
             {
+                System.IO.File.WriteAllText(@"specify_hardfail.log", $"IO Error: {ex}");
+            }
+            catch (InvalidOperationException ex)
+            {
+                System.IO.File.WriteAllText(@"specify_hardfail.log", $"Invalid Operation: {ex}");
+            }
+            catch (Exception ex) when (
+                !(ex is StackOverflowException)
+                && !(ex is OutOfMemoryException)
+                && !(ex is System.Threading.ThreadAbortException)
+                && !(ex is System.Runtime.InteropServices.SEHException)
+            )
+            {
                 System.IO.File.WriteAllText(@"specify_hardfail.log", $"{ex}");
             }
 
EOF
@@ -78,8 +78,21 @@
{
await Program.Main();
}
catch (Exception ex)
catch (System.IO.IOException ex)
{
System.IO.File.WriteAllText(@"specify_hardfail.log", $"IO Error: {ex}");
}
catch (InvalidOperationException ex)
{
System.IO.File.WriteAllText(@"specify_hardfail.log", $"Invalid Operation: {ex}");
}
catch (Exception ex) when (
!(ex is StackOverflowException)
&& !(ex is OutOfMemoryException)
&& !(ex is System.Threading.ThreadAbortException)
&& !(ex is System.Runtime.InteropServices.SEHException)
)
{
System.IO.File.WriteAllText(@"specify_hardfail.log", $"{ex}");
}

Copilot is powered by AI and may make mistakes. Always verify output.
}

Console.ReadKey();
FreeConsole();

Check notice

Code scanning / CodeQL

Calls to unmanaged code Note

Replace this call with a call to managed code if possible.

Copilot Autofix

AI about 1 month ago

The best way to fix this instance is to replace calls to the unmanaged FreeConsole() P/Invoke API with the managed equivalent Console.UnallocateConsole() method if the project's target framework is .NET 5.0 or later. This will remove dependence on unmanaged code for releasing the console window, making the application simpler and safer.

To do this:

  1. Remove the [DllImport("kernel32.dll")] private static extern bool FreeConsole(); declaration.
  2. Replace FreeConsole(); on line 87 with Console.UnallocateConsole();.
  3. Ensure using System; remains at the top as it contains the Console class.

If the code must support target frameworks older than .NET 5.0, leave a comment explaining why the unmanaged code must remain. See Microsoft docs for reference.

All changes are limited to client/Frontend/App.xaml.cs. No extra dependencies are needed.


Suggested changeset 1
client/Frontend/App.xaml.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/client/Frontend/App.xaml.cs b/client/Frontend/App.xaml.cs
--- a/client/Frontend/App.xaml.cs
+++ b/client/Frontend/App.xaml.cs
@@ -15,8 +15,8 @@
     [DllImport("Kernel32.dll")]
     public static extern bool AllocConsole();
 
-    [DllImport("kernel32.dll")]
-    private static extern bool FreeConsole();
+    // Replaced unmanaged FreeConsole with managed Console.UnallocateConsole (requires .NET 5+)
+    // private static extern bool FreeConsole();
 
     [STAThread]
     private async void Application_Startup(object sender, StartupEventArgs e)
@@ -84,7 +84,7 @@
             }
 
             Console.ReadKey();
-            FreeConsole();
+            Console.UnallocateConsole();
             Environment.Exit(0);
         }
 
EOF
@@ -15,8 +15,8 @@
[DllImport("Kernel32.dll")]
public static extern bool AllocConsole();

[DllImport("kernel32.dll")]
private static extern bool FreeConsole();
// Replaced unmanaged FreeConsole with managed Console.UnallocateConsole (requires .NET 5+)
// private static extern bool FreeConsole();

[STAThread]
private async void Application_Startup(object sender, StartupEventArgs e)
@@ -84,7 +84,7 @@
}

Console.ReadKey();
FreeConsole();
Console.UnallocateConsole();
Environment.Exit(0);
}

Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +106 to +110
catch (Exception ex)
{
MessageBox.Show($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}", "Specify", MessageBoxButton.OK, MessageBoxImage.Error);
Environment.Exit(-1);
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

Copilot Autofix

AI about 1 month ago

How to fix the problem:
Replace the generic catch (Exception ex) clause on line 106 with specific catch clauses for the exceptions that can reasonably be thrown by Utils.GetWmi. For WMI queries in C#, the likely exceptions include System.Management.ManagementException and System.UnauthorizedAccessException. To maintain user experience, these exceptions should be handled as in the original block. Optionally, if there are other specific exceptions (such as System.Runtime.InteropServices.COMException), they can be included as well if relevant. All other exceptions should be allowed to propagate, which provides better diagnostics and debuggability.

Detailed steps:

  • In the try...catch block (lines 102-110), replace the generic catch (Exception ex) on line 106 with two or more catch clauses:
    • catch (System.Management.ManagementException ex)
    • catch (System.UnauthorizedAccessException ex)
    • If warranted, potentially also catch (System.Runtime.InteropServices.COMException ex)
  • The error handling code within each block should remain as in the original: show a MessageBox and exit.
  • No additional imports are needed if System.Management and System.Runtime.InteropServices are already referenced elsewhere; otherwise, add using System.Management;.
Suggested changeset 1
client/Frontend/App.xaml.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/client/Frontend/App.xaml.cs b/client/Frontend/App.xaml.cs
--- a/client/Frontend/App.xaml.cs
+++ b/client/Frontend/App.xaml.cs
@@ -3,6 +3,7 @@
 using System;
 using System.Linq;
 using System.Runtime.InteropServices;
+using System.Management;
 
 namespace specify_client;
 
@@ -103,11 +104,21 @@
             {
                 Utils.GetWmi("Win32_OperatingSystem");
             }
-            catch (Exception ex)
+            catch (System.Management.ManagementException ex)
             {
                 MessageBox.Show($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}", "Specify", MessageBoxButton.OK, MessageBoxImage.Error);
                 Environment.Exit(-1);
             }
+            catch (System.UnauthorizedAccessException ex)
+            {
+                MessageBox.Show($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}", "Specify", MessageBoxButton.OK, MessageBoxImage.Error);
+                Environment.Exit(-1);
+            }
+            catch (System.Runtime.InteropServices.COMException ex)
+            {
+                MessageBox.Show($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}", "Specify", MessageBoxButton.OK, MessageBoxImage.Error);
+                Environment.Exit(-1);
+            }
 
             StartupUri = new Uri("/specify_client;component/Frontend/Landing.xaml", UriKind.Relative);
         }
EOF
@@ -3,6 +3,7 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Management;

namespace specify_client;

@@ -103,11 +104,21 @@
{
Utils.GetWmi("Win32_OperatingSystem");
}
catch (Exception ex)
catch (System.Management.ManagementException ex)
{
MessageBox.Show($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}", "Specify", MessageBoxButton.OK, MessageBoxImage.Error);
Environment.Exit(-1);
}
catch (System.UnauthorizedAccessException ex)
{
MessageBox.Show($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}", "Specify", MessageBoxButton.OK, MessageBoxImage.Error);
Environment.Exit(-1);
}
catch (System.Runtime.InteropServices.COMException ex)
{
MessageBox.Show($"Specify is unable to communicate with the Windows Management Instrumentation.\n{ex}", "Specify", MessageBoxButton.OK, MessageBoxImage.Error);
Environment.Exit(-1);
}

StartupUri = new Uri("/specify_client;component/Frontend/Landing.xaml", UriKind.Relative);
}
Copilot is powered by AI and may make mistakes. Always verify output.
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
var main = App.Current.MainWindow as Landing;
main.ProgramFinalize();

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
main
may be null at this access because of
this
assignment.

Copilot Autofix

AI about 1 month ago

To fix this issue, we should ensure that main is not null before attempting to dereference it. This can be done by checking that main is not null after the assignment (main = App.Current.MainWindow as Landing), before calling main.ProgramFinalize(). The best way is to wrap the dereference in an if (main != null) block. If main is null, we may optionally want to log an error, throw an exception, or handle the case gracefully. However, for the minimal fix, we should simply prevent the null dereference. No new imports or additional definitions are necessary; the change is fully contained within the lines shown.

Suggested changeset 1
client/Monolith.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/client/Monolith.cs b/client/Monolith.cs
--- a/client/Monolith.cs
+++ b/client/Monolith.cs
@@ -223,7 +223,9 @@
                     App.Current.Dispatcher.BeginInvoke(new Action(() =>
                     {
                         var main = App.Current.MainWindow as Landing;
-                        main.ProgramFinalize();
+                        if (main != null)
+                            main.ProgramFinalize();
+                        // Optionally: else handle/log null MainWindow.
                     }));
                 } else
                 {
EOF
@@ -223,7 +223,9 @@
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
var main = App.Current.MainWindow as Landing;
main.ProgramFinalize();
if (main != null)
main.ProgramFinalize();
// Optionally: else handle/log null MainWindow.
}));
} else
{
Copilot is powered by AI and may make mistakes. Always verify output.
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
var main = App.Current.MainWindow as Landing;
main.ProgramFinalizeNoUpload();

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
main
may be null at this access because of
this
assignment.

Copilot Autofix

AI about 1 month ago

To fix the problem, add a null check before calling ProgramFinalizeNoUpload() on main, which is the result of a cast operation that can yield a null value. If main is non-null, proceed to call the method; otherwise, handle the null case (e.g., do nothing, or log a message if desired). This change should be made within the lambda assigned to the BeginInvoke callback starting on line 239, specifically guarding line 241. No new imports or method definitions are required. The logic and flow of the application remain unchanged — we merely prevent a possible crash.


Suggested changeset 1
client/Monolith.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/client/Monolith.cs b/client/Monolith.cs
--- a/client/Monolith.cs
+++ b/client/Monolith.cs
@@ -238,7 +238,9 @@
                     App.Current.Dispatcher.BeginInvoke(new Action(() =>
                     {
                         var main = App.Current.MainWindow as Landing;
-                        main.ProgramFinalizeNoUpload();
+                        if (main != null)
+                            main.ProgramFinalizeNoUpload();
+                        // else, optionally log or handle error here
                     }));
                 }
                 else
EOF
@@ -238,7 +238,9 @@
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
var main = App.Current.MainWindow as Landing;
main.ProgramFinalizeNoUpload();
if (main != null)
main.ProgramFinalizeNoUpload();
// else, optionally log or handle error here
}));
}
else
Copilot is powered by AI and may make mistakes. Always verify output.
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
var main = App.Current.MainWindow as Landing;
main.ProgramFailed();

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
main
may be null at this access because of
this
assignment.

Copilot Autofix

AI about 1 month ago

To fix the problem, we need to ensure main is not null before calling main.ProgramFailed(). The safest way to do this without changing existing functionality is to add a null check for main before the dereference. If main is not null, we proceed as before. If it is null, we may choose to do nothing, or (preferably) log or display an error message so the situation is visible during debugging.

In client/Monolith.cs, lines 265-266, update the lambda block so that it only calls main.ProgramFailed() if main != null. No extra methods or imports are required for this change.


Suggested changeset 1
client/Monolith.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/client/Monolith.cs b/client/Monolith.cs
--- a/client/Monolith.cs
+++ b/client/Monolith.cs
@@ -263,7 +263,10 @@
                     App.Current.Dispatcher.BeginInvoke(new Action(() =>
                     {
                         var main = App.Current.MainWindow as Landing;
-                        main.ProgramFailed();
+                        if (main != null)
+                        {
+                            main.ProgramFailed();
+                        }
                     }));
                 }
                 else
EOF
@@ -263,7 +263,10 @@
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
var main = App.Current.MainWindow as Landing;
main.ProgramFailed();
if (main != null)
{
main.ProgramFailed();
}
}));
}
else
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants