using System.Globalization; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; namespace SQLInsight.Core.Servers; /// Task Scheduler's result codes (LastTaskResult) in Danish, and which of them mean the task failed. public static partial class TaskResults { public const uint Success = 0; public const uint Ready = 0x41300; public const uint Running = 0x41301; public const uint Disabled = 0x41302; public const uint HasNotRun = 0x41303; public const uint NoMoreRuns = 0x41304; public const uint TerminatedByUser = 0x41306; public const uint Queued = 0x41325; public const uint AlreadyRunning = 0x8004131F; /// Whether the last run ended badly. Running, queued, never run and "already running" are not failures. public static bool IsFailure(uint? result) => result is { } r && r is not (Success or Ready or Running or Disabled or HasNotRun or NoMoreRuns or Queued or AlreadyRunning); public static string Hex(uint result) => "0x" + result.ToString("X", CultureInfo.InvariantCulture); public static string Describe(uint result) => result switch { Success => "Fuldført (0x0)", Ready => "Klar (0x41300)", Running => "Kører nu (0x41301)", Disabled => "Deaktiveret (0x41302)", HasNotRun => "Har ikke kørt endnu (0x41303)", NoMoreRuns => "Ingen flere planlagte kørsler (0x41304)", TerminatedByUser => "Afbrudt af en bruger eller fordi den kørte for længe (0x41306)", Queued => "I kø (0x41325)", AlreadyRunning => "Kørte allerede, en ny kørsel blev ikke startet (0x8004131F)", 0x1 => "Programmet sluttede med fejlkode 1 (typisk en fejl i scriptet eller programmet) (0x1)", 0x2 => "Filen blev ikke fundet (0x2)", 0xC000013A => "Programmet blev afbrudt (Ctrl+C eller nedlukning) (0xC000013A)", 0x80070002 => "Filen blev ikke fundet (0x80070002)", 0x80070005 => "Adgang nægtet: kontoen, opgaven kører som, mangler rettigheder (0x80070005)", 0x8007052E => "Logon fejlede: forkert brugernavn eller adgangskode til opgavens konto (0x8007052E)", 0x800710E0 => "Opgaven blev afvist (fx fordi computeren kørte på batteri eller kontoen ikke var logget på) (0x800710E0)", 0x80041326 => "Opgaven er deaktiveret (0x80041326)", _ => "Sluttede med fejlkode " + Hex(result), }; /// Whether a task is Microsoft's own (\Microsoft\…), which is not watched unless asked for. public static bool IsMicrosoftTask(string path) { ArgumentNullException.ThrowIfNull(path); return path.StartsWith(@"\Microsoft\", StringComparison.OrdinalIgnoreCase); } /// /// Tasks that programs create per user or to update themselves, in the root folder rather than under \Microsoft\: /// they are not the server's own work, and their failures are rarely anyone's business. A name, or a prefix ending in *. /// public static readonly IReadOnlyList DefaultIgnoredNames = [ "OneDrive Standalone Update Task*", "OneDrive Per-Machine Standalone Update Task*", "OneDrive Reporting Task*", "OneDrive Startup Task*", "MicrosoftEdgeUpdateTask*", "GoogleUpdateTask*", "GoogleUpdaterTask*", "User_Feed_Synchronization-*", ]; /// /// Whether a task belongs to one user (its name carries the user's SID, S-1-5-21-…) or is a program's own updater /// (). Such tasks are not watched unless asked for. /// public static bool IsUserOrUpdaterTask(string path) { ArgumentNullException.ThrowIfNull(path); if (path.Contains("S-1-5-21-", StringComparison.OrdinalIgnoreCase)) { return true; } var name = path[(path.LastIndexOf('\\') + 1)..]; return DefaultIgnoredNames.Any(p => p.EndsWith('*') ? name.StartsWith(p[..^1], StringComparison.OrdinalIgnoreCase) : name.Equals(p, StringComparison.OrdinalIgnoreCase)); } /// Whether the task's path names a user by SID. public static bool HasUserSid(string path) { ArgumentNullException.ThrowIfNull(path); return Sid().IsMatch(path); } /// /// The task's path for titles and subjects: a user's SID is replaced by "bruger-" and a short hash of it, which tells /// two users' tasks apart without naming the user. /// public static string Display(string path) { ArgumentNullException.ThrowIfNull(path); return Sid().Replace(path, m => "bruger-" + Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(m.Value.ToUpperInvariant())))[..8].ToLowerInvariant()); } [GeneratedRegex(@"S-1-5-21(-[0-9]+)+", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] private static partial Regex Sid(); }