sqlinsight-pro/src/SQLInsight.Servers/WindowsServerAccess.cs
Søren Eggert Lundsteen Olsen 7082b278e3 Collection: Windows servers over CIM (WS-Man, DCOM fallback) and synthetic check runners
New project SQLInsight.Servers:
- CimWindowsServerSource (Microsoft.Management.Infrastructure, the API behind
  Get-CimInstance): WS-Man first, DCOM if that fails, a local session for the
  agent's own computer, as the agent's identity. Only SELECT queries (CimQueries)
  and Task Scheduler's read-only GetInfo. CPU and paging from raw counters, disks
  (Win32_Volume, Win32_LogicalDisk fallback), services, scheduled tasks, and the
  System/Application logs through the Event Log service (MS-EVEN6, XPath filter on
  the server) with Win32_NTLogEvent as fallback.
- ServerErrors: access denied, missing namespace, RPC/WinRM unreachable and
  timeouts become "ikke målbar" reasons that name the missing right and the grant
  script; never local administrator.
- WindowsServerTester: tries every area once for the web UI's test.
- SyntheticCheckRunner: TCP, HTTP(S) with expected status/text and TLS certificate
  expiry (also for refused certificates), SQL login + read-only query (re-validated,
  in a transaction that is always rolled back), DNS, and the Dynamics AX logon:
  AOS port, then a logon through the .NET Business Connector in a separate Windows
  PowerShell process with a timeout when it is installed; otherwise the result
  says clearly that only the port was tested.

Tests: error mapping, queries, AX command/parsing, runners against local TCP and
HTTP(S) servers with a self-signed certificate, the CIM source against this
computer, and the SQL check against the test instance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:18 +02:00

106 lines
4.5 KiB
C#

using SQLInsight.Core.Servers;
namespace SQLInsight.Servers;
/// <summary>Opens read-only sessions to Windows servers, as the agent's own identity.</summary>
public interface IWindowsServerSource
{
/// <summary>Connects to <paramref name="host"/>: WS-Man (WinRM) first, DCOM if that fails, or locally for the agent's own computer.</summary>
/// <exception cref="ServerAccessException">The server could not be reached; the message says why (Danish).</exception>
Task<IWindowsServerSession> ConnectAsync(string host, CancellationToken cancellationToken);
}
/// <summary>
/// One connection to a Windows server. Every method only reads: no method changes, starts or stops anything. Each
/// throws <see cref="ServerAccessException"/> with the exact reason when its area cannot be read.
/// </summary>
public interface IWindowsServerSession : IDisposable
{
/// <summary>"WS-Man", "DCOM" or "lokal".</summary>
string Protocol { get; }
/// <summary>Why the preferred protocol was not used (Danish), when it was not.</summary>
string? ProtocolNote { get; }
Task<ServerOsInfo> GetOperatingSystemAsync(CancellationToken cancellationToken);
Task<ServerPerformanceRaw> GetPerformanceAsync(DateTimeOffset now, CancellationToken cancellationToken);
Task<IReadOnlyList<ServerDisk>> GetDisksAsync(CancellationToken cancellationToken);
Task<IReadOnlyList<ServerService>> GetServicesAsync(CancellationToken cancellationToken);
/// <summary>One service, or null when it does not exist (or cannot be seen).</summary>
Task<ServerService?> GetServiceAsync(string name, CancellationToken cancellationToken);
/// <summary>Errors and critical events in the System and Application logs since <paramref name="since"/>, oldest first.</summary>
Task<ServerEventRead> GetEventsAsync(DateTimeOffset since, int max, CancellationToken cancellationToken);
/// <summary>Scheduled tasks; run information only for the ones <paramref name="watch"/> selects (at most <paramref name="maxWatched"/>).</summary>
Task<ServerTaskRead> GetTasksAsync(Func<string, bool> watch, int maxWatched, CancellationToken cancellationToken);
}
/// <param name="Method">How the logs were read: "Event Log (RPC)" or "CIM (Win32_NTLogEvent)".</param>
/// <param name="Truncated">More events than <c>max</c>: the rest is read next time.</param>
public sealed record ServerEventRead(IReadOnlyList<ServerEventEntry> Entries, string Method, bool Truncated, string? Note = null);
/// <param name="Visible">Every task the agent could see.</param>
public sealed record ServerTaskRead(IReadOnlyList<ServerTask> Watched, int Visible, string? Note = null);
public enum ServerAccessProblem
{
/// <summary>The server does not answer (name, network, firewall, WinRM/RPC not running).</summary>
Unreachable,
/// <summary>The server answers, but the agent's account may not read this.</summary>
AccessDenied,
/// <summary>The server does not have it (an older Windows without the class or namespace).</summary>
NotAvailable,
/// <summary>The server did not answer in time.</summary>
Timeout,
/// <summary>The agent cannot do it at all (not on Windows).</summary>
NotSupported,
Other,
}
/// <summary>A Windows server or one of its areas could not be read. The message is Danish and says exactly why and what to do.</summary>
public sealed class ServerAccessException : Exception
{
public ServerAccessException()
{
}
public ServerAccessException(string message)
: base(message)
{
}
public ServerAccessException(string message, Exception innerException)
: base(message, innerException)
{
}
public ServerAccessException(ServerAccessProblem problem, string message, Exception? innerException = null)
: base(message, innerException)
{
Problem = problem;
}
public ServerAccessProblem Problem { get; } = ServerAccessProblem.Other;
/// <summary>The Windows error code (HRESULT) when there was one, for support.</summary>
public uint? Code { get; init; }
}
/// <summary>For an agent that does not run on Windows: Windows servers cannot be read from Linux.</summary>
public sealed class UnsupportedWindowsServerSource : IWindowsServerSource
{
public const string Reason = "Windows-servere kan kun overvåges, når SQLInsight Agent kører på Windows (CIM/WMI og hændelseslog er Windows-protokoller).";
public Task<IWindowsServerSession> ConnectAsync(string host, CancellationToken cancellationToken) =>
throw new ServerAccessException(ServerAccessProblem.NotSupported, Reason);
}