Compare commits

...

101 commits
v1.0.3 ... main

Author SHA1 Message Date
29c0fb439d Version 1.5.2
Some checks failed
deploy community / deploy (push) Successful in 20s
deploy hub / deploy (push) Successful in 22s
CI / Build og unit tests (push) Successful in 12s
CI / MSI-installer (push) Successful in 14s
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 09:43:05 +02:00
8db226d2b6 Dynamics AX 3.0: batch STATUS checked against customer data
Cascorp's BATCH table: every STATUS 4 row has an end time and no exception
code (finished), STATUS 1 rows have no end time and starts long passed
(waiting). So AX 3.0 uses the same order as AX 4.0+: 0 Tilbageholdt,
1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet. The old assumption (0 Venter ...
4 Tilbageholdt) treated finished jobs as held and waiting ones as running.

SQLI-AX-003 skips waiting jobs planned more than BatchAbandonedDays (30)
ago: the customer has 25 such rows from 2007-2018 that no batch server
serves; they are dead rows, not a live alarm.

Adds docs/dynamics-ax/verificer-ax30-batchstatus.sql (read-only) to repeat
the check at other AX 3.0 customers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 09:36:56 +02:00
b878fd3ab6 Version 1.5.1: Windows-only tests pass in the Linux CI build
Some checks failed
deploy community / deploy (push) Successful in 16s
deploy hub / deploy (push) Successful in 18s
CI / Build og unit tests (push) Successful in 9s
CI / MSI-installer (push) Successful in 15s
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
- AxLogonClient.Command always builds a Windows path to powershell.exe
- The grant-script test is skipped outside Windows (needs Windows PowerShell)
- The DNS test looks up an absolute name, so a search domain can't resolve it

v1.5.0 was tagged but never released because these failed in CI.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 16:49:05 +02:00
a3af1334e7 Version 1.5.0
Some checks failed
CI / MSI-installer (push) Has been skipped
deploy community / deploy (push) Successful in 14s
deploy hub / deploy (push) Successful in 18s
CI / Build og unit tests (push) Failing after 39s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 16:06:36 +02:00
b29ebb424d Review 1.5: docs and appsettings for the reviewed behaviour: timeouts and backoff, new keys, API paging, dedup and redaction
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 16:06:22 +02:00
b010f37dcf Review 1.5: synthetic checks: "Kør nu" joins the running check, errors are stored once, paused checks are left alone
- "Kør nu" (ServerRequestHandler) goes through SyntheticCheckService.RunNowAsync: it joins a
  run of the check that is going on, or runs it under the same MaxParallelChecks throttle
  and registers it, so it never overlaps a scheduled run. A paused check is not run: the
  answer is "Tjekket er sat på pause." and nothing is stored, evaluated or opened.
- Server requests (test, run now) may take 3 min (the check's max timeout 120 s + the
  30 s service lookup + 30 s margin) instead of AgentRequestService's 2 min, so the result
  is always stored; a server test gets 2.5 min. Stale Running requests are cleaned after
  the longest timeout + 1 min.
- An exception a check run did not foresee becomes a stored failed result ("Tjekket kunne
  ikke køres: …"), so LastRunAt advances and the check waits its interval; it is logged
  once per change. A run that cannot even be stored is not retried before its interval
  (the agent remembers when it started it).
- The service lookup of a failing check has its own 30 s deadline; its error detail
  (naming the agent's account) is Sensitive.
- The rules get max(10, FailuresBeforeFinding, SlowRunsBeforeFinding) + 1 recent results
  (at most 100), so "Fejlet i træk" and FailuresBeforeFinding > 10 work.
- The interval is never under SyntheticCheckLimits.MinIntervalSeconds agent-side; state
  for removed checks is pruned; shutdown waits for running checks before the throttle is
  disposed; a TaskCanceledException that is not shutdown no longer ends the service.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 16:02:17 +02:00
6d8a5d9ad7 Review 1.5: a Windows server that hangs no longer holds up the agent; reads are bounded and backed off
Deadlines
- Event log over RPC (MS-EVEN6): the whole read has a deadline (OperationTimeout per log);
  the token cancels the EventLogSession's operations and is checked before each
  FormatDescription, and the agent stops waiting even if the native call ignores it.
  Reported as ServerAccessProblem.Timeout ("svarede ikke i tide over hændelsesloggens
  protokol (RPC) inden for N sekunder").
- Every CIM call runs on the thread pool and is awaited with the caller's token; a call
  that ignores cancellation is abandoned and the CIM session is closed once it returns.
- Each server cycle has a deadline (new SQLInsight:Servers:CycleTimeout, default 5 min,
  1-30 min, at least 2 x OperationTimeout): the areas not read by then are "ikke målbar"
  ("Serveren svarede ikke inden for N minutter, så indsamlingen blev afbrudt; …"),
  logged once, and the slot is freed.
- Scheduled tasks: run information is read within a time budget (CycleTimeout / 3); the
  rest keep their state without the last run, and the coverage note says so.
- "Test server" has an overall deadline (WindowsServerTester, default 2 min); areas not
  done in time are reported as not answered, so the result is always stored.

Backoff and memory
- A server that does not answer (Unreachable/Timeout, or a timed-out cycle) is backed
  off 1, 2, 5, 10, then 15 minutes, reset when it answers.
- Protocol Auto: the protocol that worked (e.g. DCOM after WS-Man was refused, also for
  access denied) is tried first for an hour, then WS-Man is tried again; a server whose
  event log RPC failed is read over CIM for an hour.
- Over WS-Man with read-only WinRM rights Task Scheduler's GetInfoByName is denied: with
  Auto the tasks are read over DCOM instead (remembered for the server); with WSMan forced
  the reason says to choose Auto or DCOM. The advice "winrm configSDDL default" (broader
  WinRM rights) is gone; texts follow the grant script (-WinRM read right,
  -EnableFirewall -AgentAddress), not Remote Management Users.

Events
- Each log continues from its own mark (ServerEventSnapshot.LogReadUntil, a small additive
  field in Core): a truncated System log no longer loses the events between its last
  read event and the newest Application event.
- CIM fallback: Win32_NTLogEvent is streamed and only the oldest max per log are kept
  (memory bounded, deterministic order), consistent with the RPC read and the coverage
  text, which now names the logs that had more.

Other
- CimInstances (and method results) are mapped to SQLInsight's types and disposed at once.
- Options are clamped when bound (ServerOptions/SyntheticCheckOptions.Normalize):
  OperationTimeout 5 s-5 min, MaxEventsPerCollection 10-10000, MaxWatched 1-200,
  MaxParallelServers/Checks 1-32, retention days >= 1, ForecastHistoryDays 1-365,
  FailuresBeforeFinding/SlowRunsBeforeFinding 1-99.
- Shutdown: the timer's cancellation is caught, running cycles are awaited before the
  throttle is disposed; a cycle's time is taken when it gets its slot; a TaskCanceledException
  that is not shutdown no longer ends the service.
- Retention maintenance also runs (hourly) when SQLInsight:Servers:Enabled is false but
  synthetic checks are on.
- LocalCimTests run alone (collection without parallelization), share one warmed-up
  session, use the same 2 min operation timeout and a 5 min deadline per test, a smaller
  workload, and report timings in assertion messages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 16:02:17 +02:00
f980bd3a4a Review 1.5: synthetic check runner: network surprises are results, AX process never outlives the agent
- An HTTP answer that breaks off while it is read (HttpIOException/IOException) is a
  failed check ("Svaret blev afbrudt, mens det blev læst: …"), not an exception that
  made the check rerun every tick. Other unforeseen network errors (IOException,
  SocketException, HttpRequestException, AuthenticationException) become
  "Forbindelsen fejlede: …"; one that hits the check's deadline is "Intet svar inden
  for N sekunder". DNS: an invalid name (ArgumentException) is a failed lookup.
- The HTTP "Adresse" detail (a URL may carry query secrets) and the AX "Object server"
  detail are Sensitive, so the API redacts them.
- SyntheticCheckRun's compiler-generated ToString no longer prints the SQL password.
- ProcessRunner (AX Business Connector logon): the PowerShell process tree is killed in
  a finally whenever it has not exited, also when the agent stops (before: only on the
  check's timeout, leaving an orphaned process and AX session). Output and errors are
  capped at 64 K characters each; the rest is read and dropped so the process never
  blocks on a full pipe.
- Tests: broken body, password redaction, bounded output, killed on agent stop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 16:01:47 +02:00
37d3d4b5ec Review 1.5: a check sent back from API v1 with [udeladt] login or URL query is refused, not saved
GET returns the SQL login and the URL query redacted; a client that PUTs it back unchanged
would otherwise replace the real values.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:54:38 +02:00
18cd879dbb Review 1.5: server and check pages: one overview query, paused checks are not run, bad numbers refused, seconds kept
- /servere reads the overview in one query and says which paused servers and checks are left out of the active
  findings; admin notes use InstanceSetup.WhoMayChangeText as main.
- The check form refuses a port, interval, timeout or limit that is not a number (Danish message) and clamps
  minutes before multiplying; an interval set to part of a minute through the API is shown and kept in seconds.
- "Kør nu" is refused for a paused check (message, no button); ?koersel= shows that run.
- A finding of a removed server or check does not link to a missing page and says why it is not active.
- The forecast text uses the history the forecasts were computed from; the grant script text matches the new
  script (-WhatIf first, -WinRM, -EnableFirewall -AgentAddress, -Remove only what it added).
- HTTP addresses are at most 2000 characters.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:53:26 +02:00
20829890e9 Review 1.5: API v1 redacts the agent's account, logins, user names and URL queries; history, event log metadata, paging
- The Sensitive helper (Value/Text) is shared (Api/Sensitive.cs) and gains ServerText ("Agentens konto (…)" and the
  test's account), Url (query left out), ServerTest and CheckResult.
- Windows servers and checks: CreatedBy/UpdatedBy, RequestedBy, LastError, area details, rule-state reasons, the
  check's SQL login and an HTTP address's query (settings.url and target) are "[udeladt]" unless
  ApiIncludesSensitiveEvidence is on. The grant script endpoint then returns the shipped template, Account
  "[udeladt]" and a note without the service account.
- GET /agent-requests/{id} rewrites ServerTest results (account and texts) and SyntheticCheckRun results (failure
  message, sensitive details) like ResultJson does for connection tests, and names serverId/checkId.
- Tests and runs reuse a request already waiting (said in the descriptions); running a paused check is 409.
- GET /server-findings: limit (max 500) and continuation with X-Continuation, evidence's sensitive flag, status,
  scopePaused/scopeRemoved; GET /server-findings/{id} has history. GET /windows-servers/{id}/events returns
  { capturedAt, method, ignored, coverage, events }. The server list/one server has latest, worstDisk, soonestFull
  and findings per severity; checks have findings per severity.
- Admin texts as main (AdminGroups, else the local Administrators); service lists trimmed and deduplicated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:53:26 +02:00
1c9d24a6a2 Review 1.5: repository: reuse waiting agent requests, active findings only on monitored servers and checks, cleanup on removal
- AddServerRequestAsync returns a ServerTest/SyntheticCheckRun already waiting for the agent (Pending, same kind and
  target) instead of queueing another one, like AddAgentRequestAsync; AgentRequestRow carries TargetId.
- Active server findings and the overview counts leave out removed servers and checks, and paused ones across all
  (a paused one's own list keeps its latest findings, as for instances); findings say ScopeRemoved/ScopePaused.
  GetServerFindingsAsync pages with OFFSET/FETCH.
- Removing a server (in the web UI or a SQL Server host by the agent's sync) detaches its synthetic checks in the same
  transaction (ServerId NULL, serviceName out of the settings); check queries only join servers in use.
- Removing a check clears its sealed password, key id, secret error and last message; a late agent result does not
  write to a removed check. (0018: UPDATE on LastMessage for the web login.)
- 0018: settings and service lists as nvarchar(max) (no 500 on long æøå queries); indexes on SampledAt/LastAt/RunAt
  for the retention deletes. Run details that do not fit are trimmed from the end instead of all dropped.
- One query for the overview's latest CPU/memory and disk collection per server.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:53:26 +02:00
53ffc008f0 Review 1.5: SQL Scripts describe the reviewed Windows server and check rules
Headers of SQLI-WIN-001/006/007/008/009/010/012/013 and SQLI-SYN-001 follow the rule
changes (not measurable instead of passed, caps and summary findings, hourly 24-hour
counts, per-user and updater tasks, neutral check titles); SQLI-WIN-010's severity
gains Low when the event log cannot be read (also in README.md).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:45:44 +02:00
7e66dc94c4 Review 1.5: server and check rules never pass unmeasured, cap and stabilize findings, keep sensitive text out of titles
- SQLI-WIN-006 is "ikke målbar" (with the forecaster's note) until a volume has enough
  history; titles "Drev X er fuldt (om ca. N dage | inden for et døgn)", as SQLI-WIN-005.
- SQLI-WIN-007/008: the startup grace period is "ikke målbar" instead of passed, so
  open findings are not resolved and reopened at every restart. One matcher for both
  rules (name or display name, prefix*). SQLI-WIN-007 is capped by
  Services:MaxFindings (default 15, 1-100) with one summary finding for the rest.
- SQLI-WIN-009: stable order (category, oldest first, key) and one summary finding
  over MaxFindings (now bounded 1-100).
- SQLI-WIN-010 only says "uden tegn på at det skete uventet" when the event log was
  read past the boot; before that "ikke målbar", and Low "årsagen kan ikke ses" when
  the log cannot be read.
- SQLI-WIN-012/013: Tasks:IgnoreUserAndUpdaterTasks (default true) leaves out per-user
  and updater tasks; TaskThresholds.Watches is the one filter for the agent and the
  rules. A SID is never in a title or subject, only in sensitive evidence.
- SQLI-SYN-001: neutral title "{navn} fejler ({n} gange i træk)", the failure message
  only in sensitive evidence; "mindst N" and "... eller tidligere" when every result
  given failed; "findes ikke" for a missing service; evidence "Tjenestens tilstand".
- SQLI-SYN-003: "udløber inden for et døgn" / "om 1 dag".
- Agent (minimal): the task filter is TaskThresholds.Watches, and performance samples
  get the configured interval.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:45:44 +02:00
2345b199fa Review 1.5: disk forecasts, 24-hour event counts, performance gaps and per-user tasks in Core
- DiskForecast.EnoughHistory: false for "ingen målinger endnu" and "for lidt historik",
  so a rule can say "ikke målbar" instead of "fyldes ikke op". Days are counted from
  now (a last point older than its hour is subtracted); FullAt takes the time the
  forecast was made; Describe says "fuld nu" at 0 days.
- ServerEventGroup.Buckets: counts per clock hour, and Recent sums only the hours with
  events in the last 24 hours (FirstAt within the window), so a recurring error no
  longer adds up from when it was first seen. Older groups without buckets still merge.
- ServerPerformanceMath.Sample takes the interval: after a gap of more than three
  intervals CPU and pages/s are null rather than the gap's average stamped now.
  Commit percent is computed in floating point.
- TaskResults.IsUserOrUpdaterTask (a user's SID in the name, OneDrive/Edge/Google
  updaters, User_Feed_Synchronization), HasUserSid and Display (SID replaced by a
  short hash for titles and subjects).
- Tests: forecast from real history, irregular sampling, identical timestamps,
  a shrunk volume, days from now; an error every ten hours over three days; a
  sample after a gap.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:45:44 +02:00
f27a060e4f Review 1.5: docs: what Windows servers and synthetic checks store, the stricter SQL check, no hub or PDF yet
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:33:04 +02:00
697d243658 Review 1.5: grant-windows-server.ps1 backs up, removes exactly what it gave, and never more than read
The grant script changes SCM and WMI security on customer servers. Before, a re-run
replaced every ACE of the account on a WMI namespace (also a customer's Deny), -Remove
took the account out of groups and ACEs it had before, only the SCM SDDL was backed up
(and never used), Remote Management Users also opened PowerShell Remoting, and
-EnableFirewall narrowed Windows' built-in rules for every other tool and was never undone.

- Checks everything before changing anything: elevation, the account resolves (broad
  groups refused), Deny ACEs stop the script, -EnableFirewall needs -AgentAddress.
- Backs up the original SDDLs (SCM, WMI namespaces, WinRM), group memberships and
  firewall rules before the first change; records what it added in a state file in an
  administrators-only folder; -Remove removes only that.
- SDDL handled with RawSecurityDescriptor (GetSD/SetSD, sc sdshow/sdset): exactly one
  ACE added in canonical order, verified before writing and after; aliases (NS) work.
- No Remote Management Users: -WinRM adds GENERIC_READ (no Execute) to WinRM's RootSDDL;
  DCOM is the default.
- Firewall: its own "SQLInsight Agent: ..." rules only from the agent's address,
  removed again with -Remove; Windows' rules untouched.
- -WhatIf, groups by SID, Danish messages, PowerShell 2.0 syntax.
- ServerGrantScript.For does not fill in an account that could end the PowerShell
  string (typographic quotes, control characters); tests run the SDDL logic in
  Windows PowerShell against real default SDDLs.
- docs/windows-servere.md: rights, safety net, firewall.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:31:49 +02:00
cb99778a3f Review 1.5: SQL Scripts for Windows servers and synthetic checks follow main's structure and registry
After the rebase onto 1.4 the 16 SQLI-WIN/SQLI-SYN scripts had their own /* */ format,
so SqlScriptTests (rule ids, review header, -- Kilde: blocks) failed.

- The scripts have main's header (Regel-id, Titel, Alvor, Versioner, Vurdering,
  Grænseværdier, Rettigheder, Data kommer fra, Regelkode, Read-only); the Windows
  WQL/XPath stays word for word in comments.
- The synthetic SQL check's SQL is registered (SyntheticCheckRunner.UseDatabaseSql,
  BeginTransactionSql, RollbackSql, SyntheticCheckSettings.DefaultQuery) and shown
  as -- Kilde: blocks in SQLI-SYN-001/002 and _collectors/SyntheticCheckRunner.sql.
- SqlScriptTests know ServerRuleCatalog; only those rules may be without SQL blocks.
- ServerScriptTests: every CimQueries query/filter is in a SQLI-WIN script word for word.
- The SQL check cancels the rest of a large answer instead of draining it on close.
- Rule counts: README (108 built-in: 92 SQL Server + 13 Windows + 3 synthetic),
  SQL Scripts/README (rule table, 124 rules, Windows/synthetic section), RuleCatalogTests,
  CONTRIBUTING (how to add a server rule).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:18:24 +02:00
8d092b6a35 Review 1.5: the read-only check of typed queries can no longer be bypassed
The synthetic SQL check (and custom rules) used a regex that ended an [identifier]
at the first ] and a block comment at the first */, so "[x]]'] DELETE ..." hid a
DELETE inside what it took for a string. T-SQL also needs no semicolon between
statements, so "SELECT 1 COMMIT" or "SELECT 1 WRITETEXT ..." passed.

- ReadOnlyQuery lexes T-SQL as SQL Server does (nested comments, '' ]] ""),
  refuses unterminated comments/strings/identifiers, every statement keyword
  (COMMIT, ROLLBACK, SET, DECLARE, WRITETEXT, UPDATETEXT, RAISERROR, CHECKPOINT,
  ENABLE/DISABLE, RECEIVE/SEND ...), NEXT VALUE FOR (sequences are not rolled
  back) and update/exclusive lock hints, and a second SELECT unless joined by
  UNION/EXCEPT/INTERSECT.
- CustomRuleDefinition.QueryProblems uses it, so the synthetic check validates the
  same way when saved and before it runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:08:34 +02:00
3edc69b48f Servers tests: give the local CIM test room when the whole suite runs at once
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:59:22 +02:00
4c2d5252ad Docs: Windows-servere og syntetiske tjek (1.5)
- docs/windows-servere.md: what is collected and how often, how the agent reads
  servers (WS-Man, DCOM fallback, event log over MS-EVEN6), the exact least-privilege
  rights and grant-windows-server.ps1 (never local administrator), known limits,
  "ikke målbar" with the exact reason, firewall ports, the disk forecast, event
  noise filtering, rules with their thresholds, retention, configuration and API.
- docs/syntetiske-tjek.md: the kinds, the AX logon (port, then .NET Business
  Connector in a separate process, or clearly only the port), service running but
  not answering, sealed SQL passwords, rules, firewall, configuration and API.
- Linked from installation, integration (API table) and databehandling (retention,
  nothing sent to the hub); on the website in the documentation index, and the rule
  list shows the Windows server and synthetic check rules in their own areas.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:59:22 +02:00
063243532d Web: style the field groups of the synthetic check form
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:59:03 +02:00
1c18bcfab8 Web: Windows-servere and Syntetiske tjek pages, and the same in API v1 (scope config:write)
- Menu group "Servere" with Windows-servere (/servere) and Syntetiske tjek
  (/syntetiske-tjek).
- /servere: every server with status (overvåges, delvist målbar, kan ikke læses),
  CPU, free memory, the fullest disk and the nearest "fuld om ca. N dage", findings;
  add application/AOS/file servers. /servere/{id}: findings and what is not
  measurable and why, CPU/memory for the last day, disks with forecasts, watched
  and stopped services, grouped event log errors, scheduled tasks, rights per area
  with the latest test, the least-privilege PowerShell script (copy or download,
  UTF-8 with BOM), role and service lists, pause and remove. /servere/fund/{id}.
- /syntetiske-tjek: checks with status and response time; one form for every kind
  (TCP, HTTP(S), SQL login + query, DNS, AX logon) with an optional server and
  service; a SQL login password is sealed for the agent at once and never shown.
  /syntetiske-tjek/{id}: latest result (incl. "kun porten er testet"), findings,
  response times, history, edit, run now, pause, remove.
- API v1: /windows-servers (+ test, grant-script, performance, disks, services,
  events, tasks, rule-states), /server-findings, /synthetic-checks (+ run, results,
  rule-states); all in OpenAPI. Passwords are write-only; event samples, failure
  messages and sensitive evidence are "[udeladt]" unless
  SQLInsight:Web:ApiIncludesSensitiveEvidence is on. /api/v1/rules lists the new rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:52 +02:00
842286d57d SQL Scripts for the Windows server and synthetic check rules (SQLI-WIN-001..013, SQLI-SYN-001..003)
One reviewable file per rule id with a Danish header: what and why, thresholds
with config keys and defaults, severity, required rights (never local
administrator), supported versions, collector file and member, and the
evaluation in plain Danish. The Windows rules use no SQL: their exact CIM/WQL
queries, event log XPath and a read-only PowerShell snippet to verify them are in
a comment block. SQLI-SYN-001/002 carry the exact SQL a SQL check runs (session
settings, transaction that is always rolled back, SELECT 1). A test checks every
server and check rule has its file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:36 +02:00
6eae719e95 Agent: monitor Windows servers and run synthetic checks; server tests and check runs on request
- ServerMonitoringService keeps the SQL Server hosts in step with the monitored
  instances (SQLInsight:Servers:IncludeSqlHosts), picks up servers added, paused or
  changed in SQLInsight Web within a minute, and runs each server's due areas with
  a limit on parallelism. Default intervals: 1 min CPU/memory, 15 min disks and
  tasks, 5 min services and events (SQLInsight:Servers:Intervals).
- ServerMonitor: one CIM session per cycle; per-minute samples, disk samples and
  the "fuld om ca. N dage" forecast from the stored history, grouped events (read
  from where the last read stopped), services and tasks. A missing right makes
  only that area "ikke målbar" with the exact reason, logged once per change; a
  server that does not answer is notified once, and again when it answers.
  Survives an agent restart (samples, boot time, event position).
- SyntheticCheckService runs checks on their interval, opens a SQL login password
  sealed for the agent (AgentCredentialKey.OpenContext), looks up the linked
  Windows service when a check fails, stores results and findings, notifies with
  links to /servere/fund/{id}.
- AgentRequestService hands ServerTest and SyntheticCheckRun to ServerRequestHandler.
- appsettings.json: SQLInsight:Servers and SQLInsight:SyntheticChecks with every
  threshold, interval and retention.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:36 +02:00
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
25ecdbc3e7 Repository: Windows servers, samples, events, synthetic checks and their findings (0018)
Migration 0018_windows_servers_synthetic_checks:
- insight.WindowsServers (SQL Server hosts kept in step by the agent, servers added
  in SQLInsight Web), ServerPerformance (per minute, one row per sample),
  ServerDiskSamples (forecast history), ServerEvents (grouped like ErrorLogEvents),
  ServerSnapshots (latest per area).
- insight.SyntheticChecks (SQL login password sealed for the agent, as in 1.3) and
  SyntheticCheckResults.
- insight.ServerFindings and ServerRuleStates, scoped to a server or a check.
- AgentRequests.TargetId and the kinds ServerTest and SyntheticCheckRun.
- SQLInsight Web gets INSERT and column UPDATE only; host, name and kind are fixed,
  nothing the agent records can be written, no DELETE.

Findings are opened, updated and resolved like instance findings; a round where a
rule is not measurable keeps what was found open. Retention for samples, disk
history, events, check results and resolved findings (RunServerMaintenanceAsync).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:18 +02:00
ba7c751b5c Rules: Windows servers (SQLI-WIN-001..013) and synthetic checks (SQLI-SYN-001..003)
Scoped rules with their own context (a Windows server, a synthetic check) that
produce the same findings and outcomes as the instance rules; missing data is
"ikke målbar" with the collector's reason. Thresholds are settings
(SQLInsight:Servers, SQLInsight:SyntheticChecks).

- Sustained CPU, low memory (share and MB), paging with low memory, commit charge.
- Free disk space and "fuld om ca. N dage" forecasts.
- Stopped automatic services (minus those that stop by design, and a grace
  period after a restart) and watched services that must run.
- Windows event log groups: important kinds at once, ordinary errors when they
  repeat, capped per server; sample messages are sensitive.
- Unexpected restart (Kernel-Power 41, EventLog 6008, BugCheck), optional uptime.
- Scheduled tasks that failed or did not run as planned.
- Synthetic checks failing (twice in a row), "svarer ikke, selv om tjenesten
  kører", slow answers, TLS certificates about to expire.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:18 +02:00
ae20954a91 Windows servers and synthetic checks core: data, disk forecast, event dedup, grant script
- Core/Servers: what the agent collects from a Windows server (CPU and memory from
  raw counters, disks, services, events, scheduled tasks), roles and sources, host
  validation and the computer behind a SQL Server data source.
- DiskForecaster: "fuld om ca. N dage" from a least-squares line over 14 days of
  SQLInsight's own samples, only since the volume last changed size, with sanity
  bounds (history, samples, R², max one year).
- ServerEventAnalysis: noise filter, grouping and a 24-hour window for System and
  Application events, like the SQL Server error log in 1.2.
- db/permissions/grant-windows-server.ps1: least-privilege rights for the agent's
  account (groups, WMI namespaces, SCM read), never local administrator; filled in
  by ServerGrantScript.
- Core/SyntheticChecks: kinds, settings, validation (read-only SQL), results.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:17 +02:00
90d37be8a5 Version 1.4.0
Some checks failed
deploy community / deploy (push) Successful in 18s
deploy hub / deploy (push) Successful in 19s
CI / Build og unit tests (push) Successful in 8s
CI / MSI-installer (push) Successful in 15s
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:45:31 +02:00
31fa89b385 Review 1.4: AX detection survives passing errors; revoke only what the grant gave; smaller fixes
- A lock timeout or deadlock while detecting (e.g. during an AX table
  synchronisation) dropped the database from AX monitoring for an hour, so
  its findings resolved and reopened. The earlier detection is kept and
  retried within 5 minutes. A cancelled collection is no longer reported as
  "læsningen fejlede".
- A lock timeout kept the earlier batch picture forever; now at most 15
  minutes (MeasuredAt), so a job that has started is not reported late from
  an old picture.
- revoke-dynamics-ax-read.sql revoked SELECT on same-named tables in every
  schema, also a DENY set by a DBA. It now finds the AX schema like the grant
  script and revokes only granted SELECT there.
- SQLI-AX-006 skips blocked sequences (as -005/-008 do); forecasts under a
  day say "inden for et døgn" instead of "om ca. 0 dage".
- Integration tests: the SQL Scripts run as-is in main's format, and the
  shared connection has the agent's session settings after the AX reads.
- docs/dynamics-ax.md: history bounds, lock-timeout window, session settings,
  /detection-problems, API redaction.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:45:31 +02:00
13f22c7541 Review 1.4: AX batch rules: one finding per job, stuck jobs only once, no crash without a start
- Axapta 3.0/4.0 keep a BATCH row per recurrence, so a failed run and the
  next waiting run share a job key and gave two SQLI-AX-001 findings with the
  same subject (the repository kept the last, the hub the first). The batch
  rules now keep one finding per subject, the most severe.
- SQLI-AX-004 treated a single earlier 10-hour run as "normally that long"
  and stayed silent; it now needs BatchHistoryMinimumRuns runs, like
  SQLI-AX-002. A stuck job is SQLI-AX-004's finding only, not also -002's.
- Running(now)!.Value threw for an executing job with neither a start time
  nor a first-seen time (and a negative duration meant clock skew); such jobs
  are skipped.
- SQLI-AX-010 no longer calls every session in an AX database "from Dynamics
  AX", and shows the raw CONTEXT_INFO as sensitive evidence.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:29:35 +02:00
8365ba5357 Review 1.4: AX pages and API leave out removed instances; bounded, complete reads
- DynamicsAxView (pages and API v1) listed instances removed from monitoring;
  /batch-runs without ?instance= returned runs of every instance. Both now
  only cover the instances the overview shows.
- AX blocking read the instance's newest 200 blocking events and filtered
  for AX afterwards, so other blocking could push AX blocking out entirely.
  GetBlockingInDatabasesAsync filters on the AX databases in SQL (OPENJSON)
  with its own TOP (1000).
- /sequence-samples loaded every sample and cut at 10,000 in memory; the limit
  is in SQL now, with X-Truncated when there are more.
- Two new runs with the same run key in one snapshot would break the
  primary key of insight.AxBatchRuns; they are deduplicated first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:29:34 +02:00
f4d2603181 Review 1.4: AX texts in API v1 and the hub without logins or host names; API has what the pages show
- "Ikke målbar" texts, notes and detection problems carry the GRANT text with
  the agent's login and database user. API v1 (batch-jobs, number-sequences,
  recids, databases, rule-states) returned them as they were, although logins
  are redacted everywhere else. AxReadAccess.WithoutPrincipals replaces them
  with the placeholder unless sensitive values are allowed; the grant script
  also hides a database user that differs from the login.
- SQLI-AX-010 put the host and program name (in 2-tier AX 3.0 the user's own
  PC) in the finding's explanation, which reaches the hub unredacted for an
  opted-in database. They are only in the sensitive evidence now, with the raw
  CONTEXT_INFO; the CONTEXT_INFO parsing says it is not verified against
  customer data.
- A database named e.g. "AX · test" was read from a subject as "AX", whose
  hub opt-in then applied. Middle dots in the database are doubled in AX
  subjects, and the AX community rules' subjects are built the same way.
- API v1 now also has the detection problems (/detection-problems), what each
  raw batch STATUS means, and the thresholds behind the number sequence and
  RecId pages; the blocking explanation is no longer redacted (it holds
  nothing sensitive now).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:29:34 +02:00
66ed4e11b3 Review 1.4: AX 3.0/4.0 local times in the AX time zone, with its DST rules
Axapta 3.0 and AX 4.0 store batch and cleanup times as a local date plus
seconds. They were turned into UTC with the SQL Server's offset at collection
time, so every time from the other side of a DST change was an hour off (a job
planned 01:50 CET on the spring night looked 70 minutes late at 03:00 CEST),
and a SQL Server running UTC behind a CET AOS shifted every time.

AxLocalTime converts with the rules of the AX time zone on that date:
SQLInsight:DynamicsAx:LocalTimeZone (Windows or IANA id), else the agent's own
zone when its offset matches the SQL Server's, else the server's offset (to the
nearest quarter hour) with a Danish data note. The skipped spring hour and the
repeated autumn hour get standard time (deterministic, never throws, never a
false "late"); seconds outside 0..86400 are unknown and 86400 is midnight. The
AX 3.0 cleanup time is local by version, also when LATESTCLEANTIME is missing.
AX 2009+ UTC times are untouched. Also: remaining numbers cannot overflow for
HIGHEST = Int64.MaxValue, and run keys use the invariant culture.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:19:17 +02:00
fdaa7e76bf Review 1.4: the AX batch history watermark no longer gets stuck
After the first read, BATCHJOBHISTORY was read from the watermark minus an
overlap of 2000 RecIds with TOP (1000), oldest first. From AX 4.0 RecIds are
handed out per table and are dense, so once 1000 rows existed in the overlap
every read returned the same old rows: the watermark never moved and new runs
were never read again (every AX 2009/2012 installation with more than ~1000
history rows). The read after the watermark now takes the overlap plus 1000
rows (TOP (3000)), so at least 1000 new runs are read each time. Test with
20,000 dense RecIds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:15:49 +02:00
be039a6f73 Review 1.4: AX reads put the agent's session settings back
AxSql.Reset ran SET LOCK_TIMEOUT -1 and READ COMMITTED after every AX read
(and after the AX detection in discovery). The collectors of an instance
share one connection, so every collector after the AX ones waited on locks
without limit instead of the agent's 5 s, and no longer read uncommitted.
Reset now restores the agent's own settings, from one place
(Core.Instances.AgentSession) that SqlConnectionFactory.SessionSettings also
uses. The AX reads themselves are unchanged: READ COMMITTED (or SNAPSHOT)
with a 2 s lock timeout inside sp_executesql, never NOLOCK.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:14:24 +02:00
3a0bbe22f1 Review 1.4: SQL Scripts for Dynamics AX in the 1.3 structure
The AX scripts written in 1.4 (own header style and a generator) now follow
main's "SQL Scripts" structure: the review header, "-- Kilde:" blocks
registered in SqlScriptSources and checked word for word against the code.

- SqlScriptSources: the shared AX detection (DatabasesSql, LoginSql,
  BuildDetectionSql), companies, batch jobs, batch history (first read and
  from the watermark) and number sequences / RecId for Axapta 3.0, AX 4.0,
  AX 2009 and AX 2012; BlockingCollector split 2008 / 2005, as CONTEXT_INFO
  comes from sys.dm_exec_sessions from 2008 and from sysprocesses on 2005.
- SQLI-AX-001..010 and _collectors/Ax*.sql in main's format; COM-AX-001..007
  with the 1.4 queries, the AX detection and USE [AXDB]; BLK-001,
  BlockingCollector and DatabaseDiscovery updated (CONTEXT_INFO,
  sp_executesql).
- Scripts_are_read_only stays strict (no INTO #temp anywhere) and now also
  checks the batch inside EXEC [db].sys.sp_executesql N'...'.
- SQLINSIGHT_UPDATE_SQL_SCRIPTS=1 (from 1.4) rewrites out-of-date blocks from
  the code; the 1.4 generator test now checks that every AX script shows its
  sources for every AX version and says what is unverified.
- README: SQLI-AX-001..010, the AX collector scripts, 108 rules.
- AxDatabaseDetector: LoginSql and BuildDetectionSql as members, so the
  registry sees them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:11:45 +02:00
08cd741b52 Dynamics AX: notice a GRANT within minutes, one AX lookup per custom-rule run
The detector looks again every 5 minutes at an AX database where the agent
lacks part of the read access, so a GRANT takes effect quickly. The AX SQL
test checks that only standard AX tables are read. The hub end-to-end test
expects no AX rule outcomes without the opt-in. Docs: what 1.5's synthetic
AX checks can reuse.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:36 +02:00
cd72f14883 Docs: Dynamics AX (1.4)
docs/dynamics-ax.md: detection, the optional read access per version, how
AX is read (SELECT only, TOP, 2 s lock timeout or snapshot, never NOLOCK),
the rules SQLI-AX-001..010 with thresholds, batch times and the unverified
AX 3.0 STATUS mapping, forecasts from SQLInsight's own samples, AX users
behind blocking (CONTEXT_INFO, honest for 3.0), the hub opt-in, the API and
how to drop in a real 2009/2012 schema. AX 2009/2012/F&O are marked as based
on Microsoft's documented schema, not verified against customer data.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:36 +02:00
d574057114 Integration tests: fake Axapta 3.0 (bmssa) and AX 2012 R2 databases
Built from the schema files in the SQL Server 2022 container and read as the
least-privilege agent login: detection and discovery, 'ikke målbar' with the
exact GRANT and measuring after it (no customer table readable, no data
changed), the shipped grant/revoke scripts, AX 2012 history by RECID read
once, a locked AX table skipped within seconds, the SQL Scripts run as-is,
repository retention and read-back, and the web pages and API v1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:23 +02:00
e2284657ae Web and API v1: Dynamics AX pages and endpoints
Dynamics AX in the menu (shown when AX databases are recognised): AX-databaser
(version, schema, companies, read access and the exact GRANT per database),
Batchjob, Nummerserier, RecId and AX-brugere bag blokering. The same data in
API v1 under /api/v1/dynamics-ax/ (databases, grant-script, batch-jobs,
batch-runs, number-sequences, recids, sequence-samples, blocking), documented
in OpenAPI, with AX users, hosts, statements and the agent's login redacted
unless ApiIncludesSensitiveEvidence is on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:23 +02:00
3e1cc74f0e Dynamics AX rules SQLI-AX-001..010 and their SQL Scripts
Batch jobs: failed (001), longer than their own history (002), late or never
started (003), stuck in Executing (004). Number sequences: running out with a
forecast from SQLInsight's own samples (005), format shorter than HIGHEST
(006), blocked but still referenced (007), continuous and not cleaned up
(008). RecId running out, flagged far earlier for AX 3.0's 32-bit RecId
(009). AX user behind blocking from CONTEXT_INFO, honest where AX 3.0 cannot
tell (010). Subjects carry the database, so findings follow the hub opt-in.

SQL Scripts/<rule>.sql (and _collectors/) for every AX rule added or changed
in 1.4, incl. COM-AX-001..007: a Danish header plus the exact SQL SQLInsight
sends, generated from the code and checked by SqlScriptTests (rewrite with
SQLINSIGHT_UPDATE_SQL_SCRIPTS=1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:23 +02:00
36ad4c0656 Repository and agent: Dynamics AX history (migration 0017), seeding, hub opt-in
0017_dynamics_ax adds insight.AxBatchRuns (completed batch runs, EventDays
retention) and insight.AxSequenceSamples (one NEXTREC/NEXTVAL sample per
sequence per day, HourlyDays retention). The agent reads them back after a
restart so job comparisons and forecasts continue, takes its thresholds from
SQLInsight:DynamicsAx, and only sends findings of the built-in SQLI-AX rules
to the hub for databases with the AX opt-in ticked; AX rule reasons (which
can name tables, logins and GRANT text) are replaced by a fixed text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:53:34 +02:00
6b945295d0 Dynamics AX collectors: shared detector, batch jobs, number sequences and RecId
dynamics-ax (hourly) lists the monitored AX databases with version, schema,
companies and read access; discovery and the AX community rules (now marked
dynamicsAx) use the same detection. dynamics-ax-batch (5 min) reads BATCH
(3.0/4.0) or BATCHJOB/BATCHJOBHISTORY (2009+), keeps each job's own run
history; dynamics-ax-sequences (hourly) reads NUMBERSEQUENCETABLE and
SYSTEMSEQUENCES and forecasts from SQLInsight's own daily samples. All reads
run through [db].sys.sp_executesql under read committed with a 2 s lock
timeout (snapshot when allowed), bounded with TOP, never NOLOCK, never
written. Blocking now captures CONTEXT_INFO for the AX user mapping.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:53:34 +02:00
4ebae4efe6 Dynamics AX core: central detector, per-version schema adapters, read-access GRANT
One place recognises AX (version 3.0/4.0/2009/2012/2012 R2/F&O, schema owner,
model store) from metadata only. Per-version adapters hold all table/column
knowledge, check every column in sys.columns before use and degrade to
'ikke målbar' with the exact column or the exact GRANT text. Only AX 3.0 is
verified against a real customer schema; 2009/2012 are written from
Microsoft's documented schema (docs/dynamics-ax/*.documented.tsv, replaced
by a real export when one is dropped in). grant-dynamics-ax-read.sql is
version- and schema-aware (bmssa) and covers exactly the adapters' tables.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:52:58 +02:00
66aa47ecb3 Deadlocks: read system_health files without a temp table
The 2012+ query used SELECT ... INTO #system_health, a write to tempdb.
One SELECT with ROW_NUMBER() now returns the deadlock rows and the newest
row (the read position) in the same pass. SQL Scripts and the read-only
test no longer allow any exception.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:47:31 +02:00
631edc2adc Review 1.3: SQL Scripts for database discovery and the permission check
Some checks failed
deploy community / deploy (push) Successful in 15s
deploy hub / deploy (push) Successful in 19s
CI / Build og unit tests (push) Successful in 7s
CI / MSI-installer (push) Successful in 17s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Registers 1.3's SQL in SqlScriptSources (DatabaseDiscovery: databases 2005/2000
and the Dynamics AX signature per database; PermissionCheck: login/sysadmin, the
HAS_PERMS_BY_NAME checks for 2005-2012, 2014-2019/MI and 2022+, databases without
access) and adds _collectors/DatabaseDiscovery.sql and PermissionCheck.sql with
Danish review headers. The constants are renamed *Sql and the login query moved to
PermissionCheck.WhoSql so the registry test finds them. The scripts of the
collectors that now skip unchosen databases say so (their SQL is unchanged).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:26:16 +02:00
473dccafff Version 1.3.0
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:43 +02:00
12cf5a1e94 Review 1.3: a request that fails in an unexpected way is completed as failed, not left Running
HandleAsync only turned a few exception types into the request's error; anything
else (a lock or command timeout, a JSON or argument error) escaped, leaving the
row Running and the page waiting until housekeeping failed it minutes later.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:43 +02:00
a6a72661e7 Review 1.3: a routine discovery that could not be stored is not retried every 2 seconds
When saving a discovery failed in the repository (deadlock victim, lock timeout,
connection), LastDiscoveryAt stayed unset and the next round 2 seconds later
listed the same instance's databases again, loading the customer's server. The
agent now waits at least 15 minutes between routine discoveries of an instance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
f0c5d01294 Review 1.3: the overview works before the agent has migrated to 0016
GetInstancesAsync now reads insight.ConfiguredInstances only when it exists, so a
1.3 web UI (e.g. upgraded first on its own server) still shows the overview
against a repository a 1.2 agent has not migrated yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
a66e3f2b3b Review 1.3: upgrade note on AX findings to the hub (now opt-in per database)
1.2 sent Dynamics AX findings to the hub with the rest; 1.3 sends them only for
databases where AX hub sharing is ticked, default off, so sharing stops at the
upgrade until it is turned on. docs/opdatering.md gets an upgrade section that
says so first, with the other 1.3 changes, and the release text points to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
f5ff42ca83 Review 1.3: an unreadable agent key file is kept, not overwritten
LoadOrCreate replaced a key file it could not decrypt, which made the old key
unrecoverable even when only the machine (DPAPI) had changed and the file could
have been put back. The file is now moved aside as credential-key.bin.unreadable-*
and the RSA object of the failed import is disposed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
356e102126 Review 1.3: redact logins and user names in the configuration API
API v1 returns logins and people's names as [udeladt] unless
ApiIncludesSensitiveEvidence is on, but /monitored-instances and /agent-requests
returned the SQL login, createdBy/updatedBy/requestedBy/selectionChangedBy, the
login the agent arrived as and 'Login failed for user ...' texts, and the GRANT
script filled in with the login. They are now redacted the same way (the GRANT
script falls back to the shipped template). ChooseAsync also answers 404 for an
unknown instance instead of 422.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
cade91aa4a Review 1.3: a request still waiting for the agent is reused, not queued again
Pressing Test forbindelsen repeatedly, or a script calling the API in a loop,
added a row per call to insight.AgentRequests, each run by the agent in turn. The
same pending request (kind, instance, parameters) is now returned instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
2adeef4597 Review 1.3: local Administrators may change the configuration until AdminGroups is set; MSI property ADMIN_GROUPS
With SQLInsight:Web:AdminGroups empty nobody could add the first server. The
installer now takes ADMIN_GROUPS (';'-separated) and writes it to the web's
sqlinsight.ini; while the setting is empty, members of the web server's
BUILTIN\Administrators (S-1-5-32-544 among the user's group SIDs; a remote
Negotiate login has the full token, a UAC-filtered deny-only SID does not count)
may change it. The page says who may.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
8e1b877fda Review 1.3: overview leaves out removed instances and marks paused ones
The overview kept listing removed and paused instances with their last data and
counted them in the totals and alerts. Removed instances now disappear from the
overview, Alle fund, /api/v1/instances and /metrics (their history stays for
reports and the instance page); paused ones are shown as "Sat på pause" and
are left out of the totals, the urgent findings and the alerts (Prometheus gets
sqlinsight_instance_paused instead of instance_up = 0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
4c65248727 Review 1.3: an instance removed from the configuration file is marked removed, not deleted
The agent deleted ConfigFile rows no longer in the file, and with them (cascade)
the pause and the database choices: commenting an instance out for a restart lost
them. It is now soft-deleted like a Web instance and brought back, choices and
all, when it returns to the file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
01c04ac95a Review 1.3: database choice and discovery take turns (sp_getapplock)
The web's database choice and the agent's discovery both update many rows of an
instance's insight.ConfiguredDatabases in one transaction, in different orders,
which can deadlock. Both now take an application lock per instance first, as
the finding saves do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
29e1909e98 Review 1.3: one implementation of a service account's SQL login
RepositorySetup.LoginFor/IsLocal duplicated Core's ServiceAccounts, which the web
uses for the GRANT script. Setup now uses ServiceAccounts too, so the login the
installer grants and the one the web shows cannot drift apart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
5ad9355130 Docs: Servere og databaser (1.3)
New docs/servere-og-databaser.md (on the community site too): adding servers,
who may change it (AdminGroups), connection test and GRANT script run by the
agent, database choice, configuration file servers, how SQL login passwords are
sealed and why, the repository grants, and the API. Installation, integration
(scope config:write) and data processing (AX findings to the hub are off by
default) point to it; AdminGroups in the web's appsettings.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
f3ef0cbec7 Web: Servere og databaser, and the same in API v1 (scope config:write)
A page under Overvågning lists the monitored servers (from SQLInsight Web and,
read-only, from the agent's configuration file), adds a server (name\instance,
optional port, Windows via the agent's account or a SQL login, encryption),
pauses, resumes and removes it, and finds instances via the agent (SQL Server
Browser). Each server's page shows the agent's connection test with the rights
it has and lacks, the least-privilege GRANT script for the login the agent
arrives as, and the discovered databases (state, size, recovery model,
compatibility level, Dynamics AX) to tick for monitoring, with "overvåg
automatisk nye databaser" and AX findings to the hub per database. Pages
refresh while the agent works on a request.

Only SQLInsight:Web:AdminGroups (new) may change anything; others see it.
SQL login passwords are sealed with the agent's public key on arrival and
never shown or returned; a new server or login needs the password again.

API v1: /monitored-instances (list, add, get, patch pause/monitor-new,
connection, delete), …/connection-test (start, latest with missing
permissions), …/discovery, …/databases (read, choose), …/grant-script,
/instance-discovery and /agent-requests/{id}. Changes need the new token scope
config:write, or AdminGroups for Windows users.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
81b8376026 Agent: instances from the repository, request queue, sealed passwords, AX hub opt-in
The agent mirrors SQLInsight:Instances into the repository at start (read-only
"fra konfigurationsfil" rows, every database monitored as before) and reads the
configuration on every tick: instances added, paused, removed or changed in
SQLInsight Web, and database choices, take effect without a restart. If the
repository cannot be read before the first load, the configuration file alone
is used.

AgentRequestService runs connection tests (with the permission check), database
discovery and instance discovery (SQL Server Browser, registry) as the agent's
own identity, and refreshes every instance's database list hourly. The agent
keeps an RSA key (DPAPI LocalMachine, restricted folder) and publishes its public
key and identity; SQL login passwords sealed by the web UI are opened only here.

Dynamics AX findings and AX rule outcomes are sent to SQLInsight Hub only for
databases where it is turned on (default off).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
b2fa78411f Collectors: monitor only chosen databases; database discovery and permission check
CollectorContext carries the instance's DatabaseSelection; the database,
index, Query Store and community-rule collectors skip databases that are not
chosen (system databases always run). DatabaseDiscovery lists every database
with state, size, recovery model, compatibility level and access, and
recognises Dynamics AX with the same signature as the AX rules, through
three-part names only (2000 and 2005+ variants). PermissionCheck reports which
rights from grant-least-privilege.sql the login has (HAS_PERMS_BY_NAME,
version-aware; not possible on 2000).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
1199b1aa86 Repository: configured instances, discovered databases, agent request queue (0016)
insight.ConfiguredInstances (Web or ConfigFile source, soft remove, sealed
password), insight.ConfiguredDatabases (discovery result, monitored, AX to hub),
insight.AgentRequests (queue for connection tests and discovery run by the agent)
and insight.AgentKeys (the agent's public key and identity). The web login gets
INSERT on ConfiguredInstances and AgentRequests and UPDATE on named columns only;
no DELETE, no Source/Name change, nothing the agent records.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:21:59 +02:00
8776faefb3 Instance configuration core: database selection, sealed credentials, agent request contracts
DatabaseSelection decides which databases the database-specific checks run in
(system databases always), and which may send Dynamics AX findings to the hub
(default none). CredentialEnvelope seals a SQL login password with the agent's
RSA public key (RSA-OAEP-SHA256 + AES-256-GCM), bound to server and login, so only
the agent can open it. Shared contracts for connection tests and discovery, server
address validation, the service account login rule, and the least-privilege GRANT
script filled in for one login.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:21:59 +02:00
2284e7f082 SQL Scripts: one reviewable script per rule
A DBA should be able to review every statement SQLInsight sends to a
monitored server before granting it access. "SQL Scripts/<rule-id>.sql"
holds, per rule (82 built-in, 16 community), a Danish header (what and
why, how C# judges the data, thresholds with config keys, severity,
exact GRANTs, versions, collector file and member) and the collector's
SQL verbatim, every version variant as its own batch.
"_collectors/<Collector>.sql" holds each collector's queries once.

The only statement that is not strictly read-only is documented in the
scripts: the deadlock collector's session temp table (#system_health).

Inline SQL in the collectors moved to internal constants and Build*Sql
methods (no text changed), so SqlScriptTests can check that the scripts
still contain the SQL the code sends, that every rule has a script, that
every collector query is registered, and that nothing writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:20:06 +02:00
28bd148691 Version 1.2.0
Some checks failed
deploy community / deploy (push) Successful in 16s
deploy hub / deploy (push) Successful in 23s
CI / Build og unit tests (push) Successful in 6s
CI / MSI-installer (push) Successful in 15s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:47:20 +02:00
d9e3b4ee6d Review 1.2: read only the default trace's current file between rollovers
With ALTER TRACE the error log collector read every default trace file
(up to 5 x 20 MB) every 5 minutes. While the current file is the one read
last time, everything since then is in it, so only that file is read;
after a rollover (and the first time) all files are read once as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:38:34 +02:00
8e551bcaa6 Review 1.2: document the new collection limits
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:35:54 +02:00
3f2e959f18 Review 1.2: list the new API v1 endpoints in the integration docs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:35:41 +02:00
d070cd872b Review 1.2: scan the version store for its generators at most hourly
sys.dm_tran_top_version_generators reads the whole version store. It ran
every 5 minutes while the version store was over half the threshold,
i.e. exactly when it is large and expensive to scan, and a timeout failed
the whole tempdb collection. It now runs at most once per hour (the
previous answer is kept in between, with its time) and a failed scan
keeps the previous answer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:35:00 +02:00
4ceb64f2d5 Review 1.2: index usage after a counter reset without a restart
sys.dm_db_index_usage_stats also starts over when a database is taken
offline, closed by AUTO_CLOSE or (2012/2014) an index is rebuilt. The
delta was then clamped to 0, so the usage since the reset was lost from
the daily history and the tracked totals. A lower counter now counts as
new usage. IndexInfo.Key also used the literal text "001f" as its
separator instead of the unit separator character.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:34:17 +02:00
a01ec374a4 Review 1.2: keep SQL Server's error message out of the replication reason
When the distribution database could not be read, SQL Server's message
(which names the agent's login) was appended to Unavailable. That text is
the rules' "not measurable" reason and is returned unredacted by
/rule-states and /replication. It is now UnavailableDetail: shown on the
local instance page and [udeladt] in the API unless
ApiIncludesSensitiveEvidence. The agent list is also capped (TOP 500).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:33:25 +02:00
fcf2466738 Review 1.2: bound the activity collector and avoid duplicate rows
The open transactions and long requests had no TOP, so a busy server could
return (and store in the snapshot every minute) thousands of rows with
statement texts. Now at most 100 transactions (large log first, then the
oldest) and the 50 longest requests.

The transactions were joined to sys.dm_exec_requests, which has one row
per request: a MARS session with an open transaction gave duplicate rows,
and the MERGE into insight.LongTransactions then failed on the primary
key every minute. The first request is now taken with OUTER APPLY TOP 1,
and the save keeps one row per transaction.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:32:40 +02:00
a256e4c8cc Review 1.2: say correctly what each error log source shows
The error log page called the default trace "the whole error log" and
said it has every line, and the ring buffer source told SQLI-LOG-002/005
to grant ALTER TRACE, which does not make 833 or stack dumps visible
(they are only in the error log itself). The rules now say that it takes
securityadmin, why SQLInsight does not grant it, and the exact statement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:31:53 +02:00
70934e7562 Review 1.2: error log entries are no longer counted again
The default trace was filtered on whole seconds only, so the newest entry
read (ReadUntil) came back at every collection until a newer one was
written: one 824 became a new event every 5 minutes. Entries at or before
ReadUntil are now left out, as for xp_readerrorlog.

The ring buffer's times were computed to whole seconds from the clock at
each read and moved by up to a second between reads, so the newest entry
could be counted twice. They are now computed to the millisecond with a
small tolerance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:31:00 +02:00
1bbe5eb8eb Plan analysis test: start from an empty plan cache
With the new collectors the full suite fills the plan cache with heavier
statements, so the light probe statements could fall outside the 50
heaviest. The test clears the cache first (test only; SQLInsight never does).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:14:54 +02:00
d27323a1a4 Availability groups: cluster node or witness down (D)
The availability group collector also reads sys.dm_hadr_cluster_members
(empty or unavailable without a WSFC), and SQLI-AG-003 (High) reports a
node or witness that is not up, with how many members are still up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:56:43 +02:00
6cc65e3c75 Index usage over time and never-updated statistics (8g, F)
The index collector now sums index reads and writes between collections
across restarts of SQL Server (and of the agent, by seeding from the
stored snapshot) and keeps them per day in insight.IndexUsageDaily
(migration 0015, HourlyDays). SQLI-IDX-002 judges an index once it has
been followed for 7 days instead of needing 7 days of uptime.
GET /api/v1/instances/{name}/index-usage lists usage per index.

SQLI-STAT-001 also reports statistics that were never updated on tables
with rows; statistic rows now come from the table's heap or clustered
index, as a never-updated statistic has no row count of its own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:55:22 +02:00
473a7d0be3 Replication from the distributor (8f)
On a distributor, a 15-minute collector reads each distribution agent's
latest run (status, latency, message) and replication errors the last
day from the distribution database. That needs SELECT on
MSdistribution_agents, MSdistribution_history and MSrepl_errors, which
the agent does not have by default (never the replicated commands);
without it SQLI-REPL-001 (agent failed/retrying, High) and SQLI-REPL-002
(latency > 5 min, Medium) are "not measurable" with the exact grant, and
"not relevant" elsewhere. Shown on the instance page and in
GET /api/v1/instances/{name}/replication. Tested against a real
distributor set up in the SQL 2022 container.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:48:46 +02:00
66129d99fc Stopped automatic SQL Server services (8e)
SQLI-SVC-001 (High) reports a service set to start automatically that is
not running (SQL Server Agent, Full-text launcher, Launchpad …), from the
sys.dm_server_services data the configuration collector already reads
(2008 R2 SP1+; not relevant before). The service account is sensitive.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:43:26 +02:00
4d18d20ab0 tempdb and version store (8c)
A five-minute collector reads tempdb space (free, version store, user and
internal objects, whether files can grow), the 'Version Store Size (KB)'
counter, the sessions using most tempdb, the oldest snapshot transaction
and, only when the version store is large, the databases generating
versions. Trend and heavy sessions survive restarts; used space and the
version store become metrics with explanation and assessment.

Rules SQLI-TEMP-001 (version store > 1 GB or steadily growing),
SQLI-TEMP-002 (nearly full and cannot grow) and SQLI-TEMP-003 (session
above 1 GB for 15 minutes), configurable under SQLInsight:Tempdb.
"tempdb lige nu" on the instance page; GET /api/v1/instances/{name}/tempdb.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:42:38 +02:00
6f7839d011 Long and large transactions and long-running statements (8b, 8d)
A one-minute collector reads open user transactions (age, log bytes used
and reserved, database, session, login, program, statement) and user
requests running longer than the threshold, leaving out the agent's own
and system sessions, BACKUP/RESTORE/DBCC (long operations) and requests
waiting on purpose. Transactions open a minute or holding 100 MB are kept
in insight.LongTransactions (migration 0014, RawDays).

Rules SQLI-TRAN-001 (open > 10 min, High at 6x), SQLI-TRAN-002 (> 1 GB
log) and SQLI-REQ-001 (statement > 15 min), configurable under
SQLInsight:Activity. Shown "right now" on the instance page and in the
time window analysis and PDF; GET /api/v1/instances/{name}/activity and
the analysis JSON leave logins and statements out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:36:41 +02:00
e6840e40d7 SQL Server error log without elevated rights (8a)
A five-minute collector reads important error log entries from the best
source the agent's rights allow: the error log itself (xp_readerrorlog,
only with securityadmin), the default trace's ErrorLog and Audit Login
Failed events (optional ALTER TRACE), or the exception and connectivity
ring buffers (VIEW SERVER STATE: numbers and severity, failed logins).
Repetitions are summed per error; the watermark is the newest entry
read, because the default trace is buffered. Stored in
insight.ErrorLogEvents (migration 0013, EventDays), resumed after restart.

Rules SQLI-LOG-001..006: corruption 823/824/825, I/O over 15 s (833),
severe errors (17+), login failure bursts (configurable), stack dumps and
AG errors; rules needing the log text are "not measurable" with the
right to grant. Page /instanser/{navn}/fejllog (menu Overvågning) and
GET /api/v1/instances/{name}/error-log with messages redacted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:30:41 +02:00
cb2f8fcbce Version 1.1.0: disk, configuration, privilege audit, wait types, explanations, API v1, foldable menu
Some checks failed
deploy community / deploy (push) Successful in 15s
deploy hub / deploy (push) Successful in 23s
CI / Build og unit tests (push) Successful in 7s
CI / MSI-installer (push) Successful in 14s
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:00:33 +02:00
55fd389d10 Dynamics AX 3.0 reference schema (from a customer's AX 3.0 database)
Table and column names only, no data. Used as the facit for AX 3.0 rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:00:33 +02:00
0dc7866808 Separate "check by hand" from "not relevant" for version-gated rules
A rule whose topic applies but whose way of measuring needs a newer
version or patch now gives the new outcome ManualCheck with concrete
Danish guidance, instead of NotApplicable. It is marked explicitly per
rule (IRule.ManualCheck) and only used when every missing capability is
a measurement capability (InstanceCapabilities.IsMeasurement): CHECKDB
status (SQLI-DB-009), VLFs (SQLI-DB-011), spills and memory grants
(SQLI-PLAN-004/005) and deadlocks on 2005 (SQLI-DLK-001). Query Store,
AG and Azure-managed topics stay "not relevant".

Shown in a visible "Skal tjekkes manuelt" section on the instance page,
a KPI on the overview, a "Tjek manuelt" column in the management
report's controls, manualChecks in /api/v1/instances and manualCheck in
rule-states; not counted as not measurable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:59:50 +02:00
f3551b1bf0 Support cases through the API: create, read and close
- POST/GET /api/v1/support-cases[/{id}] and POST .../{id}/close in
  SQLInsight Web, sharing the /support form's logic: same checks, finding
  prefill without sensitive evidence, Idempotency-Key as the request id,
  202 Accepted with Location, problem+json 403 without the support:write
  scope or a support license. RequestedBy is the Windows user or
  token:<name>.
- Closing: "Luk sag" on /support and the API record CloseRequested
  (migration 0012, column-level UPDATE for the read-only web login); the
  agent sends it to the hub with backoff and sets Closed when confirmed;
  a pending case is closed once it has a number; an older hub list never
  turns CloseRequested back to Open.
- Hub: POST /api/v1/cases/{reference}/close for the license's own cases
  (others 404), idempotent, ClosedBy/ClosedComment (EF migration
  CaseClosedBy). Portal closing records "SEOSoft: <user>"; the customer
  page shows who closed and why; the support report and CSV get "Lukket af".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:47:58 +02:00
30d89acbd7 API v1: findings, rules, reports and page data as JSON, with OpenAPI
- /api/v1/findings gains status (active/resolved/all), from/to, database,
  ruleId, category, sort and paging (limit + X-Continuation header; the
  response stays an array). /findings/{id} with fix script and history.
- /rules (built-in, community, local) and /instances/{name}/rule-states.
- /reports and /reports/{driftsrapport|analyse|rettigheder} as JSON or PDF,
  built from the same models as the pages and PDFs.
- Page data: analysis, top-sql, disks, configuration, privileges (one or
  all instances) and wait types. Statement texts, logins, names and
  service accounts are "[udeladt]" unless ApiIncludesSensitiveEvidence.
- OpenAPI at /api/v1/openapi.json; a test fails when an endpoint is
  missing from it.
- API tokens get scopes ("read", "support:write"); the old form is read
  only, and new-api-token takes --scope. Token names show as token:<name>.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:27:10 +02:00
cc71fb12c6 Menu in foldable groups
The web menu is grouped with details/summary (no script, CSP safe):
Overvågning, Analyse, Konfiguration og sikkerhed, Rapporter, Dynamics AX
(hidden until a database is recognised as AX), Support, System and
Community. The group holding the current page is open, including pages
under /instanser/{navn}/. Per-instance entries go through /vaelg/{side}
(straight to the instance when there is only one). On narrow screens
the whole menu folds behind a "Menu" checkbox toggle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:17:43 +02:00
384e1f154f Long operations, full-text rules and statement activity
- Long operations: a one-minute collector reads operations with progress
  from sys.dm_exec_requests (BACKUP, RESTORE, DBCC, REORGANIZE, SHRINK,
  ROLLBACK): command, database, start, percent, expected end, login.
  Stored in insight.LongOperations (migration 0011, RawDays), shown on
  the instance page, in the time window analysis and its PDF. The last
  10 durations per command and database survive restarts; SQLI-OPS-001
  flags a backup/restore running over twice the median.
- Full-text community rules COM-FT-001..005 (metadata only, 2005 syntax):
  disk-full or paused/throttled catalogs, stale manual/off change
  tracking indexes and disabled indexes.
- Statement activity (CPU time / time in plan cache): column in top SQL
  and SQLI-CPU-002 when above SQLInsight:TopSql:ActivityThresholdPercent
  (default 50) for at least an hour. Never clears the plan cache.
- BuiltInCollectors.Create takes a CollectorSettings record.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:07:29 +02:00
7a908cc28b Explain and assess measurements for the customer
MetricAssessment rates CPU (SQL/other), batch requests, compilations
(against batches), page life expectancy (300 s per 4 GB buffer pool,
average and dips over 50 %, max server memory unset), user connections,
memory grants pending, processes blocked and disk latency as OK, Bemærk
or Reagér with a concrete next step and link, plus a plain Danish
"Hvad betyder det?" for each. Shown under every graph on the instance
page (now all eight metrics) and in the analysis, per drive in the disk
table, and in the analysis PDF.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:57:10 +02:00
20671bfb06 Wait type drill-down with active session sampling
A new light collector samples sys.dm_exec_requests every 15 seconds:
waiting user sessions (not the agent's own) by wait type, database and
statement (same key as top SQL), with blocking. Sampling-only cycles
reuse the last probe and skip rule evaluation. Stored in
insight.ActiveWaitSamples (migration 0010) for RawDays.

Wait types on the instance page and in the analysis link to
/instanser/{navn}/ventetyper/{type}: Danish explanation from a new
knowledge base (shared with the SQLI-WAIT rules, with Dynamics AX notes),
trend over time, the statements that waited with share and estimated
wait, a link to the full text (/instanser/{navn}/sql) and, for lock
waits, to blocking in the same window. SQL Server 2005 compatible.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:50:31 +02:00
4ba7e5195e Privilege audit: sysadmin and server roles with AD unfolding
Collects members of the fixed server roles and CONTROL SERVER every 15
minutes with login type, disabled state and SQL login password policy.
Windows groups are unfolded recursively through Active Directory on a
Windows agent (cached, 20 s timeout, failures shown with the reason).
Changes since the previous collection are logged, and who/when is read
from the default trace (event 108) when the agent has the optional
ALTER TRACE; authors that only reach the buffered trace later are filled
in at the next collection. The first collection is the baseline.

New rules SQLI-SEC-001 (new privilege, High for sysadmin/securityadmin/
CONTROL SERVER) and SQLI-SEC-002 (more sysadmins than the limit, default
3). Names are sensitive evidence and never in titles or subjects.

Web: /instanser/{navn}/rettigheder and /rettigheder with change log by
period, alert banner on the instance page and overview, and an audit
PDF (/rapporter/rettigheder.pdf) linked from Rapporter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:39:56 +02:00
ab8cfcad97 Configuration page with change log
- new collector instance-config: collation, edition, version, services and
  accounts (sys.dm_server_services, 2008 R2 SP1+), Instant File
  Initialization and Lock Pages in Memory (where the build has the
  columns), tempdb files, default paths; plus a fingerprint of the whole
  setup (sp_configure, trace flags, services, version, tempdb files)
- changes are computed against the previous collection, which the agent
  restores from the repository after a restart; migration 0008 keeps them
  in insight.ConfigurationChanges (as long as resolved findings)
- SQLI-CFG-013 "Konfiguration ændret": information for a day when
  sp_configure, a trace flag or a service account changes (account names
  are sensitive evidence)
- page /instanser/{navn}/konfiguration: instance, CPU and memory, services,
  tempdb, trace flags, non-default sp_configure (default vs. in use vs.
  configured) and the change log; linked from the instance page
- the management report lists configuration changes in the period
- trace flag reading shared by trace-flags and instance-config

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:12:26 +02:00
ac13e3d826 Disk speed per drive: IOPS, MB/s and latency
- file-io records each file's drive: sys.dm_os_volume_stats from 2008 R2 SP1
  (new capability VolumeStats, mount points included), otherwise derived
  from the path (D:\..., \server\share\, / on Linux), so 2005 works too
- migration 0007: Volume and IntervalSeconds on FileIoSamples, and
  DiskHourly (rolled up by maintenance, kept HourlyDays)
- DiskActivity sums files per drive: read/write IOPS, MB/s and latency
  weighted by I/O count
- instance page: "Diskhastighed pr. drev" (table and MB/s + latency charts);
  time window analysis and its PDF: per drive against the same hours of the
  four previous weeks
- SQLI-IO-001/002 now report per drive (one finding when the storage is
  slow, the slowest files in the evidence) instead of per file

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:03:11 +02:00
c2cdb58a27 Version 1.0.4: own deadlocks fixed, AX rules find bmssa tables
Some checks failed
deploy community / deploy (push) Successful in 16s
deploy hub / deploy (push) Successful in 18s
CI / Build og unit tests (push) Successful in 7s
CI / MSI-installer (push) Successful in 14s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 07:40:20 +02:00
f362862b33 AX rules: find the AX system tables in any schema (AX 3.0 uses bmssa)
AX 3.0 owns its tables as the database user bmssa (bmssa.SQLDICTIONARY), so
looking only in dbo never recognised a real AX 3.0 database. Detection now
uses sys.tables by name in any schema; later AX versions in dbo still match.
Tested against mock AX 3.0 (bmssa, int RECID), AX 4+ (dbo, bigint RECID) and
a plain database.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 07:40:19 +02:00
25a67cb98b Stop SQLInsight deadlocking itself, and stop reporting its own deadlocks
At a customer whose repository sits on a monitored instance, the agent
reported 12 deadlocks that were its own: two instances' monitors save
findings in parallel, one transaction each, and while insight.Findings is
small SQL Server scans it, so the two lock each other's rows.

- SaveRuleResultsAsync takes an exclusive application lock for its
  transaction (the agent is the only writer; a save takes milliseconds)
  and retries if chosen as deadlock victim anyway
- repository connections are named "SQLInsight Pro (repository)"
- deadlocks where every participant is SQLInsight Pro are not reported
  (the read position still moves past them); any other session keeps it

The new integration test (two instances saving 25 rules x 40 rounds in
parallel) failed 2 of 2 without the fix and passed 5 of 5 with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 07:34:59 +02:00
523 changed files with 56348 additions and 440 deletions

View file

@ -28,6 +28,7 @@ Warnings behandles som fejl, og .NET-analyzers kører på niveauet `latest-recom
| `src/SQLInsight.Contracts` | Wire-kontrakt mellem agent og Hub |
| `src/SQLInsight.Hub` | Centralt API til findings, PostgreSQL (Fase 3) |
| `db/permissions` | Least-privilege-script til agentens login |
| `SQL Scripts` | Ét script pr. regel med præcis den SQL, SQLInsight kører, til gennemgang af en DBA (se [SQL Scripts/README.md](SQL%20Scripts/README.md)) |
| `tests/` | Unit og integrationstests |
## Sådan skriver du en regel
@ -37,16 +38,20 @@ Warnings behandles som fejl, og .NET-analyzers kører på niveauet `latest-recom
3. Et finding skal have **evidens** (de konkrete tal), **forklaring** (hvorfor det er et problem) og **anbefaling** (hvad man gør). Fix-scripts vises for brugeren og køres aldrig automatisk.
4. Skriv unit tests for alle udfald: fundet, ikke fundet, grænseværdier og versioner, hvor reglen ikke kan måles.
5. Registrér reglen i `BuiltInRules`.
6. Tilføj `SQL Scripts/<id>.sql` med den danske header (hvad, vurdering, grænser, alvor, rettigheder, versioner, collector) og collectorens SQL, ordret og med en `-- Kilde:`-linje pr. variant. Brug en eksisterende fil som skabelon. `SqlScriptTests` fejler, hvis filen mangler, eller hvis SQL'en ikke længere er den, koden sender.
En regel om en Windows-server eller et syntetisk tjek (fra 1.5, `SQLI-WIN-…`/`SQLI-SYN-…`) implementerer `IScopedRule<TContext>` i `src/SQLInsight.Rules/Servers/` og registreres i `WindowsServerRules.Create()` eller `SyntheticCheckRules.Create()` (dermed i `ServerRuleCatalog`). Dens `SQL Scripts/<id>.sql` har samme header; forespørgslerne mod Windows står ordret i kommentarer og skal stå i `src/SQLInsight.Servers/CimQueries.cs` (`ServerScriptTests` tjekker det). Agenten må kun læse: CIM-`SELECT`, hændelsesloggen og skrivebeskyttede metoder som Task Schedulers `GetInfoByName`, aldrig start/stop eller ændringer.
### Uden C#: community-regler
Mange regler kan skrives som en SQL-forespørgsel i en JSON-fil under `rules/community/`. De kræver ingen C# og kan afprøves direkte mod en instans med `SQLInsight.Agent rules test`. Se [docs/regler.md](docs/regler.md).
Mange regler kan skrives som en SQL-forespørgsel i en JSON-fil under `rules/community/`. De kræver ingen C# og kan afprøves direkte mod en instans med `SQLInsight.Agent rules test`. Se [docs/regler.md](docs/regler.md). Også en community-regel skal have sin `SQL Scripts/<id>.sql` med forespørgslen ordret (se fx `SQL Scripts/COM-SEC-001.sql`).
## Queries mod overvågede servere
- De skal være **read-only** og kunne køre med rettighederne fra `db/permissions/grant-least-privilege.sql`. Kræver de mere, skal scriptet og `LeastPrivilegeTests` opdateres i samme ændring.
- De skal være billige: ingen `DETAILED`-scans, grænser på antal rækker og intet, der kan tage låse på brugerdata.
- Tjek, hvilke versioner DMV'en og kolonnerne findes i, og angiv det som capabilities.
- Læg SQL'en i en `internal const` eller en `internal static Build…Sql`-metode i collectoren, ikke inline i `CollectAsync`, og registrér den i `tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs`. Opdatér `SQL Scripts/_collectors/<Collector>.sql` og regelfilerne, der bruger collectoren. `SqlScriptTests` fejler ellers.
## Commits

View file

@ -14,7 +14,7 @@
<Product>SQLInsight Pro</Product>
<Copyright>Copyright (c) SEOSoft</Copyright>
<!-- Release version; set with -p:Version=… when publishing. Update checks compare against it. -->
<Version>1.0.3</Version>
<Version>1.5.2</Version>
</PropertyGroup>
<PropertyGroup Condition="$(MSBuildProjectName.EndsWith('Tests'))">

View file

@ -22,11 +22,15 @@
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.12" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.12" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.12" />
<PackageVersion Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
<PackageVersion Include="System.DirectoryServices.AccountManagement" Version="10.0.12" />
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.12" />
<PackageVersion Include="PDFsharp-MigraDoc" Version="6.2.4" />
<PackageVersion Include="Markdig" Version="1.4.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Systemd" Version="10.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Negotiate" Version="10.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" />
<!-- Test -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="xunit" Version="2.9.3" />

View file

@ -4,7 +4,7 @@
SQLInsight Pro er et overvågningsværktøj til Microsoft SQL Server, skrevet i C# på .NET. Det kører hos kunden og forlader ikke netværket, medmindre kunden selv vælger det. Værktøjet nøjes ikke med at vise tal. Det forklarer, hvad der er galt, hvorfor det er et problem, og hvordan det løses. Rådene har samme niveau som dem, man får fra en erfaren DBA.
> **Status:** Fase 1–4 er implementeret: agent, 61 indbyggede regler plus community-regler, notifikationer, lokalt repository og web-UI, licenser, signerede opdateringer, SQLInsight Hub med portal, erfaring på tværs af kunder, PDF-rapporter og integration med Prometheus, Grafana og SCOM, plus MSI-installer, community-websted og Discourse-forum. Se [installation](docs/installation.md), [opdateringer](docs/opdatering.md), [integration](docs/integration.md), [rapporter](docs/rapporter.md), [egne regler](docs/regler.md), [databehandling](docs/databehandling.md), [community](docs/community.md), [Roadmap](#roadmap) og [CONTRIBUTING.md](CONTRIBUTING.md).
> **Status:** Fase 1–4 er implementeret: agent, 108 indbyggede regler (92 for SQL Server; fra 1.5 også 13 for Windows-servere og 3 for syntetiske tjek) plus community-regler, notifikationer, lokalt repository og web-UI, licenser, signerede opdateringer, SQLInsight Hub med portal, erfaring på tværs af kunder, PDF-rapporter og integration med Prometheus, Grafana og SCOM, plus MSI-installer, community-websted og Discourse-forum. Se [installation](docs/installation.md), [opdateringer](docs/opdatering.md), [integration](docs/integration.md), [rapporter](docs/rapporter.md), [egne regler](docs/regler.md), [Dynamics AX](docs/dynamics-ax.md), [Windows-servere](docs/windows-servere.md), [syntetiske tjek](docs/syntetiske-tjek.md), [databehandling](docs/databehandling.md), [community](docs/community.md), [Roadmap](#roadmap) og [CONTRIBUTING.md](CONTRIBUTING.md).
---
@ -205,6 +205,7 @@ Hvert **finding** indeholder:
### Fase 4: Videre
- [x] Anbefalinger på tværs af kunder: Hubben ser, hvilke fund der forsvinder efter en opdatering, og agenterne viser "hos 4 kunder forsvandt dette efter CU21" på fundet. Deles kun samlet og anonymt fra mindst 3 kunder. Se [docs/hub.md](docs/hub.md#erfaring-på-tværs-af-kunder)
- [x] Rapporter (PDF) til ledelse og compliance: nøgletal, sammenfatning i almindeligt sprog, kontroller, instanser og alvorlige fund. Kan hentes i webbrugerfladen og sendes månedligt på mail. Se [docs/rapporter.md](docs/rapporter.md)
- [x] Rettighedsrevision: sysadmin og øvrige serverroller med Windows-grupper foldet ud via AD, ændringer med hvem/hvornår fra default trace, fund ved nye medlemmer og revisionsrapport (PDF). Se [docs/rettigheder.md](docs/rettigheder.md)
- [x] Mulighed for, at community selv bidrager med regler: regler som SQL i JSON (community via pull request og signeret regelpakke, lokale regler hos kunden), med validering og `rules test` mod en instans. Se [docs/regler.md](docs/regler.md)
- [x] API til integration med eksisterende monitorering: JSON-API og Prometheus-endpoint med API-tokens, Grafana-dashboard, og Windows' hændelseslog med faste event-id'er til SCOM. Se [docs/integration.md](docs/integration.md)
- [x] Community-websted (download af signerede udgivelser, dokumentation, SQL Server-versioner, regler, support) og Discourse-forum på dansk med kategorier, løste svar og opsætning som kode. Se [docs/community.md](docs/community.md)

116
SQL Scripts/COM-AX-001.sql Normal file
View file

@ -0,0 +1,116 @@
-- ===================================================================================================================
-- COM-AX-001: Dynamics AX 3.0 er uden support fra Microsoft
-- ===================================================================================================================
-- Regel-id: COM-AX-001
-- Titel: Dynamics AX 3.0 er uden support fra Microsoft
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes
-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Dynamics AX 3.0 er uden support fra Microsoft. Microsoft Dynamics AX 3.0 er for længst ude af Microsofts support,
-- også den udvidede. Der kommer ingen sikkerhedsopdateringer eller rettelser til AX-kernen, klienten eller AOS, og
-- nyere Windows- og SQL Server-versioner er aldrig testet med AX 3.0. AX 3.0 bruger desuden 32-bit RecId (int), som
-- kan løbe tør på store installationer.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX;
-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget.
-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{database}
-- er en Dynamics AX 3.0-database, og AX 3.0 er uden support" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/dynamics-ax.json (id COM-AX-001, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller
-- objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-AX-001 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
USE [AXDB];
SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS [sql_version] WHERE COLUMNPROPERTY((SELECT TOP 1 object_id FROM sys.tables WHERE name = N'SQLDICTIONARY'), N'RECID', 'Precision') = 10
GO

117
SQL Scripts/COM-AX-002.sql Normal file
View file

@ -0,0 +1,117 @@
-- ===================================================================================================================
-- COM-AX-002: Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0
-- ===================================================================================================================
-- Regel-id: COM-AX-002
-- Titel: Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes
-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0. AX 3.0 blev kun frigivet
-- til SQL Server 2000 og 2005. På nyere versioner kan den virke, men optimizeren, låsning og udfasede funktioner
-- opfører sig anderledes end det, AX-kernen blev bygget til, og hverken Microsoft eller en AX-partner kan give
-- support. Fejl viser sig ofte først under belastning, fx ved månedsafslutning. Databasens compatibility level er
-- {compatibility_level}.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX;
-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget.
-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "AX
-- 3.0-databasen {database} ligger på SQL Server {sql_version}" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/dynamics-ax.json (id COM-AX-002, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller
-- objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-AX-002 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
USE [AXDB];
SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS [sql_version], d.compatibility_level AS [compatibility_level] FROM sys.databases d WHERE d.database_id = DB_ID() AND CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 4) AS int) > 9 AND COLUMNPROPERTY((SELECT TOP 1 object_id FROM sys.tables WHERE name = N'SQLDICTIONARY'), N'RECID', 'Precision') = 10
GO

116
SQL Scripts/COM-AX-003.sql Normal file
View file

@ -0,0 +1,116 @@
-- ===================================================================================================================
-- COM-AX-003: Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014)
-- ===================================================================================================================
-- Regel-id: COM-AX-003
-- Titel: Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014)
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes
-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014). Dynamics AX sender mange små,
-- hyppige forespørgsler og cursorer fra AOS. Når de får parallelle planer, bruger de flere tråde end nødvendigt,
-- venter på hinanden (CXPACKET) og holder låse længere, så brugerne oplever langsomme formularer og blokering.
-- Microsofts vejledning for Dynamics AX er derfor max degree of parallelism = 1 på instansen.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX;
-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget.
-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "MAXDOP er
-- {maxdop} på instansen med Dynamics AX-databasen {database}" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/dynamics-ax.json (id COM-AX-003, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller
-- objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-AX-003 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
USE [AXDB];
SELECT CAST(c.value_in_use AS int) AS [maxdop] FROM sys.configurations c WHERE c.name = 'max degree of parallelism' AND CAST(c.value_in_use AS int) <> 1 AND CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 4) AS int) < 13
GO

116
SQL Scripts/COM-AX-004.sql Normal file
View file

@ -0,0 +1,116 @@
-- ===================================================================================================================
-- COM-AX-004: Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere)
-- ===================================================================================================================
-- Regel-id: COM-AX-004
-- Titel: Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere)
-- Alvor: Medium
-- Versioner: SQL Server 2016 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes
-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere). Dynamics AX sender mange små, hyppige
-- forespørgsler og cursorer fra AOS. Når de får parallelle planer, bruger de flere tråde end nødvendigt, venter på
-- hinanden (CXPACKET) og holder låse længere. Microsofts vejledning for Dynamics AX er MAXDOP 1. Instansen har
-- {server_maxdop}, og databasen har {database_maxdop} (0 = instansens værdi gælder).
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX;
-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget.
-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "Dynamics
-- AX-databasen {database} kører med MAXDOP større end 1" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/dynamics-ax.json (id COM-AX-004, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller
-- objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-AX-004 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
USE [AXDB];
SELECT CAST(c.value_in_use AS int) AS [server_maxdop], CAST(s.value AS int) AS [database_maxdop] FROM sys.configurations c CROSS JOIN sys.database_scoped_configurations s WHERE c.name = 'max degree of parallelism' AND s.name = 'MAXDOP' AND (CASE WHEN CAST(s.value AS int) = 0 THEN CAST(c.value_in_use AS int) ELSE CAST(s.value AS int) END) <> 1
GO

116
SQL Scripts/COM-AX-005.sql Normal file
View file

@ -0,0 +1,116 @@
-- ===================================================================================================================
-- COM-AX-005: Dynamics AX-tabeller uden unikt RecId-indeks
-- ===================================================================================================================
-- Regel-id: COM-AX-005
-- Titel: Dynamics AX-tabeller uden unikt RecId-indeks
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes
-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Dynamics AX-tabeller uden unikt RecId-indeks. AX identificerer hver post med RecId og opretter normalt et unikt
-- indeks på den (I_<tabel-id>RECID) ved synkronisering. Mangler det, er en synkronisering typisk fejlet eller
-- afbrudt, eller indekset er fjernet manuelt. Uden det bliver opslag på RecId (som AX laver hele tiden, fx ved
-- opdatering og sletning af én post) til scanninger, og dubletter opdages ikke. Eksempler: {examples}.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX;
-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget.
-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{tables}
-- AX-tabeller i {database} har intet unikt indeks på RECID" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/dynamics-ax.json (id COM-AX-005, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller
-- objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-AX-005 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
USE [AXDB];
SELECT COUNT(*) AS [tables], LEFT(STUFF((SELECT TOP 10 N', ' + t2.name FROM sys.tables t2 WHERE COLUMNPROPERTY(t2.object_id, N'RECID', 'ColumnId') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys.indexes i JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id WHERE i.object_id = t2.object_id AND i.is_unique = 1 AND c.name = N'RECID') ORDER BY t2.name FOR XML PATH('')), 1, 2, ''), 400) AS [examples] FROM sys.tables t WHERE COLUMNPROPERTY(t.object_id, N'RECID', 'ColumnId') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys.indexes i JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id WHERE i.object_id = t.object_id AND i.is_unique = 1 AND c.name = N'RECID') HAVING COUNT(*) > 0
GO

116
SQL Scripts/COM-AX-006.sql Normal file
View file

@ -0,0 +1,116 @@
-- ===================================================================================================================
-- COM-AX-006: Store Dynamics AX-tabeller uden clustered index (heaps)
-- ===================================================================================================================
-- Regel-id: COM-AX-006
-- Titel: Store Dynamics AX-tabeller uden clustered index (heaps)
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes
-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Store Dynamics AX-tabeller uden clustered index (heaps). En tabel uden clustered index (en heap) får spredte data
-- og 'forwarded records', når poster opdateres, og den kan ikke defragmenteres med en indeks-rebuild. I AX styres
-- det af tabellens ClusterIndex-egenskab i AOT, som i AX 3.0 ofte ikke er sat på standard- og kundetabeller. På
-- store tabeller giver det langsomme opslag og mere I/O. I alt {rows} rækker. Største: {largest}.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX;
-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget.
-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{tables}
-- store AX-tabeller i {database} har intet clustered index" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/dynamics-ax.json (id COM-AX-006, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller
-- objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-AX-006 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
USE [AXDB];
SELECT COUNT(*) AS [tables], SUM(h.row_count) AS [rows], LEFT(STUFF((SELECT TOP 10 N', ' + OBJECT_NAME(h2.object_id) + N' (' + CAST(h2.row_count AS nvarchar(20)) + N')' FROM (SELECT p.object_id, SUM(p.rows) AS row_count FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id WHERE p.index_id = 0 GROUP BY p.object_id) h2 WHERE h2.row_count >= 100000 ORDER BY h2.row_count DESC FOR XML PATH('')), 1, 2, ''), 400) AS [largest] FROM (SELECT p.object_id, SUM(p.rows) AS row_count FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id WHERE p.index_id = 0 GROUP BY p.object_id) h WHERE h.row_count >= 100000 HAVING COUNT(*) > 0
GO

117
SQL Scripts/COM-AX-007.sql Normal file
View file

@ -0,0 +1,117 @@
-- ===================================================================================================================
-- COM-AX-007: Automatiske statistikker er slået fra i en Dynamics AX-database
-- ===================================================================================================================
-- Regel-id: COM-AX-007
-- Titel: Automatiske statistikker er slået fra i en Dynamics AX-database
-- Alvor: High
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes
-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Automatiske statistikker er slået fra i en Dynamics AX-database. AX bygger sine forespørgsler dynamisk ud fra
-- brugernes filtre, så optimizeren er helt afhængig af, at statistikkerne er der og er opdaterede. Uden automatiske
-- statistikker vælger SQL Server ofte forkerte planer, især efter store kørsler som lageropgørelse og nummerserier,
-- og formularer og batchjob kan pludselig blive meget langsomme. Auto create statistics: {auto_create_statistics}.
-- Auto update statistics: {auto_update_statistics}.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX;
-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget.
-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "Automatiske
-- statistikker er slået fra i AX-databasen {database}" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/dynamics-ax.json (id COM-AX-007, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller
-- objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-AX-007 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
USE [AXDB];
SELECT CASE WHEN d.is_auto_create_stats_on = 1 THEN 'til' ELSE 'fra' END AS [auto_create_statistics], CASE WHEN d.is_auto_update_stats_on = 1 THEN 'til' ELSE 'fra' END AS [auto_update_statistics] FROM sys.databases d WHERE d.database_id = DB_ID() AND (d.is_auto_create_stats_on = 0 OR d.is_auto_update_stats_on = 0)
GO

View file

@ -0,0 +1,62 @@
-- ===================================================================================================================
-- COM-CFG-001: Default trace er slået fra
-- ===================================================================================================================
-- Regel-id: COM-CFG-001
-- Titel: Default trace er slået fra
-- Alvor: Low
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
--
-- Hvad tjekkes, og hvorfor:
-- Default trace er slået fra. Default trace er en let sporing, der registrerer bl.a. autogrowth, ændringer af
-- objekter og logins. Uden den er det svært at efterforske, hvad der skete, efter en hændelse.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Forespørgslen køres én gang i master. Kører instansen en ældre version end minimumVersion, er reglen
-- "ikke relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel:
-- "Default trace er slået fra" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/security.json (id COM-CFG-001, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-CFG-001 (server-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
USE [master];
SELECT CAST(value_in_use AS int) AS [value_in_use] FROM sys.configurations WHERE name = 'default trace enabled' AND value_in_use = 0
GO

View file

@ -0,0 +1,73 @@
-- ===================================================================================================================
-- COM-DB-001: Fremmednøgler, som SQL Server ikke stoler på
-- ===================================================================================================================
-- Regel-id: COM-DB-001
-- Titel: Fremmednøgler, som SQL Server ikke stoler på
-- Alvor: Low
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
--
-- Hvad tjekkes, og hvorfor:
-- Fremmednøgler, som SQL Server ikke stoler på. Nøglen er blevet slået til igen uden WITH CHECK, fx efter en
-- dataindlæsning. Den håndhæves for nye rækker, men SQL Server kan ikke stole på eksisterende data, så optimizeren
-- udnytter den ikke til at fjerne joins, og der kan ligge ugyldige data.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql),
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{database}:
-- fremmednøglen {constraint} på {table} er ikke betroet" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/integrity.json (id COM-DB-001, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-DB-001 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke.
USE [msdb];
SELECT s.name + '.' + o.name AS [table], fk.name AS [constraint] FROM sys.foreign_keys fk JOIN sys.objects o ON o.object_id = fk.parent_object_id JOIN sys.schemas s ON s.schema_id = o.schema_id WHERE fk.is_not_trusted = 1 AND fk.is_disabled = 0
GO

View file

@ -0,0 +1,73 @@
-- ===================================================================================================================
-- COM-FT-001: Fulltext-katalog er sat på pause, fordi disken er fuld
-- ===================================================================================================================
-- Regel-id: COM-FT-001
-- Titel: Fulltext-katalog er sat på pause, fordi disken er fuld
-- Alvor: High
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
--
-- Hvad tjekkes, og hvorfor:
-- Fulltext-katalog er sat på pause, fordi disken er fuld. SQL Server har sat opbygningen af fulltext-kataloget
-- {catalog} på pause, fordi der ikke er plads på disken. Søgninger med CONTAINS og FREETEXT bruger det indeks, der
-- var bygget, så nye og ændrede rækker findes ikke. Det fortsætter ikke af sig selv, før der er plads.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql),
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel:
-- "Fulltext-kataloget {catalog} i {database} er stoppet: disken er fuld" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/fulltext.json (id COM-FT-001, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-FT-001 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke.
USE [msdb];
SELECT c.name AS [catalog], FULLTEXTCATALOGPROPERTY(c.name, 'ItemCount') AS [items] FROM sys.fulltext_catalogs c WHERE FULLTEXTCATALOGPROPERTY(c.name, 'PopulateStatus') = 8
GO

View file

@ -0,0 +1,73 @@
-- ===================================================================================================================
-- COM-FT-002: Fulltext-katalog er sat på pause eller drosles
-- ===================================================================================================================
-- Regel-id: COM-FT-002
-- Titel: Fulltext-katalog er sat på pause eller drosles
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
--
-- Hvad tjekkes, og hvorfor:
-- Fulltext-katalog er sat på pause eller drosles. Opbygningen af fulltext-kataloget {catalog} er {status}. Mens den
-- står stille, finder fulltext-søgninger ikke nye og ændrede rækker. 'Droslet' betyder, at SQL Server har bremset
-- opbygningen på grund af ressourcemangel (hukommelse eller CPU); 'pause' kan være sat manuelt eller af SQL Server.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql),
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel:
-- "Fulltext-kataloget {catalog} i {database} er {status}" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/fulltext.json (id COM-FT-002, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-FT-002 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke.
USE [msdb];
SELECT c.name AS [catalog], CASE FULLTEXTCATALOGPROPERTY(c.name, 'PopulateStatus') WHEN 2 THEN 'sat på pause' ELSE 'droslet' END AS [status] FROM sys.fulltext_catalogs c WHERE FULLTEXTCATALOGPROPERTY(c.name, 'PopulateStatus') IN (2, 3)
GO

View file

@ -0,0 +1,73 @@
-- ===================================================================================================================
-- COM-FT-003: Fulltext-indeks uden automatisk opdatering er ikke opdateret i over en uge
-- ===================================================================================================================
-- Regel-id: COM-FT-003
-- Titel: Fulltext-indeks uden automatisk opdatering er ikke opdateret i over en uge
-- Alvor: Low
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
--
-- Hvad tjekkes, og hvorfor:
-- Fulltext-indeks uden automatisk opdatering er ikke opdateret i over en uge. Fulltext-indekset på {table}
-- opdateres ikke automatisk (change tracking er {change_tracking}), og kataloget {catalog} blev sidst bygget
-- {last_population}. Søgninger finder ikke rækker, der er oprettet eller ændret siden.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql),
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel:
-- "Fulltext-indekset på {table} i {database} er sidst opdateret {last_population}" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/fulltext.json (id COM-FT-003, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-FT-003 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke.
USE [msdb];
SELECT OBJECT_NAME(i.object_id) AS [table], c.name AS [catalog], CASE i.change_tracking_state WHEN 'M' THEN 'manuel' ELSE 'slået fra' END AS [change_tracking], CONVERT(varchar(16), DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101'), 120) AS [last_population] FROM sys.fulltext_indexes i JOIN sys.fulltext_catalogs c ON c.fulltext_catalog_id = i.fulltext_catalog_id WHERE i.change_tracking_state IN ('M', 'O') AND i.is_enabled = 1 AND FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge') > 0 AND DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101') < DATEADD(day, -7, GETDATE()) AND DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101') >= DATEADD(day, -30, GETDATE())
GO

View file

@ -0,0 +1,74 @@
-- ===================================================================================================================
-- COM-FT-004: Fulltext-indeks uden automatisk opdatering er ikke opdateret i over 30 dage
-- ===================================================================================================================
-- Regel-id: COM-FT-004
-- Titel: Fulltext-indeks uden automatisk opdatering er ikke opdateret i over 30 dage
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
--
-- Hvad tjekkes, og hvorfor:
-- Fulltext-indeks uden automatisk opdatering er ikke opdateret i over 30 dage. Fulltext-indekset på {table}
-- opdateres ikke automatisk (change tracking er {change_tracking}), og kataloget {catalog} er ikke bygget i over 30
-- dage (sidst {last_population}). Søgninger med CONTAINS og FREETEXT giver derfor forældede resultater.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql),
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel:
-- "Fulltext-indekset på {table} i {database} er ikke opdateret siden {last_population}" ({kolonne} udfyldes fra
-- rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/fulltext.json (id COM-FT-004, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-FT-004 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke.
USE [msdb];
SELECT OBJECT_NAME(i.object_id) AS [table], c.name AS [catalog], CASE i.change_tracking_state WHEN 'M' THEN 'manuel' ELSE 'slået fra' END AS [change_tracking], CONVERT(varchar(16), DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101'), 120) AS [last_population] FROM sys.fulltext_indexes i JOIN sys.fulltext_catalogs c ON c.fulltext_catalog_id = i.fulltext_catalog_id WHERE i.change_tracking_state IN ('M', 'O') AND i.is_enabled = 1 AND FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge') > 0 AND DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101') < DATEADD(day, -30, GETDATE())
GO

View file

@ -0,0 +1,72 @@
-- ===================================================================================================================
-- COM-FT-005: Fulltext-indeks er deaktiveret
-- ===================================================================================================================
-- Regel-id: COM-FT-005
-- Titel: Fulltext-indeks er deaktiveret
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
--
-- Hvad tjekkes, og hvorfor:
-- Fulltext-indeks er deaktiveret. Fulltext-indekset på {table} (kataloget {catalog}) er slået fra. Det opdateres
-- ikke, og fulltext-søgninger mod tabellen fejler eller finder intet, afhængigt af hvordan applikationen søger.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql),
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel:
-- "Fulltext-indekset på {table} i {database} er deaktiveret" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/fulltext.json (id COM-FT-005, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-FT-005 (database-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Databaserne, reglen køres i
-- Kilde: CustomQueryCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql
SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke.
USE [msdb];
SELECT OBJECT_NAME(i.object_id) AS [table], c.name AS [catalog] FROM sys.fulltext_indexes i JOIN sys.fulltext_catalogs c ON c.fulltext_catalog_id = i.fulltext_catalog_id WHERE i.is_enabled = 0
GO

View file

@ -0,0 +1,62 @@
-- ===================================================================================================================
-- COM-SEC-001: Login'et sa er aktivt
-- ===================================================================================================================
-- Regel-id: COM-SEC-001
-- Titel: Login'et sa er aktivt
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
--
-- Hvad tjekkes, og hvorfor:
-- Login'et sa er aktivt. sa er det login, angribere prøver først, fordi navnet er kendt, og det har fulde
-- rettigheder. Password-politik håndhæves: {password_policy}.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Forespørgslen køres én gang i master. Kører instansen en ældre version end minimumVersion, er reglen
-- "ikke relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "Det
-- indbyggede login '{login}' er aktivt" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/security.json (id COM-SEC-001, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-SEC-001 (server-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
USE [master];
SELECT name AS [login], CASE WHEN is_policy_checked = 1 THEN 'ja' ELSE 'nej' END AS [password_policy] FROM sys.sql_logins WHERE principal_id = 1 AND is_disabled = 0
GO

View file

@ -0,0 +1,63 @@
-- ===================================================================================================================
-- COM-SEC-002: Database ejes af et login, der ikke findes
-- ===================================================================================================================
-- Regel-id: COM-SEC-002
-- Titel: Database ejes af et login, der ikke findes
-- Alvor: Low
-- Versioner: SQL Server 2008 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
--
-- Hvad tjekkes, og hvorfor:
-- Database ejes af et login, der ikke findes. Ejeren er typisk en medarbejder, som er stoppet, eller en database,
-- der er gendannet fra en anden server. Det giver fejl i fx EXECUTE AS OWNER, cross-database ownership chaining og
-- nogle jobs.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr.
-- regel). Forespørgslen køres én gang i master. Kører instansen en ældre version end minimumVersion, er reglen
-- "ikke relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel:
-- "Databasen {database} ejes af et login, der ikke længere findes" ({kolonne} udfyldes fra rækken).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CustomQueryCollector
-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs
-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql,
-- CustomQueryCollector.BuildRuleSql(database, query)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
-- rules/community/security.json (id COM-SEC-002, indlejret i regelpakken)
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CustomQueryCollector: Community-regel COM-SEC-002 (server-regel)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere
-- Kilde: CustomQueryCollector.BuildRuleSql
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
USE [master];
SELECT d.name AS [database], CONVERT(varchar(100), d.owner_sid, 1) AS [owner_sid] FROM sys.databases d LEFT JOIN sys.server_principals p ON p.sid = d.owner_sid WHERE p.sid IS NULL AND d.database_id > 4
GO

288
SQL Scripts/README.md Normal file
View file

@ -0,0 +1,288 @@
# SQL Scripts
Denne mappe indeholder **præcis den SQL, SQLInsight Pro kører mod en overvåget SQL Server**, ordnet efter regel. Den er
lavet, så en DBA kan gennemgå hver eneste sætning, før agenten får adgang til en server: hvad der læses, med hvilke
rettigheder, på hvilke versioner, og hvordan reglen bagefter vurderer data.
Agenten kører **ikke** filerne her. De er en læsbar kopi af SQL'en i koden, og unit tests sikrer, at kopien er
identisk med koden (se [Hold mappen i sync](#hold-mappen-i-sync)).
Fra 1.5 har også reglerne for Windows-servere (`SQLI-WIN-…`) og syntetiske tjek (`SQLI-SYN-…`) en fil her, med samme
header. De fleste af dem sender ingen SQL: forespørgslerne mod Windows (CIM/WQL og hændelseslogsfilteret) står ordret i
kommentarer, så de kan gennemgås på samme måde (se [Windows-servere og syntetiske tjek](#windows-servere-og-syntetiske-tjek-fra-15)).
## Navngivning
| Fil | Indhold |
|---|---|
| `<regel-id>.sql` | Én fil pr. regel, navngivet med regel-id'et præcis som i systemet: `SQLI-…` for de indbyggede regler (C#), `COM-…` for community-regler (JSON i `rules/community/`). |
| `_collectors/<Collector>.sql` | Alle forespørgsler fra én collector. Flere regler deler ofte samme collector; regelfilerne gentager SQL'en, så hver fil kan køres for sig. |
| `_collectors/InstanceProbe.sql` | Registreringen af instansen (version, udgave, Always On) og de sessionsindstillinger, agenten sætter på hver forbindelse. |
| `_collectors/AxDatabaseDetector.sql`, `_collectors/AxBatchCollector.sql`, `_collectors/AxSequenceCollector.sql` | Dynamics AX (fra 1.4): den fælles AX-genkendelse og læsningen af batchjob, nummerserier og RecId i AX-databasen. AX-forespørgslerne er vist for hver AX-version, SQLInsight har et skema for (Axapta 3.0, AX 4.0, AX 2009 og AX 2012), og for databasen `AXDB`. |
| `_collectors/DatabaseDiscovery.sql`, `_collectors/PermissionCheck.sql` | Søgningen efter databaser (med Dynamics AX-genkendelse) og forbindelsestesten med rettighedstjek under *Servere og databaser* (fra 1.3). De køres kun, når der bedes om dem i webbrugerfladen, og søgningen desuden hver time. |
| `_collectors/SyntheticCheckRunner.sql` | Det syntetiske SQL-tjek (fra 1.5): sessionsindstillingerne, `USE`, transaktionen og standardforespørgslen. Tjekkets egen forespørgsel køres i stedet for `SELECT 1`, og transaktionen rulles altid tilbage. |
## Opbygning af en regelfil
Hver fil starter med en kommentar-header på dansk:
- **Regel-id, titel, alvor (severity)** og **understøttede versioner**.
- **Hvad tjekkes, og hvorfor.**
- **Vurdering**: de fleste regler afgør ikke selv noget i SQL. Collectoren læser data, og reglen vurderer dem i C#
(fx "fund hvis ventetiden er over 5 ms i gennemsnit mellem to målinger"). Logikken er beskrevet i almindeligt dansk.
- **Grænseværdier** med konfigurationsnøgle (i agentens `appsettings.json`, sektionen `SQLInsight`) og standardværdi, eller
"fast i koden".
- **Rettigheder** som præcise `GRANT`-sætninger (`[agent]` = agentens login), svarende til
`db/permissions/grant-least-privilege.sql`.
- **Data kommer fra**: collectoren, kildefilen og konstanten/metoden, der indeholder SQL'en, samt intervallet.
- **Regelkode**: filen og klassen, der vurderer data.
- **Read-only**: hvad scriptet gør ud over SELECT.
Derefter følger SQL'en, én blok pr. variant:
```sql
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT ...
GO
```
- `Variant` siger, hvilke versioner (eller hvilken situation) teksten sendes for. Agenten vælger varianten ud fra
instansens version og capabilities.
- `Kilde` er nøglen, som testen bruger til at sammenligne med koden.
- `Kode` er stedet i koden, hvor SQL'en står eller bygges.
## Sådan køres scriptene
1. Åbn filen i SQL Server Management Studio (eller kør `sqlcmd -i "SQL Scripts\SQLI-DB-007.sql"`).
2. Forbind **som agentens login** (fx gMSA'en), eller et login med præcis de rettigheder, `grant-least-privilege.sql`
giver. Som sysadmin virker alt, men så ser man ikke, om en rettighed mangler.
3. Kør hele filen. Første batch sætter de samme sessionsindstillinger som agenten:
`SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;`
4. Varianter for andre SQL Server-versioner end din kan fejle (fx SQL Server 2000-varianten mod `master.dbo.sysaltfiles`
eller kolonner, der først findes i nyere versioner). Det er forventet; SSMS fortsætter med næste batch.
Bemærk:
- **Read-only.** Alle scripts læser kun: SELECT mod katalog-views og DMV'er, `DBCC TRACESTATUS`, `DBCC SQLPERF(WAITSTATS)`
(kun SQL Server 2000), `xp_readerrorlog`, `xp_msver`, `sys.fn_trace_gettable`, lokale variabler og `USE`.
Ingen undtagelser: heller ikke midlertidige tabeller i tempdb. Dynamics AX-læsningerne (fra 1.4) kører som
`EXEC [AXDB].sys.sp_executesql N'…'`, så sessionen aldrig skifter database; batchen i strengen er selv kun SELECT med
`TOP` (READ COMMITTED med `SET LOCK_TIMEOUT 2000`, eller SNAPSHOT, aldrig NOLOCK), og testen tjekker den på samme måde.
- **Per-database-forespørgsler** (indexes, statistikker, Query Store, database-scoped community-regler) kører agenten i
hver online brugerdatabase. Her er de vist for `msdb`, som findes på alle instanser, så scriptet kan køres uændret.
Ret `[msdb]` / `N'msdb'` til den database, du vil se.
- **Værdier, agenten beregner ved kørsel** (default trace-filens sti, tidspunktet for forrige måling) er erstattet af
variabler, der erklæres lige over forespørgslen med realistiske standardværdier. Kommentaren over viser, hvad agenten
indsætter. Grænser fra konfigurationen (fx 1024 MB log, 15 minutter) er vist med standardværdien.
- **Ikke med her:** fix-scripts i fundene (de vises for brugeren og køres aldrig automatisk), agentens eget repository
(fx baseline-historikken bag `SQLI-ANOM-001`, som ikke hentes fra den overvågede server) og kundens lokale regler
(`LOCAL-…`).
## Windows-servere og syntetiske tjek (fra 1.5)
- **`SQLI-WIN-…`** sender ingen SQL. Filen har samme header som de andre regler (rettighederne er dem, som
`db/permissions/grant-windows-server.ps1` giver agentens konto, aldrig lokal administrator) og viser derefter, i
kommentarer, de præcise CIM/WQL-forespørgsler (kun `SELECT`), XPath-filteret til hændelsesloggen og det eneste
metodekald, Task Schedulers skrivebeskyttede `GetInfoByName`, samt PowerShell til at efterprøve dem. Agenten starter,
stopper eller ændrer intet på serveren. Filerne kan åbnes i SSMS, men gør intet.
- **`SQLI-SYN-001` og `SQLI-SYN-002`** viser præcis det, et syntetisk SQL-tjek sender: sessionsindstillingerne, `USE`
(kun med en database), `BEGIN TRANSACTION`, forespørgslen (standard `SELECT 1`) og `IF @@TRANCOUNT > 0 ROLLBACK
TRANSACTION`, der altid køres bagefter. En egen forespørgsel skal være én skrivebeskyttet SELECT
(`src/SQLInsight.Core/CustomRules/ReadOnlyQuery.cs`). TCP-, HTTP-, DNS- og AX-tjek sender ingen SQL.
- **`SQLI-SYN-003`** (TLS-certifikatet) sender ingen SQL.
## Hold mappen i sync
`tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs` fejler, hvis mappen og koden ikke stemmer overens:
- `Every_registered_rule_has_a_script_and_every_script_a_rule`: hver indbygget regel (`BuiltInRules` og, fra 1.5,
`ServerRuleCatalog`) og hver community-regel i regelpakken har en `<id>.sql`, og der er ingen filer for ukendte id'er.
- `Scripts_contain_the_current_sql_from_the_code`: hver blok med `-- Kilde: <nøgle>` indeholder SQL'en, som koden
bygger den i dag, ordret (nøglerne står i `SqlScriptSources.cs`). Kun værdier, agenten beregner ved kørsel, må stå
anderledes. Kun Windows-server- og syntetiske-tjek-reglerne må være uden SQL-blokke.
- `Every_collector_query_is_registered_and_in_a_collector_script`: hver SQL-konstant og hver `Build…Sql`-metode i
collectorerne er registreret, og hver registreret forespørgsel står i et `_collectors/`-script.
- `Community_rule_scripts_contain_the_rule_query_verbatim`: community-reglens `query` fra JSON står ordret i filen.
- `Scripts_are_read_only`: ingen skrivende sætninger, heller ikke `SELECT … INTO #temp` - heller ikke i batchen i en
`EXEC [db].sys.sp_executesql N'…'`.
- `tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs` (Dynamics AX): hver AX-regel og AX-collector viser SQL'en for alle
AX-versioner, og headeren siger, hvad der ikke er verificeret mod kundedata.
- `tests/SQLInsight.Servers.Tests/ServerScriptTests.cs` (fra 1.5): hver CIM/WQL-forespørgsel og hvert
hændelseslogsfilter i `src/SQLInsight.Servers/CimQueries.cs` står ordret i en `SQLI-WIN-…`-fil, og hver af filerne viser
mindst én af dem. `tests/SQLInsight.Rules.Tests/ServerRuleScriptTests.cs`: regel-id og titel i headeren er reglens.
Er en collectors SQL ændret, kan blokkene skrives om fra koden: kør testene med `SQLINSIGHT_UPDATE_SQL_SCRIPTS=1`
(kommentarlinjerne bevares). Blokke med værdier, agenten beregner ved kørsel (⟪…⟫), udfyldes kun, når de er nye; værdien så sættes ind i hånden.
En ny regel skal derfor have sin `SQL Scripts/<id>.sql` (se `CONTRIBUTING.md`). Ændres en collectors SQL, peger
testen på de filer, der skal opdateres.
## Regler
124 regler: 108 indbyggede (C#: 92 for SQL Server, 13 for Windows-servere og 3 for syntetiske tjek, fra 1.5) og 16 community-regler (JSON).
| Regel-id | Fil | Titel | Collector(s) | Alvor |
|---|---|---|---|---|
| SQLI-AG-001 | [SQLI-AG-001.sql](SQLI-AG-001.sql) | Always On availability group er ikke sund | AvailabilityGroupCollector | High for DISCONNECTED, NOT_HEALTHY og suspenderet; Medium for PARTIALLY_HEALTHY. |
| SQLI-AG-002 | [SQLI-AG-002.sql](SQLI-AG-002.sql) | Sekundær replika er langt bagud | AvailabilityGroupCollector | Medium |
| SQLI-AG-003 | [SQLI-AG-003.sql](SQLI-AG-003.sql) | En node eller et vidne i clusteret er nede | AvailabilityGroupCollector | High |
| SQLI-ANOM-001 | [SQLI-ANOM-001.sql](SQLI-ANOM-001.sql) | Usædvanlig belastning i forhold til det normale | CpuCollector, PerformanceCounterCollector | CPU: Medium. Batch requests: Low ved stigning, Medium ved fald. Page life expectancy: Medium. |
| SQLI-AX-001 | [SQLI-AX-001.sql](SQLI-AX-001.sql) | Dynamics AX-batchjob er endt med fejl | AxDatabaseDetector, AxBatchCollector | High |
| SQLI-AX-002 | [SQLI-AX-002.sql](SQLI-AX-002.sql) | Dynamics AX-batchjob kører længere end normalt | AxDatabaseDetector, AxBatchCollector | Medium; High, når kørslen er dobbelt så lang som grænsen. |
| SQLI-AX-003 | [SQLI-AX-003.sql](SQLI-AX-003.sql) | Dynamics AX-batchjob er ikke startet til tiden | AxDatabaseDetector, AxBatchCollector | Medium; High, når jobbet i praksis aldrig er startet. |
| SQLI-AX-004 | [SQLI-AX-004.sql](SQLI-AX-004.sql) | Dynamics AX-batchjob hænger i Udfører | AxDatabaseDetector, AxBatchCollector | High |
| SQLI-AX-005 | [SQLI-AX-005.sql](SQLI-AX-005.sql) | Dynamics AX-nummerserie er ved at løbe tør | AxDatabaseDetector, AxSequenceCollector | Medium; High over den kritiske procent eller inden for de kritiske dage; Critical, når serien er opbrugt. |
| SQLI-AX-006 | [SQLI-AX-006.sql](SQLI-AX-006.sql) | Dynamics AX-nummerseriens format er for kort til Højeste | AxDatabaseDetector, AxSequenceCollector | Medium; High, når NEXTREC allerede er større end formatets kapacitet. |
| SQLI-AX-007 | [SQLI-AX-007.sql](SQLI-AX-007.sql) | Spærret Dynamics AX-nummerserie bruges stadig | AxDatabaseDetector, AxSequenceCollector | Medium |
| SQLI-AX-008 | [SQLI-AX-008.sql](SQLI-AX-008.sql) | Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op | AxDatabaseDetector, AxSequenceCollector | Low |
| SQLI-AX-009 | [SQLI-AX-009.sql](SQLI-AX-009.sql) | Dynamics AX RecId er ved at løbe tør | AxDatabaseDetector, AxSequenceCollector | Axapta 3.0: High/Critical efter grænserne; 64-bit: High; Critical, når RecId er opbrugt. |
| SQLI-AX-010 | [SQLI-AX-010.sql](SQLI-AX-010.sql) | AX-bruger blokerer andre | AxDatabaseDetector, BlockingCollector | Medium; High ved ventetid over 5 minutter eller mindst 10 blokerede sessioner. |
| SQLI-BLK-001 | [SQLI-BLK-001.sql](SQLI-BLK-001.sql) | Langvarig blocking | BlockingCollector | Medium; High når længste ventetid er mindst 5 minutter eller mindst 10 sessioner er blokeret. |
| SQLI-CFG-001 | [SQLI-CFG-001.sql](SQLI-CFG-001.sql) | max server memory er ikke sat | ConfigurationCollector, ServerCollector | High |
| SQLI-CFG-002 | [SQLI-CFG-002.sql](SQLI-CFG-002.sql) | max server memory efterlader for lidt til Windows | ConfigurationCollector, ServerCollector | Medium |
| SQLI-CFG-003 | [SQLI-CFG-003.sql](SQLI-CFG-003.sql) | cost threshold for parallelism står på standardværdien 5 | ConfigurationCollector, ServerCollector | Medium |
| SQLI-CFG-004 | [SQLI-CFG-004.sql](SQLI-CFG-004.sql) | MAXDOP passer ikke til serverens NUMA-layout | ConfigurationCollector, ServerCollector | Medium |
| SQLI-CFG-005 | [SQLI-CFG-005.sql](SQLI-CFG-005.sql) | Plan cache er fyldt med engangsplaner | ConfigurationCollector, PlanCacheCollector | Low |
| SQLI-CFG-006 | [SQLI-CFG-006.sql](SQLI-CFG-006.sql) | Priority boost er slået til | ConfigurationCollector | High |
| SQLI-CFG-007 | [SQLI-CFG-007.sql](SQLI-CFG-007.sql) | Lightweight pooling (fiber mode) er slået til | ConfigurationCollector | High |
| SQLI-CFG-008 | [SQLI-CFG-008.sql](SQLI-CFG-008.sql) | xp_cmdshell er slået til | ConfigurationCollector | Medium |
| SQLI-CFG-009 | [SQLI-CFG-009.sql](SQLI-CFG-009.sql) | Backup-komprimering er ikke standard | ConfigurationCollector, InstanceProbe | Low |
| SQLI-CFG-010 | [SQLI-CFG-010.sql](SQLI-CFG-010.sql) | Dedicated Admin Connection kan kun bruges lokalt | ConfigurationCollector | Low |
| SQLI-CFG-011 | [SQLI-CFG-011.sql](SQLI-CFG-011.sql) | Konfigurationsændringer venter på RECONFIGURE eller genstart | ConfigurationCollector | Low |
| SQLI-CFG-012 | [SQLI-CFG-012.sql](SQLI-CFG-012.sql) | Deadlocks bliver ikke registreret | TraceFlagCollector, InstanceProbe | Low |
| SQLI-CFG-013 | [SQLI-CFG-013.sql](SQLI-CFG-013.sql) | Konfiguration ændret | InstanceConfigurationCollector | Info |
| SQLI-CPU-001 | [SQLI-CPU-001.sql](SQLI-CPU-001.sql) | Vedvarende højt CPU-forbrug | CpuCollector | SQL Server: Medium fra 75 %, High fra 90 %. Andre processer: Medium. |
| SQLI-CPU-002 | [SQLI-CPU-002.sql](SQLI-CPU-002.sql) | Sætning kører næsten uafbrudt | QueryStatsCollector | Medium |
| SQLI-DB-001 | [SQLI-DB-001.sql](SQLI-DB-001.sql) | Database er ikke tilgængelig | DatabaseCollector | Critical for SUSPECT, RECOVERY_PENDING og EMERGENCY; Low for OFFLINE. |
| SQLI-DB-002 | [SQLI-DB-002.sql](SQLI-DB-002.sql) | AUTO_SHRINK er slået til | DatabaseCollector | High |
| SQLI-DB-003 | [SQLI-DB-003.sql](SQLI-DB-003.sql) | AUTO_CLOSE er slået til | DatabaseCollector | Medium |
| SQLI-DB-004 | [SQLI-DB-004.sql](SQLI-DB-004.sql) | Sidekontrol er ikke CHECKSUM | DatabaseCollector | Medium |
| SQLI-DB-005 | [SQLI-DB-005.sql](SQLI-DB-005.sql) | TRUSTWORTHY er slået til | DatabaseCollector | Medium |
| SQLI-DB-006 | [SQLI-DB-006.sql](SQLI-DB-006.sql) | Database kører med et ældre compatibility level | DatabaseCollector, InstanceProbe | Low |
| SQLI-DB-007 | [SQLI-DB-007.sql](SQLI-DB-007.sql) | Database mangler en nylig fuld backup | DatabaseCollector | Critical hvis aldrig backet op; High hvis ældre end 7 dage; Medium hvis databasen er i en availability group (backup kan være taget på en anden replika). |
| SQLI-DB-008 | [SQLI-DB-008.sql](SQLI-DB-008.sql) | Database i FULL recovery uden log-backups | DatabaseCollector | High; Medium hvis databasen er i en availability group. |
| SQLI-DB-009 | [SQLI-DB-009.sql](SQLI-DB-009.sql) | DBCC CHECKDB er ikke kørt for nylig | DatabaseCollector | High hvis aldrig kørt eller ældre end 30 dage; ellers Medium. |
| SQLI-DB-010 | [SQLI-DB-010.sql](SQLI-DB-010.sql) | Korrupte sider registreret i suspect_pages | SuspectPageCollector | Critical |
| SQLI-DB-011 | [SQLI-DB-011.sql](SQLI-DB-011.sql) | Transaktionsloggen har for mange VLF'er | DatabaseCollector | Medium; High over 10.000 VLF'er. |
| SQLI-DB-012 | [SQLI-DB-012.sql](SQLI-DB-012.sql) | Uhensigtsmæssig autogrowth | DatabaseCollector | Low |
| SQLI-DB-013 | [SQLI-DB-013.sql](SQLI-DB-013.sql) | Databasefil nærmer sig sin maksimale størrelse | DatabaseCollector | High fra 90 %; Critical når filen har nået MAXSIZE. |
| SQLI-DB-014 | [SQLI-DB-014.sql](SQLI-DB-014.sql) | Query Store indsamler ikke længere data | DatabaseCollector | High ved ERROR; ellers Medium. |
| SQLI-DB-015 | [SQLI-DB-015.sql](SQLI-DB-015.sql) | tempdb har for få eller ulige store datafiler | DatabaseCollector, ServerCollector | Medium for for få filer; Low for ulige store filer. |
| SQLI-DLK-001 | [SQLI-DLK-001.sql](SQLI-DLK-001.sql) | Deadlocks | DeadlockCollector | Medium; High fra 10 deadlocks siden sidste måling. |
| SQLI-IDX-001 | [SQLI-IDX-001.sql](SQLI-IDX-001.sql) | Manglende index med stor effekt | IndexCollector | Medium hvis forbedringsmålet er mindst 1.000.000; ellers Low. |
| SQLI-IDX-002 | [SQLI-IDX-002.sql](SQLI-IDX-002.sql) | Ubrugt index koster ved hver skrivning | IndexCollector | Low; Medium fra 1 GB. |
| SQLI-IDX-003 | [SQLI-IDX-003.sql](SQLI-IDX-003.sql) | Duplikeret eller overlappende index | IndexCollector | Medium for nøjagtige dubletter; Low for overlappende. |
| SQLI-IDX-004 | [SQLI-IDX-004.sql](SQLI-IDX-004.sql) | Heap med mange forwarded records | IndexCollector | Medium |
| SQLI-IO-001 | [SQLI-IO-001.sql](SQLI-IO-001.sql) | Høj læselatens på drev med datafiler | FileIoCollector | Medium; High fra 50 ms. |
| SQLI-IO-002 | [SQLI-IO-002.sql](SQLI-IO-002.sql) | Høj skrivelatens på drev med logfiler | FileIoCollector | Medium; High fra 15 ms. |
| SQLI-JOB-001 | [SQLI-JOB-001.sql](SQLI-JOB-001.sql) | SQL Agent-job fejler | AgentJobCollector | High hvis jobnavnet indeholder backup, checkdb, integrity eller dbcc; ellers Medium. |
| SQLI-JOB-002 | [SQLI-JOB-002.sql](SQLI-JOB-002.sql) | SQL Server Agent kører ikke | AgentJobCollector, InstanceProbe | High når der er aktive jobs; ellers Low. |
| SQLI-LOG-001 | [SQLI-LOG-001.sql](SQLI-LOG-001.sql) | SQL Server har læst beskadigede sider (fejl 823/824/825) | ErrorLogCollector | Critical for 823/824; High for 825. |
| SQLI-LOG-002 | [SQLI-LOG-002.sql](SQLI-LOG-002.sql) | I/O-forespørgsler, der tager over 15 sekunder (fejl 833) | ErrorLogCollector | High |
| SQLI-LOG-003 | [SQLI-LOG-003.sql](SQLI-LOG-003.sql) | Alvorlige fejl i SQL Servers fejllog (severity 17 og derover) | ErrorLogCollector | High fra severity 20; ellers Medium. |
| SQLI-LOG-004 | [SQLI-LOG-004.sql](SQLI-LOG-004.sql) | Mange mislykkede logins | ErrorLogCollector | Medium |
| SQLI-LOG-005 | [SQLI-LOG-005.sql](SQLI-LOG-005.sql) | SQL Server har lavet et stack dump | ErrorLogCollector | High |
| SQLI-LOG-006 | [SQLI-LOG-006.sql](SQLI-LOG-006.sql) | Fejl i Always On availability groups | ErrorLogCollector | High |
| SQLI-MEM-001 | [SQLI-MEM-001.sql](SQLI-MEM-001.sql) | Forespørgsler venter på hukommelse | PerformanceCounterCollector, WaitStatsCollector | High |
| SQLI-OPS-001 | [SQLI-OPS-001.sql](SQLI-OPS-001.sql) | Backup eller restore kører markant længere end normalt | LongOperationCollector | Medium |
| SQLI-PLAN-001 | [SQLI-PLAN-001.sql](SQLI-PLAN-001.sql) | Implicit konvertering forhindrer index seek | PlanAnalysisCollector | Medium |
| SQLI-PLAN-002 | [SQLI-PLAN-002.sql](SQLI-PLAN-002.sql) | Join uden join-betingelse (kartesisk produkt) | PlanAnalysisCollector | High |
| SQLI-PLAN-003 | [SQLI-PLAN-003.sql](SQLI-PLAN-003.sql) | Skalar funktion kaldes for hver række | PlanAnalysisCollector | Medium |
| SQLI-PLAN-004 | [SQLI-PLAN-004.sql](SQLI-PLAN-004.sql) | Sorteringer og hash joins spilder til tempdb | PlanAnalysisCollector | Medium |
| SQLI-PLAN-005 | [SQLI-PLAN-005.sql](SQLI-PLAN-005.sql) | Forespørgsel får langt mere hukommelse, end den bruger | PlanAnalysisCollector | Medium |
| SQLI-QS-001 | [SQLI-QS-001.sql](SQLI-QS-001.sql) | Planregression: forespørgsel har fået en langsommere plan | QueryStoreCollector | Medium; High når ekstra tid er mindst 10 minutter i døgnet. |
| SQLI-QS-002 | [SQLI-QS-002.sql](SQLI-QS-002.sql) | Forceret plan kan ikke bruges | QueryStoreCollector | High |
| SQLI-QS-003 | [SQLI-QS-003.sql](SQLI-QS-003.sql) | Parameterfølsom forespørgsel (parameter sniffing) | QueryStoreCollector | Medium |
| SQLI-REPL-001 | [SQLI-REPL-001.sql](SQLI-REPL-001.sql) | Replikering fejler | ReplicationCollector | High |
| SQLI-REPL-002 | [SQLI-REPL-002.sql](SQLI-REPL-002.sql) | Replikering er forsinket | ReplicationCollector | Medium |
| SQLI-REQ-001 | [SQLI-REQ-001.sql](SQLI-REQ-001.sql) | Sætning har kørt længe | ActivityCollector | Medium |
| SQLI-SEC-001 | [SQLI-SEC-001.sql](SQLI-SEC-001.sql) | Ny forhøjet rettighed på serveren | PrivilegeCollector | High for sysadmin, securityadmin og CONTROL SERVER; Medium for de øvrige faste serverroller. |
| SQLI-SEC-002 | [SQLI-SEC-002.sql](SQLI-SEC-002.sql) | For mange personer og konti med sysadmin | PrivilegeCollector | Medium |
| SQLI-STAT-001 | [SQLI-STAT-001.sql](SQLI-STAT-001.sql) | Forældede statistikker på store tabeller | IndexCollector | Low |
| SQLI-STAT-002 | [SQLI-STAT-002.sql](SQLI-STAT-002.sql) | Automatisk statistik er slået fra | DatabaseCollector | Medium |
| SQLI-SVC-001 | [SQLI-SVC-001.sql](SQLI-SVC-001.sql) | En SQL Server-tjeneste, der skal starte automatisk, kører ikke | InstanceConfigurationCollector | High |
| SQLI-SYN-001 | [SQLI-SYN-001.sql](SQLI-SYN-001.sql) | Et syntetisk tjek fejler | SyntheticCheckRunner | High. |
| SQLI-SYN-002 | [SQLI-SYN-002.sql](SQLI-SYN-002.sql) | Et syntetisk tjek svarer langsomt | SyntheticCheckRunner | Medium. |
| SQLI-SYN-003 | [SQLI-SYN-003.sql](SQLI-SYN-003.sql) | TLS-certifikatet udløber snart | SyntheticCheckRunner (HTTPS, ingen SQL) | Medium; High inden for CertificateCriticalDays; Critical når det er udløbet. |
| SQLI-TEMP-001 | [SQLI-TEMP-001.sql](SQLI-TEMP-001.sql) | Version store i tempdb er stor eller vokser | TempdbCollector | Medium |
| SQLI-TEMP-002 | [SQLI-TEMP-002.sql](SQLI-TEMP-002.sql) | tempdb er næsten fuld | TempdbCollector | High |
| SQLI-TEMP-003 | [SQLI-TEMP-003.sql](SQLI-TEMP-003.sql) | Session bruger meget tempdb i lang tid | TempdbCollector | Medium |
| SQLI-TRAN-001 | [SQLI-TRAN-001.sql](SQLI-TRAN-001.sql) | Transaktion har været åben længe | ActivityCollector | Medium; High når den har været åben 6 x grænsen. |
| SQLI-TRAN-002 | [SQLI-TRAN-002.sql](SQLI-TRAN-002.sql) | Transaktion bruger meget transaktionslog | ActivityCollector | Medium |
| SQLI-VER-001 | [SQLI-VER-001.sql](SQLI-VER-001.sql) | SQL Server-versionens supportstatus | InstanceProbe | Critical når extended support er udløbet; High når mainstream er udløbet og der er højst 365 dage til extended support slutter; ellers Medium i extended support. |
| SQLI-VER-002 | [SQLI-VER-002.sql](SQLI-VER-002.sql) | Instansen mangler opdateringer | InstanceProbe | High når versionen stadig er i support; Medium når den er ude af support (så er SQLI-VER-001 det vigtigste fund). |
| SQLI-WAIT-001 | [SQLI-WAIT-001.sql](SQLI-WAIT-001.sql) | SQL Server løber tør for worker threads | WaitStatsCollector | Critical |
| SQLI-WAIT-002 | [SQLI-WAIT-002.sql](SQLI-WAIT-002.sql) | Tasks venter på CPU (høj signal wait) | WaitStatsCollector | Medium; High fra 40 %. |
| SQLI-WAIT-003 | [SQLI-WAIT-003.sql](SQLI-WAIT-003.sql) | Sessioner venter meget på låse | WaitStatsCollector | Medium; High fra 5 ventende tasks i gennemsnit. |
| SQLI-WAIT-004 | [SQLI-WAIT-004.sql](SQLI-WAIT-004.sql) | Forespørgsler venter meget på at læse data fra disk | WaitStatsCollector | Medium; High fra 8 ventende tasks i gennemsnit. |
| SQLI-WAIT-005 | [SQLI-WAIT-005.sql](SQLI-WAIT-005.sql) | Commits venter på langsom skrivning til transaktionsloggen | WaitStatsCollector | Medium; High fra 15 ms i gennemsnit. |
| SQLI-WAIT-006 | [SQLI-WAIT-006.sql](SQLI-WAIT-006.sql) | Parallelle forespørgsler venter meget på hinanden | WaitStatsCollector | Medium; High fra 16 ventende tasks i gennemsnit. |
| SQLI-WAIT-007 | [SQLI-WAIT-007.sql](SQLI-WAIT-007.sql) | Contention på sider i hukommelsen (PAGELATCH) | WaitStatsCollector | Medium; High fra 4 ventende tasks i gennemsnit. |
| SQLI-WIN-001 | [SQLI-WIN-001.sql](SQLI-WIN-001.sql) | CPU-forbruget er højt i længere tid | CimWindowsServerSource (Windows, ingen SQL) | High; Critical når gennemsnittet er 98 % eller derover. |
| SQLI-WIN-002 | [SQLI-WIN-002.sql](SQLI-WIN-002.sql) | Serveren har næsten ingen ledig hukommelse | CimWindowsServerSource (Windows, ingen SQL) | Medium; High under 2 % og under 256 MB. |
| SQLI-WIN-003 | [SQLI-WIN-003.sql](SQLI-WIN-003.sql) | Serveren pager hukommelse til disk | CimWindowsServerSource (Windows, ingen SQL) | Medium. |
| SQLI-WIN-004 | [SQLI-WIN-004.sql](SQLI-WIN-004.sql) | Den reserverede hukommelse (commit) er tæt på grænsen | CimWindowsServerSource (Windows, ingen SQL) | Medium; High ved 97 % eller derover. |
| SQLI-WIN-005 | [SQLI-WIN-005.sql](SQLI-WIN-005.sql) | Lidt ledig plads på et drev | CimWindowsServerSource (Windows, ingen SQL) | Medium under FreePercent, High under CriticalFreePercent, Critical under 1 GB eller 1 %. |
| SQLI-WIN-006 | [SQLI-WIN-006.sql](SQLI-WIN-006.sql) | Et drev bliver fuldt om få dage | CimWindowsServerSource (Windows, ingen SQL) | Medium; High inden for 7 dage; Critical inden for 2 dage. |
| SQLI-WIN-007 | [SQLI-WIN-007.sql](SQLI-WIN-007.sql) | En tjeneste, der skal starte automatisk, kører ikke | CimWindowsServerSource (Windows, ingen SQL) | Medium. |
| SQLI-WIN-008 | [SQLI-WIN-008.sql](SQLI-WIN-008.sql) | En overvåget tjeneste kører ikke | CimWindowsServerSource (Windows, ingen SQL) | High når tjenesten ikke kører; Medium når den ikke findes (eller ikke kan ses). |
| SQLI-WIN-009 | [SQLI-WIN-009.sql](SQLI-WIN-009.sql) | Fejl i Windows' hændelseslog | CimWindowsServerSource (Windows, ingen SQL) | High: disk/filsystem, ressourcer, niveau Critical, tjeneste stoppet 3+ gange. Medium: tjeneste stoppet, program gik ned, Dynamics AX. Low: almindelige fejl der gentager sig. (Uventet nedlukning: se SQLI-WIN-010.) |
| SQLI-WIN-010 | [SQLI-WIN-010.sql](SQLI-WIN-010.sql) | Serveren er genstartet | CimWindowsServerSource (Windows, ingen SQL) | High ved uventet genstart; Info ved genstart uden tegn på at den var uventet; Low når hændelsesloggen ikke kan læses. |
| SQLI-WIN-011 | [SQLI-WIN-011.sql](SQLI-WIN-011.sql) | Serveren er ikke genstartet i lang tid | CimWindowsServerSource (Windows, ingen SQL) | Low. |
| SQLI-WIN-012 | [SQLI-WIN-012.sql](SQLI-WIN-012.sql) | En planlagt opgave fejlede sidste gang, den kørte | CimWindowsServerSource (Windows, ingen SQL) | Medium. |
| SQLI-WIN-013 | [SQLI-WIN-013.sql](SQLI-WIN-013.sql) | En planlagt opgave kørte ikke, da den skulle | CimWindowsServerSource (Windows, ingen SQL) | Medium. |
| COM-AX-001 | [COM-AX-001.sql](COM-AX-001.sql) | Dynamics AX 3.0 er uden support fra Microsoft | CustomQueryCollector | Medium |
| COM-AX-002 | [COM-AX-002.sql](COM-AX-002.sql) | Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0 | CustomQueryCollector | Medium |
| COM-AX-003 | [COM-AX-003.sql](COM-AX-003.sql) | Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014) | CustomQueryCollector | Medium |
| COM-AX-004 | [COM-AX-004.sql](COM-AX-004.sql) | Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere) | CustomQueryCollector | Medium |
| COM-AX-005 | [COM-AX-005.sql](COM-AX-005.sql) | Dynamics AX-tabeller uden unikt RecId-indeks | CustomQueryCollector | Medium |
| COM-AX-006 | [COM-AX-006.sql](COM-AX-006.sql) | Store Dynamics AX-tabeller uden clustered index (heaps) | CustomQueryCollector | Medium |
| COM-AX-007 | [COM-AX-007.sql](COM-AX-007.sql) | Automatiske statistikker er slået fra i en Dynamics AX-database | CustomQueryCollector | High |
| COM-CFG-001 | [COM-CFG-001.sql](COM-CFG-001.sql) | Default trace er slået fra | CustomQueryCollector | Low |
| COM-DB-001 | [COM-DB-001.sql](COM-DB-001.sql) | Fremmednøgler, som SQL Server ikke stoler på | CustomQueryCollector | Low |
| COM-FT-001 | [COM-FT-001.sql](COM-FT-001.sql) | Fulltext-katalog er sat på pause, fordi disken er fuld | CustomQueryCollector | High |
| COM-FT-002 | [COM-FT-002.sql](COM-FT-002.sql) | Fulltext-katalog er sat på pause eller drosles | CustomQueryCollector | Medium |
| COM-FT-003 | [COM-FT-003.sql](COM-FT-003.sql) | Fulltext-indeks uden automatisk opdatering er ikke opdateret i over en uge | CustomQueryCollector | Low |
| COM-FT-004 | [COM-FT-004.sql](COM-FT-004.sql) | Fulltext-indeks uden automatisk opdatering er ikke opdateret i over 30 dage | CustomQueryCollector | Medium |
| COM-FT-005 | [COM-FT-005.sql](COM-FT-005.sql) | Fulltext-indeks er deaktiveret | CustomQueryCollector | Medium |
| COM-SEC-001 | [COM-SEC-001.sql](COM-SEC-001.sql) | Login'et sa er aktivt | CustomQueryCollector | Medium |
| COM-SEC-002 | [COM-SEC-002.sql](COM-SEC-002.sql) | Database ejes af et login, der ikke findes | CustomQueryCollector | Low |
## Collectors
| Collector | Fil | Interval | Versioner | Bruges af |
|---|---|---|---|---|
| [InstanceProbe](_collectors/InstanceProbe.sql) | `src/SQLInsight.Connectivity/InstanceProbe.cs` | én gang, når agenten forbinder til instansen | Alle (SQL Server 2000 og nyere) | 6 regler |
| [ServerCollector](_collectors/ServerCollector.sql) | `src/SQLInsight.Collectors/Instance/ServerCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:server) | Alle (SQL Server 2000 og nyere) | 5 regler |
| [ConfigurationCollector](_collectors/ConfigurationCollector.sql) | `src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:configuration) | Alle (SQL Server 2000 og nyere) | 11 regler |
| [TraceFlagCollector](_collectors/TraceFlagCollector.sql) | `src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:trace-flags) | Alle (SQL Server 2000 og nyere) | 1 regel |
| [InstanceConfigurationCollector](_collectors/InstanceConfigurationCollector.sql) | `src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:instance-config) | Alle; tjenester og IFI fra SQL Server 2008 R2 SP1 | 2 regler |
| [PrivilegeCollector](_collectors/PrivilegeCollector.sql) | `src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:privileges) | SQL Server 2005 og nyere | 2 regler |
| [DatabaseCollector](_collectors/DatabaseCollector.sql) | `src/SQLInsight.Collectors/Databases/DatabaseCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:databases) | Alle (SQL Server 2000 og nyere); CHECKDB og VLF'er fra 2016 SP2; Query Store fra 2016 | 15 regler |
| [SuspectPageCollector](_collectors/SuspectPageCollector.sql) | `src/SQLInsight.Collectors/Instance/SuspectPageCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:suspect-pages) | SQL Server 2005 og nyere | 1 regel |
| [PlanCacheCollector](_collectors/PlanCacheCollector.sql) | `src/SQLInsight.Collectors/Instance/PlanCacheCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:plan-cache) | SQL Server 2005 og nyere | 1 regel |
| [WaitStatsCollector](_collectors/WaitStatsCollector.sql) | `src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) | Alle (SQL Server 2000 og nyere) | 8 regler |
| [FileIoCollector](_collectors/FileIoCollector.sql) | `src/SQLInsight.Collectors/Performance/FileIoCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:file-io) | SQL Server 2005 og nyere; drev fra 2008 R2 SP1 | 2 regler |
| [QueryStatsCollector](_collectors/QueryStatsCollector.sql) | `src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:query-stats) | SQL Server 2005 og nyere | 1 regel |
| [WaitSamplingCollector](_collectors/WaitSamplingCollector.sql) | `src/SQLInsight.Collectors/Performance/WaitSamplingCollector.cs` | 15 sek som standard (SQLInsight:CollectorIntervals:wait-sampling) | SQL Server 2005 og nyere | ingen regel |
| [LongOperationCollector](_collectors/LongOperationCollector.sql) | `src/SQLInsight.Collectors/Instance/LongOperationCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:long-operations) | SQL Server 2005 og nyere | 1 regel |
| [ErrorLogCollector](_collectors/ErrorLogCollector.sql) | `src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:error-log) | SQL Server 2005 og nyere | 6 regler |
| [TempdbCollector](_collectors/TempdbCollector.sql) | `src/SQLInsight.Collectors/Instance/TempdbCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:tempdb) | SQL Server 2005 og nyere | 3 regler |
| [ReplicationCollector](_collectors/ReplicationCollector.sql) | `src/SQLInsight.Collectors/Instance/ReplicationCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:replication) | SQL Server 2005 og nyere | 2 regler |
| [PerformanceCounterCollector](_collectors/PerformanceCounterCollector.sql) | `src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:performance-counters) | Alle (SQL Server 2000 og nyere) | 2 regler |
| [CpuCollector](_collectors/CpuCollector.sql) | `src/SQLInsight.Collectors/Performance/CpuCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:cpu) | SQL Server 2005 og nyere | 2 regler |
| [BlockingCollector](_collectors/BlockingCollector.sql) | `src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:blocking) | Alle (SQL Server 2000 og nyere) | 2 regler |
| [ActivityCollector](_collectors/ActivityCollector.sql) | `src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:activity) | SQL Server 2005 og nyere | 3 regler |
| [DeadlockCollector](_collectors/DeadlockCollector.sql) | `src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:deadlocks) | SQL Server 2008 og nyere (filer fra 2012, ellers ring buffer) | 1 regel |
| [QueryStoreCollector](_collectors/QueryStoreCollector.sql) | `src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs` | 30 min som standard (SQLInsight:CollectorIntervals:query-store) | SQL Server 2016 og nyere | 3 regler |
| [PlanAnalysisCollector](_collectors/PlanAnalysisCollector.sql) | `src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs` | 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis) | SQL Server 2005 SP2 og nyere | 5 regler |
| [IndexCollector](_collectors/IndexCollector.sql) | `src/SQLInsight.Collectors/Databases/IndexCollector.cs` | 6 timer som standard (SQLInsight:CollectorIntervals:indexes) | SQL Server 2005 og nyere | 5 regler |
| [AvailabilityGroupCollector](_collectors/AvailabilityGroupCollector.sql) | `src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:availability-groups) | SQL Server 2012 og nyere med Always On slået til | 3 regler |
| [AgentJobCollector](_collectors/AgentJobCollector.sql) | `src/SQLInsight.Collectors/Instance/AgentJobCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:agent-jobs) | SQL Server 2005 og nyere | 2 regler |
| [CustomQueryCollector](_collectors/CustomQueryCollector.sql) | `src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) | SQL Server 2005 og nyere (pr. regel: minimumVersion) | 16 regler |
| [AxDatabaseDetector](_collectors/AxDatabaseDetector.sql) | `src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs` | 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax); uden AX-læseadgang hvert 5. minut | SQL Server 2005 og nyere | 17 regler (alle AX-regler) |
| [AxBatchCollector](_collectors/AxBatchCollector.sql) | `src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch) | SQL Server 2005 og nyere; kræver valgfri AX-læseadgang | 4 regler |
| [AxSequenceCollector](_collectors/AxSequenceCollector.sql) | `src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs` | 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences) | SQL Server 2005 og nyere; kræver valgfri AX-læseadgang | 5 regler |
| [SyntheticCheckRunner](_collectors/SyntheticCheckRunner.sql) | `src/SQLInsight.Servers/Synthetic/SyntheticCheckRunner.cs` | tjekkets eget interval (standard 300 sek, mindst 60) og "Kør nu" | SQL Server 2005 og nyere | 2 regler (SQLI-SYN-001, SQLI-SYN-002) |

View file

@ -0,0 +1,85 @@
-- ===================================================================================================================
-- SQLI-AG-001: Always On availability group er ikke sund
-- ===================================================================================================================
-- Regel-id: SQLI-AG-001
-- Titel: Always On availability group er ikke sund
-- Alvor: High for DISCONNECTED, NOT_HEALTHY og suspenderet; Medium for PARTIALLY_HEALTHY.
-- Versioner: SQL Server 2012 og nyere med Always On slået til (IsHadrEnabled).
--
-- Hvad tjekkes, og hvorfor:
-- Om replikaer eller databaser i en availability group ikke er sunde: afbrudt, ikke synkroniseret eller
-- suspenderet.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund pr. replika med connected_state_desc = DISCONNECTED eller synchronization_health_desc
-- NOT_HEALTHY/PARTIALLY_HEALTHY, og pr. database-replika, der er suspenderet eller har synchronization_health_desc
-- NOT_HEALTHY/PARTIALLY_HEALTHY.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AvailabilityGroupCollector
-- Fil: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs
-- Medlemmer: AvailabilityGroupCollector.ReplicasSql, AvailabilityGroupCollector.DatabasesSql,
-- AvailabilityGroupCollector.ClusterMembersSql
-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:availability-groups)
-- Delt script: SQL Scripts/_collectors/AvailabilityGroupCollector.sql
-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : AvailabilityGroupHealthRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AvailabilityGroupCollector: Always On: replikaer, databasereplikaer (køer, hastigheder, commit-tid) og
-- Windows-clusterets medlemmer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere med Always On slået til
-- Kilde: AvailabilityGroupCollector.ReplicasSql
-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ReplicasSql
SELECT ag.name, ar.replica_server_name, ars.is_local, ars.role_desc, ars.connected_state_desc, ars.synchronization_health_desc,
ar.availability_mode_desc, ar.failover_mode_desc
FROM sys.availability_groups ag
JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id
LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id
GO
-- Variant: SQL Server 2012 og nyere med Always On slået til
-- Kilde: AvailabilityGroupCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.DatabasesSql
SELECT ag.name, DB_NAME(drs.database_id), ar.replica_server_name, drs.is_local,
CASE WHEN ars.role = 1 THEN 1 ELSE 0 END,
drs.synchronization_state_desc, drs.synchronization_health_desc, drs.is_suspended, drs.suspend_reason_desc,
drs.log_send_queue_size, drs.log_send_rate, drs.redo_queue_size, drs.redo_rate, drs.last_commit_time
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_replicas ar ON ar.replica_id = drs.replica_id
JOIN sys.availability_groups ag ON ag.group_id = drs.group_id
LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = drs.replica_id
GO
-- Variant: SQL Server 2012 og nyere med Always On slået til (tom uden Windows-cluster)
-- Kilde: AvailabilityGroupCollector.ClusterMembersSql
-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ClusterMembersSql
SELECT member_name, member_type_desc, member_state, member_state_desc, number_of_quorum_votes FROM sys.dm_hadr_cluster_members
GO

View file

@ -0,0 +1,84 @@
-- ===================================================================================================================
-- SQLI-AG-002: Sekundær replika er langt bagud
-- ===================================================================================================================
-- Regel-id: SQLI-AG-002
-- Titel: Sekundær replika er langt bagud
-- Alvor: Medium
-- Versioner: SQL Server 2012 og nyere med Always On slået til.
--
-- Hvad tjekkes, og hvorfor:
-- Om en sekundær replika er langt bagud: muligt datatab (commit-forsinkelse) eller lang failover-tid (redo queue).
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For hver sekundær, ikke suspenderet database-replika: muligt datatab = last_commit_time på den primære - på den
-- sekundære (sekunder); recovery-tid = redo_queue_size / redo_rate (sekunder). Fund hvis datatab >= 60 s eller
-- recovery-tid >= 600 s.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 60 sekunder (MaxDataLossSeconds) og 600 sekunder (MaxRecoverySeconds), fast i koden.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AvailabilityGroupCollector
-- Fil: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs
-- Medlemmer: AvailabilityGroupCollector.ReplicasSql, AvailabilityGroupCollector.DatabasesSql,
-- AvailabilityGroupCollector.ClusterMembersSql
-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:availability-groups)
-- Delt script: SQL Scripts/_collectors/AvailabilityGroupCollector.sql
-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : AvailabilityGroupLagRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AvailabilityGroupCollector: Always On: replikaer, databasereplikaer (køer, hastigheder, commit-tid) og
-- Windows-clusterets medlemmer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere med Always On slået til
-- Kilde: AvailabilityGroupCollector.ReplicasSql
-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ReplicasSql
SELECT ag.name, ar.replica_server_name, ars.is_local, ars.role_desc, ars.connected_state_desc, ars.synchronization_health_desc,
ar.availability_mode_desc, ar.failover_mode_desc
FROM sys.availability_groups ag
JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id
LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id
GO
-- Variant: SQL Server 2012 og nyere med Always On slået til
-- Kilde: AvailabilityGroupCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.DatabasesSql
SELECT ag.name, DB_NAME(drs.database_id), ar.replica_server_name, drs.is_local,
CASE WHEN ars.role = 1 THEN 1 ELSE 0 END,
drs.synchronization_state_desc, drs.synchronization_health_desc, drs.is_suspended, drs.suspend_reason_desc,
drs.log_send_queue_size, drs.log_send_rate, drs.redo_queue_size, drs.redo_rate, drs.last_commit_time
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_replicas ar ON ar.replica_id = drs.replica_id
JOIN sys.availability_groups ag ON ag.group_id = drs.group_id
LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = drs.replica_id
GO
-- Variant: SQL Server 2012 og nyere med Always On slået til (tom uden Windows-cluster)
-- Kilde: AvailabilityGroupCollector.ClusterMembersSql
-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ClusterMembersSql
SELECT member_name, member_type_desc, member_state, member_state_desc, number_of_quorum_votes FROM sys.dm_hadr_cluster_members
GO

View file

@ -0,0 +1,83 @@
-- ===================================================================================================================
-- SQLI-AG-003: En node eller et vidne i clusteret er nede
-- ===================================================================================================================
-- Regel-id: SQLI-AG-003
-- Titel: En node eller et vidne i clusteret er nede
-- Alvor: High
-- Versioner: SQL Server 2012 og nyere med Always On og et Windows Server Failover Cluster (tom ved CLUSTER_TYPE
-- NONE/EXTERNAL).
--
-- Hvad tjekkes, og hvorfor:
-- Om en node eller et vidne i Windows-clusteret under availability groups er nede, så et tab mere kan tage quorum.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. medlem af sys.dm_hadr_cluster_members med member_state <> 1 (UP).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AvailabilityGroupCollector
-- Fil: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs
-- Medlemmer: AvailabilityGroupCollector.ReplicasSql, AvailabilityGroupCollector.DatabasesSql,
-- AvailabilityGroupCollector.ClusterMembersSql
-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:availability-groups)
-- Delt script: SQL Scripts/_collectors/AvailabilityGroupCollector.sql
-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : ClusterMemberDownRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AvailabilityGroupCollector: Always On: replikaer, databasereplikaer (køer, hastigheder, commit-tid) og
-- Windows-clusterets medlemmer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere med Always On slået til
-- Kilde: AvailabilityGroupCollector.ReplicasSql
-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ReplicasSql
SELECT ag.name, ar.replica_server_name, ars.is_local, ars.role_desc, ars.connected_state_desc, ars.synchronization_health_desc,
ar.availability_mode_desc, ar.failover_mode_desc
FROM sys.availability_groups ag
JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id
LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id
GO
-- Variant: SQL Server 2012 og nyere med Always On slået til
-- Kilde: AvailabilityGroupCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.DatabasesSql
SELECT ag.name, DB_NAME(drs.database_id), ar.replica_server_name, drs.is_local,
CASE WHEN ars.role = 1 THEN 1 ELSE 0 END,
drs.synchronization_state_desc, drs.synchronization_health_desc, drs.is_suspended, drs.suspend_reason_desc,
drs.log_send_queue_size, drs.log_send_rate, drs.redo_queue_size, drs.redo_rate, drs.last_commit_time
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_replicas ar ON ar.replica_id = drs.replica_id
JOIN sys.availability_groups ag ON ag.group_id = drs.group_id
LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = drs.replica_id
GO
-- Variant: SQL Server 2012 og nyere med Always On slået til (tom uden Windows-cluster)
-- Kilde: AvailabilityGroupCollector.ClusterMembersSql
-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ClusterMembersSql
SELECT member_name, member_type_desc, member_state, member_state_desc, number_of_quorum_votes FROM sys.dm_hadr_cluster_members
GO

View file

@ -0,0 +1,130 @@
-- ===================================================================================================================
-- SQLI-ANOM-001: Usædvanlig belastning i forhold til det normale
-- ===================================================================================================================
-- Regel-id: SQLI-ANOM-001
-- Titel: Usædvanlig belastning i forhold til det normale
-- Alvor: CPU: Medium. Batch requests: Low ved stigning, Medium ved fald. Page life expectancy: Medium.
-- Versioner: SQL Server 2005 og nyere (CPU); tællerne fra 2000.
--
-- Hvad tjekkes, og hvorfor:
-- Om belastningen er usædvanlig i forhold til, hvad der er normalt for netop denne time på ugen.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Baseline beregnes af agenten i C# fra dens EGET repository (timegennemsnit de seneste 28 dage for samme time på
-- ugen +/- 1 time, mindst 6 målepunkter) - ikke fra den overvågede server. CPU: gns. af de seneste 15 minutters
-- ProcessUtilization >= 50 % og >= middel + max(3 x std.afv., 20). Batch requests/s (forskel mellem to
-- tællermålinger): >= 100 og >= middel + max(3 x std.afv., middel) giver stigning; middel >= 50 og nu <= 30 % af
-- middel giver fald. Page life expectancy: middel >= 300 s og nu <= 30 % af middel.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Fast i koden (AnomalyRule og BaselineCalculator.MinSamples = 6).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CpuCollector
-- Fil: src/SQLInsight.Collectors/Performance/CpuCollector.cs
-- Medlemmer: CpuCollector.Sql, CpuCollector.HostPlatformSql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:cpu)
-- Delt script: SQL Scripts/_collectors/CpuCollector.sql
-- PerformanceCounterCollector
-- Fil: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs
-- Medlemmer: Db.StartTimeSql(version), PerformanceCounterCollector.BuildSql(dmv: true),
-- PerformanceCounterCollector.BuildSql(dmv: false)
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:performance-counters)
-- Delt script: SQL Scripts/_collectors/PerformanceCounterCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/AnomalyRule.cs : AnomalyRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt lokale variabler, SET-sessionsindstillinger. Intet ændrer data, indstillinger
-- eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CpuCollector: SQL Servers og systemets CPU-forbrug pr. minut (scheduler monitor ring buffer, op til 60 minutter).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CpuCollector.Sql
-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.Sql
DECLARE @now_ticks bigint;
SELECT @now_ticks = ms_ticks FROM sys.dm_os_sys_info;
SELECT TOP (60)
DATEADD(second, CAST((rb.[timestamp] - @now_ticks) / 1000 AS int), GETUTCDATE()),
rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int'),
rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int')
FROM (
SELECT [timestamp], CONVERT(xml, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE N'%<SystemHealth>%'
) rb
ORDER BY rb.[timestamp] DESC
GO
-- Variant: SQL Server 2017 og nyere (Windows eller Linux)
-- Kilde: CpuCollector.HostPlatformSql
-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.HostPlatformSql
SELECT host_platform FROM sys.dm_os_host_info
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PerformanceCounterCollector: Udvalgte performance counters: memory grants, page life expectancy, forbindelser,
-- blokerede processer, batch requests og kompileringer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: PerformanceCounterCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: PerformanceCounterCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt
-- Kilde: PerformanceCounterCollector.StartTimeSql(2000)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: PerformanceCounterCollector.BuildSql(dmv)
-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: true)
SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint)
FROM sys.dm_os_performance_counters
WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding'))
OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy')
OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked'))
OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec'))
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: PerformanceCounterCollector.BuildSql(sysperfinfo)
-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: false)
SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint)
FROM master.dbo.sysperfinfo
WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding'))
OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy')
OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked'))
OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec'))
GO

159
SQL Scripts/SQLI-AX-001.sql Normal file
View file

@ -0,0 +1,159 @@
-- ===================================================================================================================
-- SQLI-AX-001: Dynamics AX-batchjob er endt med fejl
-- ===================================================================================================================
-- Regel-id: SQLI-AX-001
-- Titel: Dynamics AX-batchjob er endt med fejl
-- Alvor: High
-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/
-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Et batchjob i Dynamics AX er stoppet med fejl. Det, jobbet skulle gøre (bogføring, lagerafslutning, integration
-- ...), er ikke gjort eller kun delvist, og gentagne job fortsætter ikke nødvendigvis af sig selv.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Et fund pr. job, når jobbet nu har status Fejl og sluttede (eller var planlagt) inden for grænsen, eller når jobbet
-- venter på næste gentagelse, men dets seneste kørsel i SQLInsights egen historik (insight.AxBatchRuns) endte med
-- fejl inden for grænsen.
-- STATUS oversættes af versionens adapter: AX 2009+ 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet,
-- 5 Klar, 6 Ikke kørt, 7 Annullerer, 8 Annulleret; Axapta 3.0/4.0 samme rækkefølge (1 og 4 kontrolleret mod kundedata 2026-10-03):
-- 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet. Højst 25 fund pr. database.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:DynamicsAx:BatchFailedLookbackHours = 24: fejl inden for så mange timer rapporteres.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger):
-- Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [bruger];
-- AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [bruger];
-- AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [bruger];
-- GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [bruger];
-- Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → AX-databaser
-- og GET /api/v1/dynamics-ax/grant-script). Uden GRANT ændres intet.
-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en
-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- AxBatchCollector
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs
-- Medlemmer: AxSchemaAdapter.BatchJobs(database), AxSchemaAdapter.BatchHistory(database, afterRecId), AxSql.InDatabase
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch)
-- Delt script: SQL Scripts/_collectors/AxBatchCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBatchFailedRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(3.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(4.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(2009)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
-- Kilde: AxSchemaAdapter.BatchHistory(2009, første)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet)
-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (3000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(2012)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
-- Kilde: AxSchemaAdapter.BatchHistory(2012, første)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet)
-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (3000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO

163
SQL Scripts/SQLI-AX-002.sql Normal file
View file

@ -0,0 +1,163 @@
-- ===================================================================================================================
-- SQLI-AX-002: Dynamics AX-batchjob kører længere end normalt
-- ===================================================================================================================
-- Regel-id: SQLI-AX-002
-- Titel: Dynamics AX-batchjob kører længere end normalt
-- Alvor: Medium; High, når kørslen er dobbelt så lang som grænsen.
-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/
-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Et kørende batchjob har kørt meget længere end jobbets egne seneste kørsler. Det kan skyldes blokering, en ændret
-- datamængde, en dårlig plan, eller at jobbet hænger - og imens kan det holde låse, som brugerne venter på.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For job med status Udfører: kørt = nu - faktisk start (AX 2009+: BATCHJOB.STARTDATETIME; Axapta 3.0/4.0 gemmer
-- ikke den faktiske start, så det er første gang, SQLInsight så jobbet køre). Grænse = max(faktor x 90 %-fraktil af
-- jobbets seneste op til 20 vellykkede kørsler, minimum). Historikken er SQLInsights egen (insight.AxBatchRuns): fra
-- BATCHJOBHISTORY i AX 2009+, ellers de kørsler, SQLInsight selv ser blive afsluttet. Hænger jobbet (SQLI-AX-004),
-- gives kun det fund.
-- STATUS oversættes af versionens adapter: AX 2009+ 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet,
-- 5 Klar, 6 Ikke kørt, 7 Annullerer, 8 Annulleret; Axapta 3.0/4.0 samme rækkefølge (1 og 4 kontrolleret mod kundedata 2026-10-03):
-- 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet. Højst 25 fund pr. database.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:DynamicsAx:BatchLongRunningFactor = 2.0: kørt mere end så mange gange jobbets 90 %-fraktil
-- SQLInsight:DynamicsAx:BatchLongRunningMinimumMinutes = 10: men aldrig under så mange minutter
-- SQLInsight:DynamicsAx:BatchHistoryMinimumRuns = 5: så mange tidligere kørsler skal der være
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger):
-- Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [bruger];
-- AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [bruger];
-- AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [bruger];
-- GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [bruger];
-- Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → AX-databaser
-- og GET /api/v1/dynamics-ax/grant-script). Uden GRANT ændres intet.
-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en
-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- AxBatchCollector
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs
-- Medlemmer: AxSchemaAdapter.BatchJobs(database), AxSchemaAdapter.BatchHistory(database, afterRecId), AxSql.InDatabase
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch)
-- Delt script: SQL Scripts/_collectors/AxBatchCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBatchLongRunningRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(3.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(4.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(2009)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
-- Kilde: AxSchemaAdapter.BatchHistory(2009, første)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet)
-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (3000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(2012)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
-- Kilde: AxSchemaAdapter.BatchHistory(2012, første)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet)
-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (3000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO

164
SQL Scripts/SQLI-AX-003.sql Normal file
View file

@ -0,0 +1,164 @@
-- ===================================================================================================================
-- SQLI-AX-003: Dynamics AX-batchjob er ikke startet til tiden
-- ===================================================================================================================
-- Regel-id: SQLI-AX-003
-- Titel: Dynamics AX-batchjob er ikke startet til tiden
-- Alvor: Medium; High, når jobbet i praksis aldrig er startet.
-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/
-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Et ventende batchjob, hvis planlagte start er passeret: batchserveren (AOS med batch, eller batch-klienten i
-- Axapta 3.0) tager det ikke. Mange forsinkede job på én gang betyder typisk, at batchserveren slet ikke kører.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For job med status Venter: forsinkelse = nu - planlagt start. Planlagt start er i AX 2009+ BATCHJOB.ORIGSTARTDATETIME
-- (UTC). I Axapta 3.0/4.0 er den BATCH.STARTDATE (lokal dato) + STARTTIME (sekunder efter midnat, lokal tid); den
-- regnes om til UTC med AX-tidszonen (SQLInsight:DynamicsAx:LocalTimeZone, ellers agentens tidszone) og de
-- sommertidsregler, der gjaldt på datoen. AX' tomme dato (1900-01-01) er "ingen". Job med status Tilbageholdt er
-- ikke forsinkede. De mest forsinkede først.
-- STATUS oversættes af versionens adapter: AX 2009+ 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet,
-- 5 Klar, 6 Ikke kørt, 7 Annullerer, 8 Annulleret; Axapta 3.0/4.0 samme rækkefølge: 0 Tilbageholdt, 1 Venter,
-- 2 Udfører, 3 Fejl, 4 Afsluttet (1 og 4 kontrolleret mod kundedata 2026-10-03). Højst 25 fund pr. database.
-- Job planlagt for mere end BatchAbandonedDays siden regnes som opgivne rækker og meldes ikke.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:DynamicsAx:BatchLateMinutes = 30: forsinket mere end så mange minutter: Medium
-- SQLInsight:DynamicsAx:BatchNeverStartedHours = 24: forsinket mere end så mange timer: High, "aldrig startet"
-- SQLInsight:DynamicsAx:BatchAbandonedDays = 30: planlagt for mere end så mange dage siden: opgivet, ingen fund
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger):
-- Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [bruger];
-- AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [bruger];
-- AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [bruger];
-- GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [bruger];
-- Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → AX-databaser
-- og GET /api/v1/dynamics-ax/grant-script). Uden GRANT ændres intet.
-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en
-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- AxBatchCollector
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs
-- Medlemmer: AxSchemaAdapter.BatchJobs(database), AxSchemaAdapter.BatchHistory(database, afterRecId), AxSql.InDatabase
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch)
-- Delt script: SQL Scripts/_collectors/AxBatchCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBatchLateRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(3.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(4.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(2009)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
-- Kilde: AxSchemaAdapter.BatchHistory(2009, første)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet)
-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (3000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(2012)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
-- Kilde: AxSchemaAdapter.BatchHistory(2012, første)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet)
-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (3000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO

163
SQL Scripts/SQLI-AX-004.sql Normal file
View file

@ -0,0 +1,163 @@
-- ===================================================================================================================
-- SQLI-AX-004: Dynamics AX-batchjob hænger i Udfører
-- ===================================================================================================================
-- Regel-id: SQLI-AX-004
-- Titel: Dynamics AX-batchjob hænger i Udfører
-- Alvor: High
-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/
-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Et batchjob har haft status Udfører i flere timer. Typisk er AOS'en eller batch-klienten genstartet eller gået ned
-- midt i kørslen, så jobbet aldrig blev afsluttet: det står som kørende, intet sker, og gentagne job planlægges ikke
-- igen.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For job med status Udfører: kørt = nu - faktisk start (AX 2009+: BATCHJOB.STARTDATETIME; Axapta 3.0/4.0: første
-- gang, SQLInsight så jobbet køre, så det kan have kørt længere). Fund, når kørt >= BatchStuckHours, medmindre jobbet
-- efter sin egen historik (insight.AxBatchRuns, mindst BatchHistoryMinimumRuns kørsler) normalt kører så længe
-- (kørt <= faktor x 90 %-fraktil).
-- STATUS oversættes af versionens adapter: AX 2009+ 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet,
-- 5 Klar, 6 Ikke kørt, 7 Annullerer, 8 Annulleret; Axapta 3.0/4.0 samme rækkefølge (1 og 4 kontrolleret mod kundedata 2026-10-03):
-- 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet. Højst 25 fund pr. database.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:DynamicsAx:BatchStuckHours = 6: kørt mindst så mange timer
-- SQLInsight:DynamicsAx:BatchLongRunningFactor = 2.0: ...og mere end så mange gange jobbets 90 %-fraktil, når der er
-- historik
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger):
-- Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [bruger];
-- AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [bruger];
-- AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [bruger];
-- GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [bruger];
-- Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → AX-databaser
-- og GET /api/v1/dynamics-ax/grant-script). Uden GRANT ændres intet.
-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en
-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- AxBatchCollector
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs
-- Medlemmer: AxSchemaAdapter.BatchJobs(database), AxSchemaAdapter.BatchHistory(database, afterRecId), AxSql.InDatabase
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch)
-- Delt script: SQL Scripts/_collectors/AxBatchCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBatchStuckRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(3.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(4.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(2009)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
-- Kilde: AxSchemaAdapter.BatchHistory(2009, første)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet)
-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (3000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
-- Kilde: AxSchemaAdapter.BatchJobs(2012)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
-- Kilde: AxSchemaAdapter.BatchHistory(2012, første)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet)
-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (3000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO

131
SQL Scripts/SQLI-AX-005.sql Normal file
View file

@ -0,0 +1,131 @@
-- ===================================================================================================================
-- SQLI-AX-005: Dynamics AX-nummerserie er ved at løbe tør
-- ===================================================================================================================
-- Regel-id: SQLI-AX-005
-- Titel: Dynamics AX-nummerserie er ved at løbe tør
-- Alvor: Medium; High over den kritiske procent eller inden for de kritiske dage; Critical, når serien er opbrugt.
-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/
-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Når en nummerserie er brugt op, kan AX ikke tildele flere numre: oprettelse eller bogføring af fx ordrer, fakturaer
-- eller posteringer, der bruger serien, fejler for alle brugere.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Grænse = HIGHEST, eller formatets kapacitet (10^antal # - 1), hvis den er lavere. Brugt % = (NEXTREC - LOWEST) /
-- (grænse - LOWEST + 1). Tilbage = grænse - NEXTREC + 1. Prognose: forbrug pr. dag = (NEXTREC nu - NEXTREC i den
-- ældste daglige måling i vinduet) / dage imellem; dage tilbage = (grænse - NEXTREC) / forbrug pr. dag. Målingerne er
-- SQLInsights egne (insight.AxSequenceSamples, én pr. serie pr. dag). Manuelle, cykliske og spærrede serier springes
-- over. Højst 25 fund pr. database. Tal: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : Interpret, AxForecast.From.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:DynamicsAx:NumberSequenceWarnPercent = 80: brugt mindst så mange procent: Medium
-- SQLInsight:DynamicsAx:NumberSequenceCriticalPercent = 95: High
-- SQLInsight:DynamicsAx:ForecastWarnDays = 90: løber tør inden for så mange dage efter prognosen: Medium
-- SQLInsight:DynamicsAx:ForecastCriticalDays = 30: High
-- SQLInsight:DynamicsAx:ForecastBasisDays = 30: prognosen bruger den ældste daglige måling inden for så mange dage
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger):
-- Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [bruger];
-- GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [bruger]; -- valgfri: om serien bruges
-- AX 4.0/2009: de samme tabeller i [dbo]
-- AX 2012/F&O: desuden GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [bruger]; -- valgfri: firma
-- Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet.
-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en
-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- AxSequenceCollector
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs
-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase
-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences)
-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxNumberSequenceExhaustionRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(3.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(4.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(2009)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(2012)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO

124
SQL Scripts/SQLI-AX-006.sql Normal file
View file

@ -0,0 +1,124 @@
-- ===================================================================================================================
-- SQLI-AX-006: Dynamics AX-nummerseriens format er for kort til Højeste
-- ===================================================================================================================
-- Regel-id: SQLI-AX-006
-- Titel: Dynamics AX-nummerseriens format er for kort til Højeste
-- Alvor: Medium; High, når NEXTREC allerede er større end formatets kapacitet.
-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/
-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Formatet (fx "FA-####") har færre cifre, end HIGHEST kræver. Når serien når forbi formatets kapacitet, fejler
-- tildelingen af numre, selv om serien ikke er brugt op.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Kapacitet = 10^(antal # i FORMAT) - 1. Fund, når HIGHEST > kapacitet (ikke for manuelle eller spærrede serier). High,
-- når også NEXTREC > kapacitet. Højst 25 fund pr. database.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle: ren sammenligning.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger):
-- Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [bruger];
-- GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [bruger]; -- valgfri: om serien bruges
-- AX 4.0/2009: de samme tabeller i [dbo]
-- AX 2012/F&O: desuden GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [bruger]; -- valgfri: firma
-- Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet.
-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en
-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- AxSequenceCollector
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs
-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase
-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences)
-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxNumberSequenceFormatRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(3.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(4.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(2009)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(2012)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO

125
SQL Scripts/SQLI-AX-007.sql Normal file
View file

@ -0,0 +1,125 @@
-- ===================================================================================================================
-- SQLI-AX-007: Spærret Dynamics AX-nummerserie bruges stadig
-- ===================================================================================================================
-- Regel-id: SQLI-AX-007
-- Titel: Spærret Dynamics AX-nummerserie bruges stadig
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/
-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Serien er spærret (BLOCKED = 1), men en nummerseriereference peger stadig på den. Alt i AX, der skal have et nummer
-- fra den reference, fejler med en fejl om, at nummerserien er spærret.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund, når BLOCKED = 1 og der findes en NUMBERSEQUENCEREFERENCE til serien (Axapta 3.0/4.0/2009: samme NUMBERSEQUENCE
-- og DATAAREAID; AX 2012/F&O: NUMBERSEQUENCEID = seriens RECID). Kan referencetabellen ikke læses, vides det ikke, og
-- der gives intet fund. Højst 25 fund pr. database.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen konfigurationsnøgle.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger):
-- Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [bruger];
-- GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [bruger]; -- valgfri: om serien bruges
-- AX 4.0/2009: de samme tabeller i [dbo]
-- AX 2012/F&O: desuden GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [bruger]; -- valgfri: firma
-- Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet.
-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en
-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- AxSequenceCollector
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs
-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase
-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences)
-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxNumberSequenceBlockedRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(3.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(4.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(2009)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(2012)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO

127
SQL Scripts/SQLI-AX-008.sql Normal file
View file

@ -0,0 +1,127 @@
-- ===================================================================================================================
-- SQLI-AX-008: Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op
-- ===================================================================================================================
-- Regel-id: SQLI-AX-008
-- Titel: Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op
-- Alvor: Low
-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/
-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- En kontinuerlig serie må ikke have huller. Numre reserveret af afbrudte sessioner bliver liggende som optagede, til
-- serien ryddes op; uden oprydning opstår huller og fejl, og serien bliver en flaskehals, fordi hver tildeling låser
-- rækken i NUMBERSEQUENCETABLE, til transaktionen er færdig.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For kontinuerlige (CONTINUOUS = 1), ikke spærrede, ikke manuelle serier, der er taget i brug (NEXTREC > LOWEST):
-- fund, når seneste oprydning er ældre end grænsen eller aldrig er sket (1900-01-01). Seneste oprydning er
-- LATESTCLEANDATETIME (UTC) i AX 2009+, og LATESTCLEANDATE + LATESTCLEANTIME (lokal tid, sekunder efter midnat) i
-- Axapta 3.0. Mangler kolonnen, gives intet fund. Højst 25 fund pr. database.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 7 dage (fast, AxNumberSequenceContinuousRule.MinimumDays), eller 2 x seriens eget CLEANINTERVAL (timer), hvis længere.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger):
-- Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [bruger];
-- GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [bruger]; -- valgfri: om serien bruges
-- AX 4.0/2009: de samme tabeller i [dbo]
-- AX 2012/F&O: desuden GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [bruger]; -- valgfri: firma
-- Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet.
-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en
-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- AxSequenceCollector
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs
-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase
-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences)
-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxNumberSequenceContinuousRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(3.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(4.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(2009)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
-- Kilde: AxSchemaAdapter.NumberSequences(2012)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO

130
SQL Scripts/SQLI-AX-009.sql Normal file
View file

@ -0,0 +1,130 @@
-- ===================================================================================================================
-- SQLI-AX-009: Dynamics AX RecId er ved at løbe tør
-- ===================================================================================================================
-- Regel-id: SQLI-AX-009
-- Titel: Dynamics AX RecId er ved at løbe tør
-- Alvor: Axapta 3.0 (32-bit): High fra advarselsgrænsen, Critical fra den kritiske grænse; 64-bit: High; altid Critical, når RecId er opbrugt.
-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/
-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- Når en tabels RecId-serie er brugt op, kan AX ikke oprette flere poster. Axapta 3.0 har 32-bit RecId (int, højst
-- 2.147.483.647 pr. tabel og firma), og det løses kun med et stort projekt - derfor advares langt tidligere for 3.0.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Grænse = typens maksimum (int, når SYSTEMSEQUENCES.NEXTVAL er int som i Axapta 3.0; ellers bigint), eller MAXVAL,
-- hvis den er sat lavere. Brugt % = NEXTVAL / grænse. Prognose ud fra SQLInsights egne daglige målinger af NEXTVAL
-- (insight.AxSequenceSamples). Højst 25 fund pr. database, de mest brugte først. Tal: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : Interpret(AxRecIdRow).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:DynamicsAx:RecId32WarnPercent = 50: Axapta 3.0: High fra så mange procent af int-intervallet
-- SQLInsight:DynamicsAx:RecId32CriticalPercent = 75: Axapta 3.0: Critical
-- SQLInsight:DynamicsAx:RecId32ForecastDays = 730: Axapta 3.0: High, hvis den løber tør inden for så mange dage;
-- Critical inden for halvdelen
-- SQLInsight:DynamicsAx:RecId64WarnPercent = 80: 64-bit (eller sænket MAXVAL): High
-- SQLInsight:DynamicsAx:ForecastWarnDays = 90: 64-bit: High, hvis den løber tør inden for så mange dage
-- SQLInsight:DynamicsAx:ForecastBasisDays = 30: prognosen bruger den ældste daglige måling inden for så mange dage
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger):
-- Axapta 3.0: GRANT SELECT ON [bmssa].[SYSTEMSEQUENCES] TO [bruger];
-- GRANT SELECT ON [bmssa].[SQLDICTIONARY] TO [bruger]; -- valgfri: tabelnavne
-- AX 4.0/2009/2012/F&O: de samme tabeller i [dbo]
-- Mangler SYSTEMSEQUENCES, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet.
-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en
-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- AxSequenceCollector
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs
-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase
-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences)
-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxRecIdExhaustionRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxSequenceCollector: RecId pr. tabel og firma i AX-databasen (valgfri AX-læseadgang)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): RecId pr. tabel (SYSTEMSEQUENCES)
-- Kilde: AxSchemaAdapter.RecIds(3.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [bmssa].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [bmssa].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES)
-- Kilde: AxSchemaAdapter.RecIds(4.0)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES)
-- Kilde: AxSchemaAdapter.RecIds(2009)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES)
-- Kilde: AxSchemaAdapter.RecIds(2012)
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO

168
SQL Scripts/SQLI-AX-010.sql Normal file
View file

@ -0,0 +1,168 @@
-- ===================================================================================================================
-- SQLI-AX-010: AX-bruger blokerer andre
-- ===================================================================================================================
-- Regel-id: SQLI-AX-010
-- Titel: AX-bruger blokerer andre
-- Alvor: Medium; High ved ventetid over 5 minutter eller mindst 10 blokerede sessioner.
-- Versioner: Blokering: SQL Server 2000 og nyere; AX-genkendelsen og dermed reglen: SQL Server 2005 og nyere.
-- Axapta 3.0/4.0: AX-brugeren kan ikke ses. AX 2009/2012/F&O: baseret på Microsofts dokumentation - ikke
-- verificeret mod kundedata.
--
-- Hvad tjekkes, og hvorfor:
-- En session i en Dynamics AX-database holder låse, som andre AX-brugere eller batchjob venter på. Alle AX-brugere går
-- gennem AOS'ens login, så SQL Server-login'et siger ikke, hvem det er. Reglen viser AX-brugeren, når SQL Server kan
-- se den.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For hver head blocker, hvis egen database eller en blokeret sessions database er genkendt som AX: fund, når den
-- længste ventetid i kæden er over grænsen. AX 2009/2012/F&O: AX-brugeren og AX-sessionen læses fra CONTEXT_INFO,
-- som AOS'en sætter, når "connectioncontext" er slået til i AOS-konfigurationen (formatet er dokumenteret af
-- Microsoft for AX 2012 - ikke verificeret mod kundedata). Axapta 3.0 og AX 4.0 sætter aldrig CONTEXT_INFO:
-- brugeren kan ikke ses fra SQL Server, og fundet siger det ærligt. AX-bruger, vært, program og CONTEXT_INFO er
-- følsomme målinger (sendes ikke til SQLInsight Hub uden SendSensitiveEvidence). Brugeren findes af
-- src/SQLInsight.Core/DynamicsAx/AxSessionMapper.cs. AX-tabeller læses ikke (og aldrig med NOLOCK).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:DynamicsAx:BlockingSeconds = 30: den længste ventetid bag sessionen skal være mindst så mange sekunder.
-- High fra 300.000 ms eller 10 blokerede (fast i koden, som SQLI-BLK-001).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
-- Ingen AX-læseadgang: kun grant-least-privilege.sql.
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019 (sessionerne)
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- AX-genkendelsen (metadata)
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; før 2014 en databasebruger i AX-databasen
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AxDatabaseDetector (den fælles AX-genkendelse)
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
-- BlockingCollector
-- Fil: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs
-- Medlemmer: BlockingCollector.BuildSql2005(version), BlockingCollector.Sql2000
-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:blocking)
-- Delt script: SQL Scripts/_collectors/BlockingCollector.sql
-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBlockingUserRule
--
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
-- overvågning.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
-- Kilde: AxDatabaseDetector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
ORDER BY name
GO
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
-- Kilde: AxDatabaseDetector.BuildDetectionSql
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
ORDER BY s.name, t.name, c.column_id';
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- BlockingCollector: Blokerede sessioner og deres blokerere lige nu, med sætning, åbne transaktioner og CONTEXT_INFO.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere
-- Kilde: BlockingCollector.BuildSql2005(2012+)
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version)
SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource,
DB_NAME(COALESCE(r.database_id, s.database_id)), s.login_name, s.host_name, s.program_name, s.open_transaction_count,
LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL
THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1,
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
- r.statement_start_offset) / 2 + 1)
ELSE t.text END, 4000),
s.last_request_end_time, s.context_info
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id
LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id
OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t
WHERE r.blocking_session_id > 0
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
GO
-- Variant: SQL Server 2008 – 2008 R2
-- Kilde: BlockingCollector.BuildSql2005(2008)
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version)
SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource,
DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id),
LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL
THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1,
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
- r.statement_start_offset) / 2 + 1)
ELSE t.text END, 4000),
s.last_request_end_time, s.context_info
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id
LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id
OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t
WHERE r.blocking_session_id > 0
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
GO
-- Variant: SQL Server 2005 (CONTEXT_INFO fra sys.sysprocesses)
-- Kilde: BlockingCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version)
SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource,
DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id),
LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL
THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1,
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
- r.statement_start_offset) / 2 + 1)
ELSE t.text END, 4000),
s.last_request_end_time, (SELECT TOP 1 p.context_info FROM sys.sysprocesses p WHERE p.spid = s.session_id AND p.ecid = 0)
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id
LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id
OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t
WHERE r.blocking_session_id > 0
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: BlockingCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.Sql2000
SELECT p.spid, NULLIF(CASE WHEN p.blocked = p.spid THEN 0 ELSE p.blocked END, 0), RTRIM(p.status), RTRIM(p.lastwaittype),
p.waittime, RTRIM(p.waitresource), DB_NAME(p.dbid), RTRIM(p.loginame), RTRIM(p.hostname), RTRIM(p.program_name),
p.open_tran, CAST(NULL AS nvarchar(4000)), p.last_batch, p.context_info
FROM master.dbo.sysprocesses p
WHERE (p.blocked > 0 AND p.blocked <> p.spid)
OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid)
GO

View file

@ -0,0 +1,121 @@
-- ===================================================================================================================
-- SQLI-BLK-001: Langvarig blocking
-- ===================================================================================================================
-- Regel-id: SQLI-BLK-001
-- Titel: Langvarig blocking
-- Alvor: Medium; High når længste ventetid er mindst 5 minutter eller mindst 10 sessioner er blokeret.
-- Versioner: Alle (SQL Server 2000 via master.dbo.sysprocesses).
--
-- Hvad tjekkes, og hvorfor:
-- Om der er blocking-kæder, der har varet længe nok til at genere brugerne.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- SQL'en henter alle blokerede sessioner og deres blokerere hvert minut. I C# findes head blockers (blokerer andre,
-- er ikke selv blokeret); for hver følges hele kæden. Fund hvis den længst ventende i kæden har ventet mindst 30
-- sekunder. En head blocker, der er 'sleeping' med åben transaktion, beskrives særskilt.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 30.000 ms (BlockingRule.MinBlockedMs); High fra 300.000 ms eller 10 blokerede (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- BlockingCollector
-- Fil: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs
-- Medlemmer: BlockingCollector.BuildSql2005(version), BlockingCollector.Sql2000
-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:blocking)
-- Delt script: SQL Scripts/_collectors/BlockingCollector.sql
-- Regelkode: src/SQLInsight.Rules/Concurrency/ConcurrencyRules.cs : BlockingRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- BlockingCollector: Blokerede sessioner og deres blokerere lige nu, med sætning og åbne transaktioner.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere
-- Kilde: BlockingCollector.BuildSql2005(2012+)
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version)
SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource,
DB_NAME(COALESCE(r.database_id, s.database_id)), s.login_name, s.host_name, s.program_name, s.open_transaction_count,
LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL
THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1,
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
- r.statement_start_offset) / 2 + 1)
ELSE t.text END, 4000),
s.last_request_end_time, s.context_info
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id
LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id
OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t
WHERE r.blocking_session_id > 0
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
GO
-- Variant: SQL Server 2008 – 2008 R2
-- Kilde: BlockingCollector.BuildSql2005(2008)
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version)
SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource,
DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id),
LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL
THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1,
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
- r.statement_start_offset) / 2 + 1)
ELSE t.text END, 4000),
s.last_request_end_time, s.context_info
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id
LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id
OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t
WHERE r.blocking_session_id > 0
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
GO
-- Variant: SQL Server 2005 (CONTEXT_INFO fra sys.sysprocesses)
-- Kilde: BlockingCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version)
SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource,
DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id),
LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL
THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1,
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
- r.statement_start_offset) / 2 + 1)
ELSE t.text END, 4000),
s.last_request_end_time, (SELECT TOP 1 p.context_info FROM sys.sysprocesses p WHERE p.spid = s.session_id AND p.ecid = 0)
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id
LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id
OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t
WHERE r.blocking_session_id > 0
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: BlockingCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.Sql2000
SELECT p.spid, NULLIF(CASE WHEN p.blocked = p.spid THEN 0 ELSE p.blocked END, 0), RTRIM(p.status), RTRIM(p.lastwaittype),
p.waittime, RTRIM(p.waitresource), DB_NAME(p.dbid), RTRIM(p.loginame), RTRIM(p.hostname), RTRIM(p.program_name),
p.open_tran, CAST(NULL AS nvarchar(4000)), p.last_batch, p.context_info
FROM master.dbo.sysprocesses p
WHERE (p.blocked > 0 AND p.blocked <> p.spid)
OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid)
GO

View file

@ -0,0 +1,145 @@
-- ===================================================================================================================
-- SQLI-CFG-001: max server memory er ikke sat
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-001
-- Titel: max server memory er ikke sat
-- Alvor: High
-- Versioner: Alle (SQL Server 2000 og nyere). Ikke relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om 'max server memory (MB)' står på standardværdien, så SQL Server kan tage al hukommelse og presse Windows til
-- paging.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis value_in_use for 'max server memory (MB)' (ConfigurationCollector) er >= 2147483647. Den anbefalede
-- værdi i fundet beregnes af fysisk hukommelse (ServerCollector): max(fysisk / 2, fysisk - max(4096 MB, 10 % af
-- fysisk)).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Standardværdien 2147483647 (int.MaxValue). Reserve til Windows: det største af 4096 MB og 10 %
-- (MemoryGuidance.Reserve, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- ServerCollector
-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs
-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server)
-- Delt script: SQL Scripts/_collectors/ServerCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : MaxServerMemoryDefaultRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), SET-sessionsindstillinger. Intet ændrer
-- data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere
-- Kilde: ServerCollector.BuildSql(2012+)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_kb / 1024,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds
-- Kilde: ServerCollector.BuildSql(2008 R2 SP1)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 og 2008 R2 før SP1
-- Kilde: ServerCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2005
-- Kilde: ServerCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT create_date FROM sys.databases WHERE database_id = 2),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt
-- Kilde: ServerCollector.StartTimeSql(2000)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse
-- Kilde: ServerCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000
EXEC master.dbo.xp_msver
GO

View file

@ -0,0 +1,145 @@
-- ===================================================================================================================
-- SQLI-CFG-002: max server memory efterlader for lidt til Windows
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-002
-- Titel: max server memory efterlader for lidt til Windows
-- Alvor: Medium
-- Versioner: Alle, hvor fysisk hukommelse kan aflæses (SQL Server 2005+ via sys.dm_os_sys_info, 2000 via xp_msver).
-- Ikke relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om 'max server memory (MB)' er sat så højt, at der er for lidt tilbage til Windows, tråd-stacks og allokeringer
-- uden for buffer pool.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis max server memory < 2147483647 (standardværdien er SQLI-CFG-001) og fysisk hukommelse - max server
-- memory < halvdelen af reserven, hvor reserven er max(4096 MB, 10 % af fysisk). Fundet anbefaler max(fysisk / 2,
-- fysisk - reserven).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Reserve: max(4096 MB, 10 %) (MemoryGuidance.Reserve, fast i koden); fund under halvdelen af reserven.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- ServerCollector
-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs
-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server)
-- Delt script: SQL Scripts/_collectors/ServerCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : MaxServerMemoryTooHighRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), SET-sessionsindstillinger. Intet ændrer
-- data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere
-- Kilde: ServerCollector.BuildSql(2012+)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_kb / 1024,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds
-- Kilde: ServerCollector.BuildSql(2008 R2 SP1)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 og 2008 R2 før SP1
-- Kilde: ServerCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2005
-- Kilde: ServerCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT create_date FROM sys.databases WHERE database_id = 2),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt
-- Kilde: ServerCollector.StartTimeSql(2000)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse
-- Kilde: ServerCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000
EXEC master.dbo.xp_msver
GO

View file

@ -0,0 +1,142 @@
-- ===================================================================================================================
-- SQLI-CFG-003: cost threshold for parallelism står på standardværdien 5
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-003
-- Titel: cost threshold for parallelism står på standardværdien 5
-- Alvor: Medium
-- Versioner: Alle (SQL Server 2000 og nyere).
--
-- Hvad tjekkes, og hvorfor:
-- Om 'cost threshold for parallelism' står på standardværdien 5, så selv små forespørgsler kører parallelt.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis value_in_use for 'cost threshold for parallelism' = 5, 'max degree of parallelism' <> 1 og cpu_count
-- (ServerCollector) > 1. Fix-scriptet i fundet foreslår 50 (køres aldrig automatisk).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Standardværdien 5; anbefalet startværdi 50 (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- ServerCollector
-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs
-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server)
-- Delt script: SQL Scripts/_collectors/ServerCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : CostThresholdRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), SET-sessionsindstillinger. Intet ændrer
-- data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere
-- Kilde: ServerCollector.BuildSql(2012+)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_kb / 1024,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds
-- Kilde: ServerCollector.BuildSql(2008 R2 SP1)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 og 2008 R2 før SP1
-- Kilde: ServerCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2005
-- Kilde: ServerCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT create_date FROM sys.databases WHERE database_id = 2),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt
-- Kilde: ServerCollector.StartTimeSql(2000)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse
-- Kilde: ServerCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000
EXEC master.dbo.xp_msver
GO

View file

@ -0,0 +1,144 @@
-- ===================================================================================================================
-- SQLI-CFG-004: MAXDOP passer ikke til serverens NUMA-layout
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-004
-- Titel: MAXDOP passer ikke til serverens NUMA-layout
-- Alvor: Medium
-- Versioner: Alle (NUMA-noder tælles fra SQL Server 2008; på 2005/2000 regnes med én node).
--
-- Hvad tjekkes, og hvorfor:
-- Om MAXDOP passer til serverens NUMA-layout efter Microsofts vejledning.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- schedulers = antal VISIBLE ONLINE schedulers (ellers cpu_count); noder = antal forskellige parent_node_id (mindst
-- 1); pr. node = schedulers / noder. Anbefalet = min(8, pr. node) ved én node; ved flere noder pr. node hvis <= 16,
-- ellers min(16, pr. node / 2). Fund hvis schedulers > 1 og enten MAXDOP = 0 og anbefalet < schedulers, eller
-- MAXDOP > anbefalet.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Microsofts vejledning (MaxDopRule.Recommended, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- ServerCollector
-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs
-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server)
-- Delt script: SQL Scripts/_collectors/ServerCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : MaxDopRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), SET-sessionsindstillinger. Intet ændrer
-- data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere
-- Kilde: ServerCollector.BuildSql(2012+)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_kb / 1024,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds
-- Kilde: ServerCollector.BuildSql(2008 R2 SP1)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 og 2008 R2 før SP1
-- Kilde: ServerCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2005
-- Kilde: ServerCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT create_date FROM sys.databases WHERE database_id = 2),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt
-- Kilde: ServerCollector.StartTimeSql(2000)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse
-- Kilde: ServerCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000
EXEC master.dbo.xp_msver
GO

View file

@ -0,0 +1,89 @@
-- ===================================================================================================================
-- SQLI-CFG-005: Plan cache er fyldt med engangsplaner
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-005
-- Titel: Plan cache er fyldt med engangsplaner
-- Alvor: Low
-- Versioner: SQL Server 2005 og nyere (DynamicManagementViews; indstillingen findes fra 2008 - mangler den, er
-- reglen "ikke målbar").
--
-- Hvad tjekkes, og hvorfor:
-- Om plan cache er fyldt med ad hoc-planer, der kun er brugt én gang, mens 'optimize for ad hoc workloads' er slået
-- fra.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis 'optimize for ad hoc workloads' value_in_use = 0, og engangs-ad hoc-planer (objtype = Adhoc og
-- usecounts = 1) fylder mindst 25 % af plan cache og mindst 100 MB.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Andel >= 25 % og >= 100 MB (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- PlanCacheCollector
-- Fil: src/SQLInsight.Collectors/Instance/PlanCacheCollector.cs
-- Medlemmer: PlanCacheCollector.Sql
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:plan-cache)
-- Delt script: SQL Scripts/_collectors/PlanCacheCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : OptimizeForAdHocRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PlanCacheCollector: Plan cache: antal og størrelse af planer og af ad hoc-planer brugt én gang.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: PlanCacheCollector.Sql
-- Kode: src/SQLInsight.Collectors/Instance/PlanCacheCollector.cs : PlanCacheCollector.Sql
SELECT
COUNT_BIG(*),
ISNULL(SUM(CAST(size_in_bytes AS bigint)), 0) / 1048576.0,
ISNULL(SUM(CASE WHEN objtype = N'Adhoc' AND usecounts = 1 THEN 1 ELSE 0 END), 0),
ISNULL(SUM(CASE WHEN objtype = N'Adhoc' AND usecounts = 1 THEN CAST(size_in_bytes AS bigint) ELSE 0 END), 0) / 1048576.0
FROM sys.dm_exec_cached_plans
GO

View file

@ -0,0 +1,65 @@
-- ===================================================================================================================
-- SQLI-CFG-006: Priority boost er slået til
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-006
-- Titel: Priority boost er slået til
-- Alvor: High
-- Versioner: Alle (SQL Server 2000 og nyere). Ikke relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om 'priority boost' er slået til. Det kan udsulte Windows og cluster-tjenester og er forældet.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis value_in_use for 'priority boost' <> 0.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen (alt andet end 0 er et fund).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create
-- (ConfigurationOptionRule "priority boost")
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO

View file

@ -0,0 +1,66 @@
-- ===================================================================================================================
-- SQLI-CFG-007: Lightweight pooling (fiber mode) er slået til
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-007
-- Titel: Lightweight pooling (fiber mode) er slået til
-- Alvor: High
-- Versioner: Alle (SQL Server 2000 og nyere). Ikke relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om 'lightweight pooling' (fiber mode) er slået til. Det understøttes ikke af bl.a. CLR og distribuerede
-- transaktioner.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis value_in_use for 'lightweight pooling' <> 0.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen (alt andet end 0 er et fund).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create
-- (ConfigurationOptionRule "lightweight pooling")
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO

View file

@ -0,0 +1,66 @@
-- ===================================================================================================================
-- SQLI-CFG-008: xp_cmdshell er slået til
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-008
-- Titel: xp_cmdshell er slået til
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere (på 2000 findes indstillingen ikke i sp_configure: "ikke målbar"). Ikke
-- relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om xp_cmdshell er slået til, så T-SQL kan køre vilkårlige Windows-kommandoer med tjenestekontoens rettigheder.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis value_in_use for 'xp_cmdshell' <> 0.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen (alt andet end 0 er et fund).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create
-- (ConfigurationOptionRule "xp_cmdshell")
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO

View file

@ -0,0 +1,91 @@
-- ===================================================================================================================
-- SQLI-CFG-009: Backup-komprimering er ikke standard
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-009
-- Titel: Backup-komprimering er ikke standard
-- Alvor: Low
-- Versioner: SQL Server 2008 og nyere (indstillingen findes ikke før: "ikke målbar"). Bestået på Express
-- (EngineEdition 4 fra InstanceProbe), som ikke kan komprimere backups. Ikke relevant på Azure SQL
-- Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om backup-komprimering er standard, så hvert backup-job ikke selv skal huske WITH COMPRESSION.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis value_in_use for 'backup compression default' = 0 og udgaven ikke er Express.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- InstanceProbe
-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs
-- Medlemmer: InstanceProbe.Query
-- Interval: én gang, når agenten forbinder til instansen
-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create
-- (ConfigurationOptionRule "backup compression default")
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO
-- -------------------------------------------------------------------------------------------------------------------
-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og
-- bestemmer hvilke varianter og regler der bruges.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner (SQL Server 2000 og nyere)
-- Kilde: InstanceProbe.Query
-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query
SELECT
CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)),
CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)),
CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('Edition') AS nvarchar(128)),
CAST(SERVERPROPERTY('EngineEdition') AS int),
CAST(SERVERPROPERTY('IsClustered') AS int),
CAST(SERVERPROPERTY('IsHadrEnabled') AS int)
GO

View file

@ -0,0 +1,66 @@
-- ===================================================================================================================
-- SQLI-CFG-010: Dedicated Admin Connection kan kun bruges lokalt
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-010
-- Titel: Dedicated Admin Connection kan kun bruges lokalt
-- Alvor: Low
-- Versioner: SQL Server 2005 og nyere (på 2000 findes indstillingen ikke: "ikke målbar"). Ikke relevant på Azure
-- SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om Dedicated Admin Connection (DAC) kun kan bruges fra selve serveren.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis value_in_use for 'remote admin connections' = 0.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create
-- (ConfigurationOptionRule "remote admin connections")
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO

View file

@ -0,0 +1,66 @@
-- ===================================================================================================================
-- SQLI-CFG-011: Konfigurationsændringer venter på RECONFIGURE eller genstart
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-011
-- Titel: Konfigurationsændringer venter på RECONFIGURE eller genstart
-- Alvor: Low
-- Versioner: Alle (SQL Server 2000 og nyere).
--
-- Hvad tjekkes, og hvorfor:
-- Om indstillinger er ændret med sp_configure uden RECONFIGURE eller genstart, så de træder i kraft på et uventet
-- tidspunkt.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund med alle indstillinger, hvor value (konfigureret) <> value_in_use (i brug). 'min server memory (MB)'
-- ignoreres (konfigureret 0, mens SQL Server rapporterer det effektive minimum).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs
-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration)
-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : PendingConfigurationRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO

View file

@ -0,0 +1,81 @@
-- ===================================================================================================================
-- SQLI-CFG-012: Deadlocks bliver ikke registreret
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-012
-- Titel: Deadlocks bliver ikke registreret
-- Alvor: Low
-- Versioner: Har kun betydning på SQL Server 2000 og 2005: fra 2008 (system_health-sessionen, udledt af versionen
-- fra InstanceProbe) er reglen altid bestået.
--
-- Hvad tjekkes, og hvorfor:
-- Om deadlocks kan analyseres bagefter på versioner uden system_health.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Bestået hvis instansen har system_health (2008+). Ellers fund, hvis hverken trace flag 1222 eller 1204 er slået
-- til globalt (DBCC TRACESTATUS(-1): Status = 1 og Global = 1).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- TraceFlagCollector
-- Fil: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs
-- Medlemmer: TraceFlagCollector.Sql
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:trace-flags)
-- Delt script: SQL Scripts/_collectors/TraceFlagCollector.sql
-- InstanceProbe
-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs
-- Medlemmer: InstanceProbe.Query
-- Interval: én gang, når agenten forbinder til instansen
-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : DeadlockTraceFlagRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt DBCC TRACESTATUS (læser trace flags), SET-sessionsindstillinger. Intet ændrer
-- data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- TraceFlagCollector: De globalt slåede trace flags.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner (SQL Server 2000 og nyere)
-- Kilde: TraceFlagCollector.Sql
-- Kode: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs : TraceFlagCollector.Sql
DBCC TRACESTATUS(-1) WITH NO_INFOMSGS
GO
-- -------------------------------------------------------------------------------------------------------------------
-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og
-- bestemmer hvilke varianter og regler der bruges.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner (SQL Server 2000 og nyere)
-- Kilde: InstanceProbe.Query
-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query
SELECT
CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)),
CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)),
CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('Edition') AS nvarchar(128)),
CAST(SERVERPROPERTY('EngineEdition') AS int),
CAST(SERVERPROPERTY('IsClustered') AS int),
CAST(SERVERPROPERTY('IsHadrEnabled') AS int)
GO

View file

@ -0,0 +1,121 @@
-- ===================================================================================================================
-- SQLI-CFG-013: Konfiguration ændret
-- ===================================================================================================================
-- Regel-id: SQLI-CFG-013
-- Titel: Konfiguration ændret
-- Alvor: Info
-- Versioner: Alle. Tjenestekonti kun fra SQL Server 2008 R2 SP1 (sys.dm_server_services).
--
-- Hvad tjekkes, og hvorfor:
-- Om nogen har ændret en sp_configure-indstilling, et globalt trace flag eller en tjenestekonto. Information, så
-- ændringen er synlig.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- InstanceConfigurationCollector bygger et "fingeraftryk" af sp_configure (value_in_use), globale trace flags,
-- tjenester og konti, version, udgave, collation, tempdb-filer m.m. og sammenligner med forrige måling (gemt i
-- agentens repository, så en ændring mens agenten var nede også ses). Ændringer i områderne sp_configure, trace
-- flag og tjenestekonto inden for de seneste 24 timer giver ét Info-fund pr. ændring. Alt sker i C#; SQL'en læser
-- kun de nuværende værdier.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Fundet holdes åbent i 24 timer (ConfigurationFingerprint.RecentWindow, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- InstanceConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs
-- Medlemmer: InstanceConfigurationCollector.PropertiesSql, InstanceConfigurationCollector.TempdbSql,
-- InstanceConfigurationCollector.ColumnsSql, InstanceConfigurationCollector.BuildServicesSql(true),
-- InstanceConfigurationCollector.BuildServicesSql(false), InstanceConfigurationCollector.MemoryModelSql
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:instance-config)
-- Delt script: SQL Scripts/_collectors/InstanceConfigurationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : ConfigurationChangedRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt DBCC TRACESTATUS (læser trace flags), SET-sessionsindstillinger. Intet ændrer
-- data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- InstanceConfigurationCollector: Instansens opsætning til konfigurationssiden og ændringsloggen: egenskaber,
-- tempdb-filer, tjenester og konti, Instant File Initialization, memory model, sp_configure og trace flags.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere (samme forespørgsel som ConfigurationCollector)
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti) (samme forespørgsel som ConfigurationCollector)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO
-- Variant: Alle versioner (SQL Server 2000 og nyere) (samme forespørgsel som TraceFlagCollector)
-- Kilde: TraceFlagCollector.Sql
-- Kode: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs : TraceFlagCollector.Sql
DBCC TRACESTATUS(-1) WITH NO_INFOMSGS
GO
-- Variant: Alle versioner
-- Kilde: InstanceConfigurationCollector.PropertiesSql
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.PropertiesSql
SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), CAST(SERVERPROPERTY('Collation') AS nvarchar(128)),
CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)), CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260))
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: InstanceConfigurationCollector.TempdbSql
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.TempdbSql
SELECT name, type_desc, CAST(size AS bigint) / 128, growth, is_percent_growth, max_size
FROM sys.master_files WHERE database_id = 2 ORDER BY file_id
GO
-- Variant: SQL Server 2008 R2 SP1 og nyere
-- Kilde: InstanceConfigurationCollector.ColumnsSql
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.ColumnsSql
SELECT COL_LENGTH('sys.dm_server_services', 'instant_file_initialization_enabled'), COL_LENGTH('sys.dm_os_sys_info', 'sql_memory_model_desc')
GO
-- Variant: SQL Server 2008 R2 SP1 og nyere, builds med kolonnen instant_file_initialization_enabled (se ColumnsSql)
-- Kilde: InstanceConfigurationCollector.BuildServicesSql(med IFI)
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(true)
SELECT servicename, service_account, startup_type_desc, status_desc, instant_file_initialization_enabled FROM sys.dm_server_services
GO
-- Variant: SQL Server 2008 R2 SP1 og nyere, builds uden kolonnen instant_file_initialization_enabled
-- Kilde: InstanceConfigurationCollector.BuildServicesSql(uden IFI)
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(false)
SELECT servicename, service_account, startup_type_desc, status_desc, CAST(NULL AS nvarchar(1)) FROM sys.dm_server_services
GO
-- Variant: Builds med kolonnen sql_memory_model_desc (se ColumnsSql)
-- Kilde: InstanceConfigurationCollector.MemoryModelSql
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.MemoryModelSql
SELECT sql_memory_model_desc FROM sys.dm_os_sys_info
GO

View file

@ -0,0 +1,77 @@
-- ===================================================================================================================
-- SQLI-CPU-001: Vedvarende højt CPU-forbrug
-- ===================================================================================================================
-- Regel-id: SQLI-CPU-001
-- Titel: Vedvarende højt CPU-forbrug
-- Alvor: SQL Server: Medium fra 75 %, High fra 90 %. Andre processer: Medium.
-- Versioner: SQL Server 2005 og nyere. "Andre processer" kan ikke måles på Linux (host_platform fra SQL Server
-- 2017) og Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om CPU'en har været vedvarende høj, opdelt i SQL Servers eget forbrug og andre processers.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Scheduler monitor skriver én post i minuttet (ProcessUtilization = SQL Server, SystemIdle = ledig). Reglen bruger
-- de seneste op til 15 poster (kræver mindst 5). Fund 1: gennemsnitlig ProcessUtilization >= 75 %. Fund 2:
-- gennemsnitlig andel til andre processer (100 - SystemIdle - ProcessUtilization) >= 25 %.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 15 minutter (CpuRule.Minutes); 75 % / 90 % for SQL Server; 25 % for andre (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- CpuCollector
-- Fil: src/SQLInsight.Collectors/Performance/CpuCollector.cs
-- Medlemmer: CpuCollector.Sql, CpuCollector.HostPlatformSql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:cpu)
-- Delt script: SQL Scripts/_collectors/CpuCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/ResourceRules.cs : CpuRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt lokale variabler, SET-sessionsindstillinger. Intet ændrer data, indstillinger
-- eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- CpuCollector: SQL Servers og systemets CPU-forbrug pr. minut (scheduler monitor ring buffer, op til 60 minutter).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: CpuCollector.Sql
-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.Sql
DECLARE @now_ticks bigint;
SELECT @now_ticks = ms_ticks FROM sys.dm_os_sys_info;
SELECT TOP (60)
DATEADD(second, CAST((rb.[timestamp] - @now_ticks) / 1000 AS int), GETUTCDATE()),
rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int'),
rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int')
FROM (
SELECT [timestamp], CONVERT(xml, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE N'%<SystemHealth>%'
) rb
ORDER BY rb.[timestamp] DESC
GO
-- Variant: SQL Server 2017 og nyere (Windows eller Linux)
-- Kilde: CpuCollector.HostPlatformSql
-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.HostPlatformSql
SELECT host_platform FROM sys.dm_os_host_info
GO

View file

@ -0,0 +1,130 @@
-- ===================================================================================================================
-- SQLI-CPU-002: Sætning kører næsten uafbrudt
-- ===================================================================================================================
-- Regel-id: SQLI-CPU-002
-- Titel: Sætning kører næsten uafbrudt
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om en sætning i praksis holder en CPU-kerne beskæftiget hele tiden.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For hver sætning (query_hash; på 2005 sql_handle + offsets) i top SQL: aktivitet = total_worker_time /
-- (last_execution_time - creation_time) i procent af én kerne. Fund (op til 5, højeste først) for sætninger, der
-- har kørt inden for de seneste 15 minutter, har ligget mindst 1 time i plan cache og har aktivitet over grænsen.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:TopSql:ActivityThresholdPercent, standard 50 (%); mindst 1 time i plan cache
-- (ConstantActivityRule.MinimumLifetime, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- QueryStatsCollector
-- Fil: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs
-- Medlemmer: Db.StartTimeSql(version), QueryStatsCollector.Sql2008 via SqlFor(version, since: null),
-- QueryStatsCollector.Sql2005 via SqlFor(version, since: null), QueryStatsCollector.ServerTimeSql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:query-stats)
-- Delt script: SQL Scripts/_collectors/QueryStatsCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/OperationRules.cs : ConstantActivityRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- QueryStatsCollector: Top SQL: de tungeste sætninger (top 50 efter CPU, varighed, læsninger og antal) blandt dem,
-- der har kørt siden forrige måling. Ingen planer, tekst afkortet.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: QueryStatsCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: QueryStatsCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2008 og nyere (første kørsel: de seneste 15 minutter)
-- Kilde: QueryStatsCollector.SqlFor(2008+)
-- Kode: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs : QueryStatsCollector.Sql2008 via SqlFor(version, since: null)
WITH s AS (
SELECT qs.query_hash AS k,
SUM(qs.execution_count) AS execs, SUM(qs.total_worker_time) AS cpu, SUM(qs.total_elapsed_time) AS elapsed,
SUM(qs.total_logical_reads) AS lreads, SUM(qs.total_physical_reads) AS preads, SUM(qs.total_logical_writes) AS writes,
MIN(qs.creation_time) AS created, MAX(qs.last_execution_time) AS last_exec
FROM sys.dm_exec_query_stats qs
GROUP BY qs.query_hash
HAVING MAX(qs.last_execution_time) >= DATEADD(minute, -15, GETDATE())),
t AS (
SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY cpu DESC) a
UNION SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY elapsed DESC) b
UNION SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY lreads DESC) c
UNION SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY execs DESC) d)
SELECT CONVERT(varchar(20), s.k, 1), s.execs, s.cpu, s.elapsed, s.lreads, s.preads, s.writes, s.created, s.last_exec,
DB_NAME(COALESCE(st.dbid, pa.dbid)),
CAST(SUBSTRING(st.text, r.statement_start_offset / 2 + 1,
CASE WHEN r.statement_end_offset = -1 THEN 4000
ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) AS nvarchar(4000)),
GETDATE()
FROM s JOIN t ON t.k = s.k
OUTER APPLY (SELECT TOP (1) q.sql_handle, q.plan_handle, q.statement_start_offset, q.statement_end_offset
FROM sys.dm_exec_query_stats q WHERE q.query_hash = s.k ORDER BY q.last_execution_time DESC) r
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) st
OUTER APPLY (SELECT CAST(a.value AS int) AS dbid FROM sys.dm_exec_plan_attributes(r.plan_handle) a WHERE a.attribute = N'dbid') pa
GO
-- Variant: SQL Server 2005 (første kørsel: de seneste 15 minutter)
-- Kilde: QueryStatsCollector.SqlFor(2005)
-- Kode: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs : QueryStatsCollector.Sql2005 via SqlFor(version, since: null)
WITH s AS (
SELECT qs.sql_handle AS h, qs.statement_start_offset AS so, qs.statement_end_offset AS eo,
SUM(qs.execution_count) AS execs, SUM(qs.total_worker_time) AS cpu, SUM(qs.total_elapsed_time) AS elapsed,
SUM(qs.total_logical_reads) AS lreads, SUM(qs.total_physical_reads) AS preads, SUM(qs.total_logical_writes) AS writes,
MIN(qs.creation_time) AS created, MAX(qs.last_execution_time) AS last_exec
FROM sys.dm_exec_query_stats qs
GROUP BY qs.sql_handle, qs.statement_start_offset, qs.statement_end_offset
HAVING MAX(qs.last_execution_time) >= DATEADD(minute, -15, GETDATE())),
t AS (
SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY cpu DESC) a
UNION SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY elapsed DESC) b
UNION SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY lreads DESC) c
UNION SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY execs DESC) d)
SELECT sys.fn_varbintohexstr(s.h) + ':' + CAST(s.so AS varchar(12)) + ':' + CAST(s.eo AS varchar(12)),
s.execs, s.cpu, s.elapsed, s.lreads, s.preads, s.writes, s.created, s.last_exec,
DB_NAME(COALESCE(st.dbid, pa.dbid)),
CAST(SUBSTRING(st.text, s.so / 2 + 1, CASE WHEN s.eo = -1 THEN 4000 ELSE (s.eo - s.so) / 2 + 1 END) AS nvarchar(4000)),
GETDATE()
FROM s JOIN t ON t.h = s.h AND t.so = s.so AND t.eo = s.eo
OUTER APPLY sys.dm_exec_sql_text(s.h) st
OUTER APPLY (SELECT TOP (1) q.plan_handle FROM sys.dm_exec_query_stats q
WHERE q.sql_handle = s.h AND q.statement_start_offset = s.so AND q.statement_end_offset = s.eo) r
OUTER APPLY (SELECT CAST(a.value AS int) AS dbid FROM sys.dm_exec_plan_attributes(r.plan_handle) a WHERE a.attribute = N'dbid') pa
GO
-- Variant: Alle versioner, kun når ingen sætning har kørt siden sidst
-- Kilde: QueryStatsCollector.ServerTimeSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs : QueryStatsCollector.ServerTimeSql
SELECT GETDATE()
GO

222
SQL Scripts/SQLI-DB-001.sql Normal file
View file

@ -0,0 +1,222 @@
-- ===================================================================================================================
-- SQLI-DB-001: Database er ikke tilgængelig
-- ===================================================================================================================
-- Regel-id: SQLI-DB-001
-- Titel: Database er ikke tilgængelig
-- Alvor: Critical for SUSPECT, RECOVERY_PENDING og EMERGENCY; Low for OFFLINE.
-- Versioner: Alle (SQL Server 2000 via DATABASEPROPERTYEX(..., 'Status')).
--
-- Hvad tjekkes, og hvorfor:
-- Om en database er utilgængelig.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. database (undtagen tempdb), hvis state_desc er SUSPECT, RECOVERY_PENDING, EMERGENCY eller OFFLINE.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : DatabaseStateRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

223
SQL Scripts/SQLI-DB-002.sql Normal file
View file

@ -0,0 +1,223 @@
-- ===================================================================================================================
-- SQLI-DB-002: AUTO_SHRINK er slået til
-- ===================================================================================================================
-- Regel-id: SQLI-DB-002
-- Titel: AUTO_SHRINK er slået til
-- Alvor: High
-- Versioner: Alle (SQL Server 2000 og nyere).
--
-- Hvad tjekkes, og hvorfor:
-- Om AUTO_SHRINK er slået til. Det fragmenterer indexes og koster CPU og I/O i en evig cyklus.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. ONLINE database med is_auto_shrink_on = 1.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SimpleDatabaseRules.Create (DatabaseSettingRule
-- "SQLI-DB-002")
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

223
SQL Scripts/SQLI-DB-003.sql Normal file
View file

@ -0,0 +1,223 @@
-- ===================================================================================================================
-- SQLI-DB-003: AUTO_CLOSE er slået til
-- ===================================================================================================================
-- Regel-id: SQLI-DB-003
-- Titel: AUTO_CLOSE er slået til
-- Alvor: Medium
-- Versioner: Alle (SQL Server 2000 og nyere).
--
-- Hvad tjekkes, og hvorfor:
-- Om AUTO_CLOSE er slået til, så databasen lukkes og plan cache tømmes, når sidste forbindelse slipper den.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. ONLINE database med is_auto_close_on = 1.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SimpleDatabaseRules.Create (DatabaseSettingRule
-- "SQLI-DB-003")
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

224
SQL Scripts/SQLI-DB-004.sql Normal file
View file

@ -0,0 +1,224 @@
-- ===================================================================================================================
-- SQLI-DB-004: Sidekontrol er ikke CHECKSUM
-- ===================================================================================================================
-- Regel-id: SQLI-DB-004
-- Titel: Sidekontrol er ikke CHECKSUM
-- Alvor: Medium
-- Versioner: Alle (på SQL Server 2000 rapporteres TORN_PAGE_DETECTION eller NONE, da CHECKSUM først findes fra
-- 2005).
--
-- Hvad tjekkes, og hvorfor:
-- Om sidekontrollen (PAGE_VERIFY) er andet end CHECKSUM, så korruption fra storage ikke opdages ved læsning.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. ONLINE database, hvor page_verify_option_desc ikke er NULL og ikke er CHECKSUM.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SimpleDatabaseRules.Create (DatabaseSettingRule
-- "SQLI-DB-004")
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

223
SQL Scripts/SQLI-DB-005.sql Normal file
View file

@ -0,0 +1,223 @@
-- ===================================================================================================================
-- SQLI-DB-005: TRUSTWORTHY er slået til
-- ===================================================================================================================
-- Regel-id: SQLI-DB-005
-- Titel: TRUSTWORTHY er slået til
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere (på 2000 rapporterer collectoren altid 0).
--
-- Hvad tjekkes, og hvorfor:
-- Om TRUSTWORTHY er slået til, hvilket kan lade en db_owner blive sysadmin.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. ONLINE database med is_trustworthy_on = 1, undtagen msdb (hvor det er Microsofts standard).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SimpleDatabaseRules.Create (DatabaseSettingRule
-- "SQLI-DB-005")
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

247
SQL Scripts/SQLI-DB-006.sql Normal file
View file

@ -0,0 +1,247 @@
-- ===================================================================================================================
-- SQLI-DB-006: Database kører med et ældre compatibility level
-- ===================================================================================================================
-- Regel-id: SQLI-DB-006
-- Titel: Database kører med et ældre compatibility level
-- Alvor: Low
-- Versioner: Alle.
--
-- Hvad tjekkes, og hvorfor:
-- Om en brugerdatabase kører med et ældre compatibility level end instansens eget.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Instansens niveau = hovedversion (fra ProductVersion i InstanceProbe) x 10, fx 160 for SQL Server 2022. Ét fund
-- pr. ONLINE brugerdatabase (ikke master, model, msdb, tempdb) med compatibility_level under det.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- InstanceProbe
-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs
-- Medlemmer: InstanceProbe.Query
-- Interval: én gang, når agenten forbinder til instansen
-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : CompatibilityLevelRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO
-- -------------------------------------------------------------------------------------------------------------------
-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og
-- bestemmer hvilke varianter og regler der bruges.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner (SQL Server 2000 og nyere)
-- Kilde: InstanceProbe.Query
-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query
SELECT
CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)),
CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)),
CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('Edition') AS nvarchar(128)),
CAST(SERVERPROPERTY('EngineEdition') AS int),
CAST(SERVERPROPERTY('IsClustered') AS int),
CAST(SERVERPROPERTY('IsHadrEnabled') AS int)
GO

225
SQL Scripts/SQLI-DB-007.sql Normal file
View file

@ -0,0 +1,225 @@
-- ===================================================================================================================
-- SQLI-DB-007: Database mangler en nylig fuld backup
-- ===================================================================================================================
-- Regel-id: SQLI-DB-007
-- Titel: Database mangler en nylig fuld backup
-- Alvor: Critical hvis aldrig backet op; High hvis ældre end 7 dage; Medium hvis databasen er i en availability
-- group (backup kan være taget på en anden replika).
-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance (Azure tager backup).
--
-- Hvad tjekkes, og hvorfor:
-- Om en database mangler en nylig fuld backup.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For hver ONLINE database, der ikke er mirror/AG-sekundær, ikke er model og er mindst 1 dag gammel: fund hvis
-- seneste fulde backup (msdb.dbo.backupset type 'D', taget efter databasens create_date - 1 sekund, omregnet til
-- UTC) mangler eller er ældre end 7 dage.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 7 dage (FullBackupRule.MaxAge, fast i koden); databaser yngre end 1 dag springes over.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : FullBackupRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

225
SQL Scripts/SQLI-DB-008.sql Normal file
View file

@ -0,0 +1,225 @@
-- ===================================================================================================================
-- SQLI-DB-008: Database i FULL recovery uden log-backups
-- ===================================================================================================================
-- Regel-id: SQLI-DB-008
-- Titel: Database i FULL recovery uden log-backups
-- Alvor: High; Medium hvis databasen er i en availability group.
-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om en database i FULL eller BULK_LOGGED recovery mangler log-backups, så loggen vokser og point-in-time restore
-- går tabt.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund for ONLINE databaser i FULL/BULK_LOGGED, der ikke er mirror/AG-sekundær, ikke er read-only, ikke er model og
-- HAR haft en fuld backup (før den første fulde backup trunkeres loggen som i SIMPLE), når seneste log-backup
-- (backupset type 'L') mangler eller er ældre end 24 timer.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 24 timer (LogBackupRule.MaxAge, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : LogBackupRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

225
SQL Scripts/SQLI-DB-009.sql Normal file
View file

@ -0,0 +1,225 @@
-- ===================================================================================================================
-- SQLI-DB-009: DBCC CHECKDB er ikke kørt for nylig
-- ===================================================================================================================
-- Regel-id: SQLI-DB-009
-- Titel: DBCC CHECKDB er ikke kørt for nylig
-- Alvor: High hvis aldrig kørt eller ældre end 30 dage; ellers Medium.
-- Versioner: SQL Server 2016 SP2 og nyere (DATABASEPROPERTYEX(..., 'LastGoodCheckDbTime')). Ældre versioner: "tjek
-- manuelt" (DBCC DBINFO ... WITH TABLERESULTS, dbi_dbccLastKnownGood, kræver sysadmin). Ikke relevant på
-- Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om DBCC CHECKDB ikke er kørt fejlfrit for nylig.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For hver ONLINE database, der er mindst 7 dage gammel og har en LastGoodCheckDbTime: fund hvis tiden er
-- 1900-01-01 (aldrig) eller ældre end 7 dage.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 7 dage (CheckDbRule.MaxAge); High over 30 dage (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : CheckDbRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

View file

@ -0,0 +1,56 @@
-- ===================================================================================================================
-- SQLI-DB-010: Korrupte sider registreret i suspect_pages
-- ===================================================================================================================
-- Regel-id: SQLI-DB-010
-- Titel: Korrupte sider registreret i suspect_pages
-- Alvor: Critical
-- Versioner: SQL Server 2005 og nyere (msdb.dbo.suspect_pages).
--
-- Hvad tjekkes, og hvorfor:
-- Om SQL Server har registreret korrupte sider (fejl 823/824/825) i msdb.dbo.suspect_pages.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Rækker, der ikke er løst (event_type 1, 2 og 3; 4 = gendannet, 5 = repareret og 7 = deallokeret tæller ikke),
-- grupperes pr. database: ét fund pr. database med op til 20 sider som målinger.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- USE msdb; GRANT SELECT ON dbo.suspect_pages TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- SuspectPageCollector
-- Fil: src/SQLInsight.Collectors/Instance/SuspectPageCollector.cs
-- Medlemmer: SuspectPageCollector.Sql
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:suspect-pages)
-- Delt script: SQL Scripts/_collectors/SuspectPageCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SuspectPagesRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- SuspectPageCollector: Sider, SQL Server har registreret som korrupte (msdb.dbo.suspect_pages).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: SuspectPageCollector.Sql
-- Kode: src/SQLInsight.Collectors/Instance/SuspectPageCollector.cs : SuspectPageCollector.Sql
SELECT ISNULL(DB_NAME(database_id), CAST(database_id AS nvarchar(20))), file_id, page_id, event_type, error_count, last_update_date
FROM msdb.dbo.suspect_pages
GO

223
SQL Scripts/SQLI-DB-011.sql Normal file
View file

@ -0,0 +1,223 @@
-- ===================================================================================================================
-- SQLI-DB-011: Transaktionsloggen har for mange VLF'er
-- ===================================================================================================================
-- Regel-id: SQLI-DB-011
-- Titel: Transaktionsloggen har for mange VLF'er
-- Alvor: Medium; High over 10.000 VLF'er.
-- Versioner: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info). Ældre: "tjek manuelt" med DBCC LOGINFO (sysadmin).
-- Ikke relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om transaktionsloggen har så mange virtuelle logfiler, at recovery, restore og log-backups bliver langsomme.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. ONLINE database med flere end 1.000 VLF'er (COUNT(*) fra sys.dm_db_log_info).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 1.000 (VirtualLogFileRule.Threshold); High over 10 x grænsen (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : VirtualLogFileRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

223
SQL Scripts/SQLI-DB-012.sql Normal file
View file

@ -0,0 +1,223 @@
-- ===================================================================================================================
-- SQLI-DB-012: Uhensigtsmæssig autogrowth
-- ===================================================================================================================
-- Regel-id: SQLI-DB-012
-- Titel: Uhensigtsmæssig autogrowth
-- Alvor: Low
-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om data- og logfiler har en autogrowth, der giver mange eller uforudsigelige vækster.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For hver fil på mindst 1024 MB med autogrowth > 0 i ONLINE databaser: fund hvis væksten er i procent, eller hvis
-- den er under 64 MB.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Filstørrelse >= 1024 MB; fast vækst under 64 MB (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : FileGrowthRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

223
SQL Scripts/SQLI-DB-013.sql Normal file
View file

@ -0,0 +1,223 @@
-- ===================================================================================================================
-- SQLI-DB-013: Databasefil nærmer sig sin maksimale størrelse
-- ===================================================================================================================
-- Regel-id: SQLI-DB-013
-- Titel: Databasefil nærmer sig sin maksimale størrelse
-- Alvor: High fra 90 %; Critical når filen har nået MAXSIZE.
-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om en databasefil nærmer sig sin MAXSIZE, så ændringer snart fejler (1105/9002), selv om der er plads på disken.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund for filer i ONLINE databaser med en MAXSIZE (> 0 og < 2 TB, der regnes som ubegrænset), når størrelse >= 90
-- % af MAXSIZE.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 90 %; MAXSIZE på 2 TB eller mere regnes som ubegrænset (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : FileMaxSizeRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

224
SQL Scripts/SQLI-DB-014.sql Normal file
View file

@ -0,0 +1,224 @@
-- ===================================================================================================================
-- SQLI-DB-014: Query Store indsamler ikke længere data
-- ===================================================================================================================
-- Regel-id: SQLI-DB-014
-- Titel: Query Store indsamler ikke længere data
-- Alvor: High ved ERROR; ellers Medium.
-- Versioner: SQL Server 2016 og nyere (QueryStore).
--
-- Hvad tjekkes, og hvorfor:
-- Om Query Store selv er gået over i READ_ONLY eller ERROR, så der ikke indsamles nye planer.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For ONLINE brugerdatabaser, der ikke er mirror/AG-sekundær, læses sys.database_query_store_options. Fund hvis
-- desired_state_desc = READ_WRITE og actual_state_desc <> READ_WRITE. readonly_reason med bit 65536 betyder, at
-- Query Store er fuld.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : QueryStoreStateRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

299
SQL Scripts/SQLI-DB-015.sql Normal file
View file

@ -0,0 +1,299 @@
-- ===================================================================================================================
-- SQLI-DB-015: tempdb har for få eller ulige store datafiler
-- ===================================================================================================================
-- Regel-id: SQLI-DB-015
-- Titel: tempdb har for få eller ulige store datafiler
-- Alvor: Medium for for få filer; Low for ulige store filer.
-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance.
--
-- Hvad tjekkes, og hvorfor:
-- Om tempdb har for få eller ulige store datafiler, hvilket giver PAGELATCH-contention på allokeringssider.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- tempdb's datafiler (type ROWS fra Files2005/Files2000, database_id 2) og cpu_count (ServerCollector). Anbefalet =
-- min(8, logiske CPU'er). Fund 1: antal filer < anbefalet og < 4. Fund 2: mere end én fil, og størrelser eller
-- autogrowth (MB/%) er ikke ens.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Op til 8 filer, én pr. logisk CPU; under 4 filer (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- ServerCollector
-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs
-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server)
-- Delt script: SQL Scripts/_collectors/ServerCollector.sql
-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : TempdbFilesRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), SET-sessionsindstillinger. Intet ændrer
-- data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere
-- Kilde: ServerCollector.BuildSql(2012+)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_kb / 1024,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds
-- Kilde: ServerCollector.BuildSql(2008 R2 SP1)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
virtual_machine_type_desc
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2008 og 2008 R2 før SP1
-- Kilde: ServerCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT sqlserver_start_time FROM sys.dm_os_sys_info),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2005
-- Kilde: ServerCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version)
SELECT
(SELECT create_date FROM sys.databases WHERE database_id = 2),
cpu_count,
(SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
(SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'),
physical_memory_in_bytes / 1048576,
CAST(NULL AS nvarchar(60))
FROM sys.dm_os_sys_info
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt
-- Kilde: ServerCollector.StartTimeSql(2000)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse
-- Kilde: ServerCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000
EXEC master.dbo.xp_msver
GO

View file

@ -0,0 +1,76 @@
-- ===================================================================================================================
-- SQLI-DLK-001: Deadlocks
-- ===================================================================================================================
-- Regel-id: SQLI-DLK-001
-- Titel: Deadlocks
-- Alvor: Medium; High fra 10 deadlocks siden sidste måling.
-- Versioner: SQL Server 2008 og nyere (system_health). 2012+: event-filerne; 2008/2008 R2: ring buffer. SQL Server
-- 2005: "tjek manuelt" (trace flag 1222).
--
-- Hvad tjekkes, og hvorfor:
-- Om der er sket deadlocks siden forrige måling.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- xml_deadlock_report-hændelser fra system_health parses i C#. Første gang tages kun det seneste døgn med; derefter
-- læses videre fra seneste fil og offset. Agentens egne deadlocks (program 'SQLInsight Pro') tælles ikke. Fund når
-- der er nye deadlocks; de seneste 3 vises med processer.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen (High fra 10, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DeadlockCollector
-- Fil: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs
-- Medlemmer: DeadlockCollector.BuildFileSql(lastFile: null, lastOffset: null), DeadlockCollector.RingBufferSql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:deadlocks)
-- Delt script: SQL Scripts/_collectors/DeadlockCollector.sql
-- Regelkode: src/SQLInsight.Rules/Concurrency/ConcurrencyRules.cs : DeadlockRule.Evaluate
--
-- Read-only: Ja: kun SELECT (sys.fn_xe_file_target_read_file og DMV'er) samt SET-sessionsindstillinger. Ingen
-- temp-tabeller; læsepositionen findes med ROW_NUMBER() i samme forespørgsel.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DeadlockCollector: Nye deadlock-grafer (xml_deadlock_report) fra den indbyggede system_health-session.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2012 og nyere (system_health-filer; første læsning)
-- Kilde: DeadlockCollector.BuildFileSql
-- Kode: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs : DeadlockCollector.BuildFileSql(lastFile: null, lastOffset: null)
SELECT e.event_data, e.file_name, e.file_offset, e.rn
FROM (
SELECT CASE WHEN object_name = N'xml_deadlock_report' THEN CAST(event_data AS nvarchar(max)) END AS event_data,
file_name, file_offset,
ROW_NUMBER() OVER (ORDER BY file_name DESC, file_offset DESC) AS rn
FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL)
) AS e
WHERE e.event_data IS NOT NULL OR e.rn = 1;
GO
-- Variant: SQL Server 2008 – 2008 R2, og når event-filerne ikke kan læses
-- Kilde: DeadlockCollector.RingBufferSql
-- Kode: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs : DeadlockCollector.RingBufferSql
SELECT CAST(t.target_data AS nvarchar(max))
FROM sys.dm_xe_session_targets t
JOIN sys.dm_xe_sessions s ON s.address = t.event_session_address
WHERE s.name = N'system_health' AND t.target_name = N'ring_buffer'
GO

View file

@ -0,0 +1,161 @@
-- ===================================================================================================================
-- SQLI-IDX-001: Manglende index med stor effekt
-- ===================================================================================================================
-- Regel-id: SQLI-IDX-001
-- Titel: Manglende index med stor effekt
-- Alvor: Medium hvis forbedringsmålet er mindst 1.000.000; ellers Low.
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om optimizeren gentagne gange har ønsket et index med stor effekt - vurderet kritisk mod de indexes, der allerede
-- findes.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Forbedringsmål = avg_total_user_cost x (avg_user_impact / 100) x (user_seeks + user_scans). Fund for forslag med
-- mål >= 50.000 og mindst 100 seeks + scans (højst 10, største først). C# sammenligner med tabellens eksisterende
-- indexes: findes et nonclustered index med lighedskolonnerne forrest, foreslås det udvidet; mange indexes, mange
-- skrivninger, brede INCLUDE og kort oppetid nævnes.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 50.000 (MinImprovementMeasure) og 100 brug (MinUses), fast i koden.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- IndexCollector
-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs
-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql,
-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database,
-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database)
-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes)
-- Delt script: SQL Scripts/_collectors/IndexCollector.sql
-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : MissingIndexRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase.
-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql
SELECT d.name FROM sys.databases d
WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1
AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.MissingSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql
SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns,
gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek
FROM sys.dm_db_missing_index_details d
JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle
JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle
GO
-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2008+)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
i.filter_definition,
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2005)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
CAST(NULL AS nvarchar(max)),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildStatisticsSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database)
SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid),
COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr
FROM [msdb].sys.sysindexes si
JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
WHERE si.indid > 0 AND si.indid < 255;
GO

View file

@ -0,0 +1,160 @@
-- ===================================================================================================================
-- SQLI-IDX-002: Ubrugt index koster ved hver skrivning
-- ===================================================================================================================
-- Regel-id: SQLI-IDX-002
-- Titel: Ubrugt index koster ved hver skrivning
-- Alvor: Low; Medium fra 1 GB.
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om et nonclustered index vedligeholdes ved hver skrivning, men aldrig læses.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- sys.dm_db_index_usage_stats nulstilles ved genstart, så agenten følger brugen på tværs af genstarter
-- (IndexUsageTracking). Fund for nonclustered, ikke-unikke, ikke-deaktiverede indexes (ikke PK/unik constraint),
-- der er fulgt i mindst 7 dage, har 0 læsninger (seeks + scans + lookups), og har mindst 1.000 skrivninger eller
-- fylder mindst 100 MB. Højst 10.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 7 dage (IndexText.MinUptimeForUsage), 1.000 skrivninger, 100 MB (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- IndexCollector
-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs
-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql,
-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database,
-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database)
-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes)
-- Delt script: SQL Scripts/_collectors/IndexCollector.sql
-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : UnusedIndexRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase.
-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql
SELECT d.name FROM sys.databases d
WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1
AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.MissingSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql
SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns,
gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek
FROM sys.dm_db_missing_index_details d
JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle
JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle
GO
-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2008+)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
i.filter_definition,
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2005)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
CAST(NULL AS nvarchar(max)),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildStatisticsSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database)
SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid),
COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr
FROM [msdb].sys.sysindexes si
JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
WHERE si.indid > 0 AND si.indid < 255;
GO

View file

@ -0,0 +1,160 @@
-- ===================================================================================================================
-- SQLI-IDX-003: Duplikeret eller overlappende index
-- ===================================================================================================================
-- Regel-id: SQLI-IDX-003
-- Titel: Duplikeret eller overlappende index
-- Alvor: Medium for nøjagtige dubletter; Low for overlappende.
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om et index er overflødigt, fordi et andet index på samme tabel dækker det.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- I C#: et nonclustered index (ikke PK/unik constraint) er dækket af et andet, når filteret er ens, dets
-- nøglekolonner er de første nøglekolonner i det andet (samme rækkefølge), et unikt index kun dækkes af et med
-- præcis samme nøgler, og alle dets INCLUDE-kolonner findes i det andet (et clustered index dækker kun ved samme
-- nøgler). Højst 10 fund.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- IndexCollector
-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs
-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql,
-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database,
-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database)
-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes)
-- Delt script: SQL Scripts/_collectors/IndexCollector.sql
-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : DuplicateIndexRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase.
-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql
SELECT d.name FROM sys.databases d
WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1
AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.MissingSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql
SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns,
gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek
FROM sys.dm_db_missing_index_details d
JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle
JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle
GO
-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2008+)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
i.filter_definition,
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2005)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
CAST(NULL AS nvarchar(max)),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildStatisticsSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database)
SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid),
COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr
FROM [msdb].sys.sysindexes si
JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
WHERE si.indid > 0 AND si.indid < 255;
GO

View file

@ -0,0 +1,158 @@
-- ===================================================================================================================
-- SQLI-IDX-004: Heap med mange forwarded records
-- ===================================================================================================================
-- Regel-id: SQLI-IDX-004
-- Titel: Heap med mange forwarded records
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om en heap har mange forwarded records, så læsninger hopper rundt.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund for heaps (index_id 0) med mindst 10.000 rækker og mindst 10.000 forwarded_fetch_count
-- (sys.dm_db_index_operational_stats, siden genstart). Højst 10, flest først.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 10.000 rækker og 10.000 forwarded fetches (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- IndexCollector
-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs
-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql,
-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database,
-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database)
-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes)
-- Delt script: SQL Scripts/_collectors/IndexCollector.sql
-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : ForwardedRecordsRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase.
-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql
SELECT d.name FROM sys.databases d
WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1
AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.MissingSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql
SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns,
gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek
FROM sys.dm_db_missing_index_details d
JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle
JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle
GO
-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2008+)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
i.filter_definition,
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2005)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
CAST(NULL AS nvarchar(max)),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildStatisticsSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database)
SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid),
COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr
FROM [msdb].sys.sysindexes si
JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
WHERE si.indid > 0 AND si.indid < 255;
GO

View file

@ -0,0 +1,92 @@
-- ===================================================================================================================
-- SQLI-IO-001: Høj læselatens på drev med datafiler
-- ===================================================================================================================
-- Regel-id: SQLI-IO-001
-- Titel: Høj læselatens på drev med datafiler
-- Alvor: Medium; High fra 50 ms.
-- Versioner: SQL Server 2005 og nyere (drev/mount point fra sys.dm_os_volume_stats fra 2008 R2 SP1; før det udledt
-- af filstien).
--
-- Hvad tjekkes, og hvorfor:
-- Om læsninger fra datafilerne på et drev er langsomme.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Forskellen mellem to målinger af sys.dm_io_virtual_file_stats (nulstilles ved genstart). Datafiler (ROWS)
-- grupperes pr. drev: fund pr. drev, hvis der er mindst 1.000 læsninger i intervallet og gennemsnittet
-- (io_stall_read_ms / num_of_reads) er mindst 20 ms. De tre langsomste filer står i målingerne.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- >= 1.000 læsninger; >= 20 ms; High fra 50 ms (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- FileIoCollector
-- Fil: src/SQLInsight.Collectors/Performance/FileIoCollector.cs
-- Medlemmer: Db.StartTimeSql(version), FileIoCollector.SqlWithVolumes, FileIoCollector.Sql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:file-io)
-- Delt script: SQL Scripts/_collectors/FileIoCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/ResourceRules.cs : DataFileReadLatencyRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- FileIoCollector: Kumulativ I/O og I/O-ventetid pr. data- og logfil, med drev/mount point. Reglerne bruger
-- forskellen mellem to målinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: FileIoCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: FileIoCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2008 R2 SP1 og nyere (med drev/mount point)
-- Kilde: FileIoCollector.SqlWithVolumes
-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.SqlWithVolumes
SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name,
vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms,
vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms,
vs.volume_mount_point
FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs
JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id
OUTER APPLY sys.dm_os_volume_stats(vfs.database_id, vfs.file_id) vs
WHERE mf.type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2005 – 2008 R2 RTM
-- Kilde: FileIoCollector.Sql
-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.Sql
SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name,
vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms,
vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms,
CAST(NULL AS nvarchar(260))
FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs
JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id
WHERE mf.type_desc IN (N'ROWS', N'LOG')
GO

View file

@ -0,0 +1,90 @@
-- ===================================================================================================================
-- SQLI-IO-002: Høj skrivelatens på drev med logfiler
-- ===================================================================================================================
-- Regel-id: SQLI-IO-002
-- Titel: Høj skrivelatens på drev med logfiler
-- Alvor: Medium; High fra 15 ms.
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om skrivninger til logfilerne på et drev er langsomme, hvilket forsinker alle commits.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Forskellen mellem to målinger. Logfiler (LOG) grupperes pr. drev: fund pr. drev, hvis der er mindst 1.000
-- skrivninger i intervallet og gennemsnittet (io_stall_write_ms / num_of_writes) er mindst 5 ms.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- >= 1.000 skrivninger; >= 5 ms; High fra 15 ms (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- FileIoCollector
-- Fil: src/SQLInsight.Collectors/Performance/FileIoCollector.cs
-- Medlemmer: Db.StartTimeSql(version), FileIoCollector.SqlWithVolumes, FileIoCollector.Sql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:file-io)
-- Delt script: SQL Scripts/_collectors/FileIoCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/ResourceRules.cs : LogFileWriteLatencyRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- FileIoCollector: Kumulativ I/O og I/O-ventetid pr. data- og logfil, med drev/mount point. Reglerne bruger
-- forskellen mellem to målinger.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: FileIoCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: FileIoCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2008 R2 SP1 og nyere (med drev/mount point)
-- Kilde: FileIoCollector.SqlWithVolumes
-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.SqlWithVolumes
SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name,
vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms,
vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms,
vs.volume_mount_point
FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs
JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id
OUTER APPLY sys.dm_os_volume_stats(vfs.database_id, vfs.file_id) vs
WHERE mf.type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2005 – 2008 R2 RTM
-- Kilde: FileIoCollector.Sql
-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.Sql
SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name,
vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms,
vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms,
CAST(NULL AS nvarchar(260))
FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs
JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id
WHERE mf.type_desc IN (N'ROWS', N'LOG')
GO

View file

@ -0,0 +1,76 @@
-- ===================================================================================================================
-- SQLI-JOB-001: SQL Agent-job fejler
-- ===================================================================================================================
-- Regel-id: SQLI-JOB-001
-- Titel: SQL Agent-job fejler
-- Alvor: High hvis jobnavnet indeholder backup, checkdb, integrity eller dbcc; ellers Medium.
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om et aktivt SQL Agent-job fejlede ved seneste kørsel.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. job med enabled = 1, hvor seneste job-udfald (sysjobhistory, step_id = 0, højeste instance_id) har
-- run_status = 0 (fejlet). Seneste fejlende trin og dets besked samt antal fejl siden i går vises som målinger.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- USE msdb; GRANT SELECT ON dbo.sysjobs TO [agent]; GRANT SELECT ON dbo.sysjobhistory TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AgentJobCollector
-- Fil: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs
-- Medlemmer: AgentJobCollector.RunningSql, AgentJobCollector.JobsSql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:agent-jobs)
-- Delt script: SQL Scripts/_collectors/AgentJobCollector.sql
-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : FailedJobRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt lokale variabler, SET-sessionsindstillinger. Intet ændrer data, indstillinger
-- eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AgentJobCollector: SQL Agent-jobs fra msdb: aktiv, seneste udfald, seneste fejlende trin og antal fejl det seneste
-- døgn, og om SQL Server Agent kører.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: AgentJobCollector.RunningSql
-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.RunningSql
SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE program_name LIKE N'SQLAgent - %'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: AgentJobCollector.JobsSql
-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.JobsSql
DECLARE @since int;
SET @since = CONVERT(int, CONVERT(char(8), DATEADD(day, -1, GETDATE()), 112));
SELECT j.name, j.enabled, last_run.run_status, last_run.run_date, last_run.run_time,
failed_step.step_name, LEFT(failed_step.message, 2000), failures.n
FROM msdb.dbo.sysjobs j
OUTER APPLY (SELECT TOP (1) h.run_status, h.run_date, h.run_time FROM msdb.dbo.sysjobhistory h
WHERE h.job_id = j.job_id AND h.step_id = 0 ORDER BY h.instance_id DESC) last_run
OUTER APPLY (SELECT TOP (1) h.step_name, h.message FROM msdb.dbo.sysjobhistory h
WHERE h.job_id = j.job_id AND h.step_id > 0 AND h.run_status = 0 ORDER BY h.instance_id DESC) failed_step
OUTER APPLY (SELECT COUNT(*) AS n FROM msdb.dbo.sysjobhistory h
WHERE h.job_id = j.job_id AND h.step_id = 0 AND h.run_status = 0 AND h.run_date >= @since) failures
GO

View file

@ -0,0 +1,101 @@
-- ===================================================================================================================
-- SQLI-JOB-002: SQL Server Agent kører ikke
-- ===================================================================================================================
-- Regel-id: SQLI-JOB-002
-- Titel: SQL Server Agent kører ikke
-- Alvor: High når der er aktive jobs; ellers Low.
-- Versioner: SQL Server 2005 og nyere. Bestået på Express (EngineEdition fra InstanceProbe), som ikke har SQL
-- Server Agent.
--
-- Hvad tjekkes, og hvorfor:
-- Om SQL Server Agent ikke kører, så ingen planlagte jobs (backups!) bliver udført.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Agenten regnes som kørende, hvis der findes mindst én session med program_name LIKE 'SQLAgent - %'. Fund hvis
-- ingen findes, og udgaven ikke er Express.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- USE msdb; GRANT SELECT ON dbo.sysjobs TO [agent]; GRANT SELECT ON dbo.sysjobhistory TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- AgentJobCollector
-- Fil: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs
-- Medlemmer: AgentJobCollector.RunningSql, AgentJobCollector.JobsSql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:agent-jobs)
-- Delt script: SQL Scripts/_collectors/AgentJobCollector.sql
-- InstanceProbe
-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs
-- Medlemmer: InstanceProbe.Query
-- Interval: én gang, når agenten forbinder til instansen
-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql
-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : AgentStoppedRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt lokale variabler, SET-sessionsindstillinger. Intet ændrer data, indstillinger
-- eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- AgentJobCollector: SQL Agent-jobs fra msdb: aktiv, seneste udfald, seneste fejlende trin og antal fejl det seneste
-- døgn, og om SQL Server Agent kører.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: AgentJobCollector.RunningSql
-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.RunningSql
SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE program_name LIKE N'SQLAgent - %'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: AgentJobCollector.JobsSql
-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.JobsSql
DECLARE @since int;
SET @since = CONVERT(int, CONVERT(char(8), DATEADD(day, -1, GETDATE()), 112));
SELECT j.name, j.enabled, last_run.run_status, last_run.run_date, last_run.run_time,
failed_step.step_name, LEFT(failed_step.message, 2000), failures.n
FROM msdb.dbo.sysjobs j
OUTER APPLY (SELECT TOP (1) h.run_status, h.run_date, h.run_time FROM msdb.dbo.sysjobhistory h
WHERE h.job_id = j.job_id AND h.step_id = 0 ORDER BY h.instance_id DESC) last_run
OUTER APPLY (SELECT TOP (1) h.step_name, h.message FROM msdb.dbo.sysjobhistory h
WHERE h.job_id = j.job_id AND h.step_id > 0 AND h.run_status = 0 ORDER BY h.instance_id DESC) failed_step
OUTER APPLY (SELECT COUNT(*) AS n FROM msdb.dbo.sysjobhistory h
WHERE h.job_id = j.job_id AND h.step_id = 0 AND h.run_status = 0 AND h.run_date >= @since) failures
GO
-- -------------------------------------------------------------------------------------------------------------------
-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og
-- bestemmer hvilke varianter og regler der bruges.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner (SQL Server 2000 og nyere)
-- Kilde: InstanceProbe.Query
-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query
SELECT
CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)),
CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)),
CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('Edition') AS nvarchar(128)),
CAST(SERVERPROPERTY('EngineEdition') AS int),
CAST(SERVERPROPERTY('IsClustered') AS int),
CAST(SERVERPROPERTY('IsHadrEnabled') AS int)
GO

View file

@ -0,0 +1,118 @@
-- ===================================================================================================================
-- SQLI-LOG-001: SQL Server har læst beskadigede sider (fejl 823/824/825)
-- ===================================================================================================================
-- Regel-id: SQLI-LOG-001
-- Titel: SQL Server har læst beskadigede sider (fejl 823/824/825)
-- Alvor: Critical for 823/824; High for 825.
-- Versioner: SQL Server 2005 og nyere. Virker med alle tre kilder (fejllog, default trace, exception ring buffer).
--
-- Hvad tjekkes, og hvorfor:
-- Om SQL Server har læst beskadigede sider (fejl 823, 824) eller måtte læse en side igen for at få den (825).
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fejlloggen læses fra forrige måling (første gang den seneste time) fra den bedste kilde, agentens rettigheder
-- giver: fejlloggen (securityadmin), default trace (ALTER TRACE) eller exception ring buffer (VIEW SERVER STATE).
-- Fejl grupperes pr. fejlnummer; et fund pr. fejlnummer 823/824/825 set inden for de seneste 24 timer.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Fundet holdes åbent 24 timer efter seneste forekomst (ErrorLogAnalysis.RecentWindow, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde)
-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ErrorLogCollector
-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs
-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal),
-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal),
-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log)
-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql
-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : CorruptionErrorRule
--
-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace),
-- SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1)
-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og
-- connectivity ring buffer (kun fejlnumre).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ErrorLogCollector.CanReadErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql
SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END,
DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE()
GO
-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin)
-- Kilde: ErrorLogCollector.BuildErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal)
-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx
-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fra nvarchar(19);
DECLARE @til nvarchar(19);
SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126);
EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.DefaultTracePathSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql
SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.BuildDefaultTraceSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal)
-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle
-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid).
-- Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fil nvarchar(260);
DECLARE @siden nvarchar(19);
SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1;
SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000))
FROM sys.fn_trace_gettable(@fil, DEFAULT)
WHERE EventClass IN (20, 22) AND StartTime > @siden
GO
-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE)
-- Kilde: ErrorLogCollector.RingBufferSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb
CROSS JOIN sys.dm_os_sys_info i
GO
-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer
-- Kilde: ErrorLogCollector.ConnectivitySql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -0,0 +1,118 @@
-- ===================================================================================================================
-- SQLI-LOG-002: I/O-forespørgsler, der tager over 15 sekunder (fejl 833)
-- ===================================================================================================================
-- Regel-id: SQLI-LOG-002
-- Titel: I/O-forespørgsler, der tager over 15 sekunder (fejl 833)
-- Alvor: High
-- Versioner: SQL Server 2005 og nyere. Kræver at fejlloggen selv kan læses (xp_readerrorlog, serverrollen
-- securityadmin): fejl 833 står hverken i default trace eller ring buffer, så ellers er reglen "ikke
-- målbar".
--
-- Hvad tjekkes, og hvorfor:
-- Om storage har brugt over 15 sekunder på I/O-forespørgsler (fejl 833).
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Som SQLI-LOG-001, men kun med kilden "fejlloggen": et fund, når fejl 833 er set inden for de seneste 24 timer.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 24 timer (ErrorLogAnalysis.RecentWindow).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde)
-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ErrorLogCollector
-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs
-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal),
-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal),
-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log)
-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql
-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : SlowIoErrorRule
--
-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace),
-- SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1)
-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og
-- connectivity ring buffer (kun fejlnumre).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ErrorLogCollector.CanReadErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql
SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END,
DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE()
GO
-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin)
-- Kilde: ErrorLogCollector.BuildErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal)
-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx
-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fra nvarchar(19);
DECLARE @til nvarchar(19);
SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126);
EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.DefaultTracePathSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql
SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.BuildDefaultTraceSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal)
-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle
-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid).
-- Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fil nvarchar(260);
DECLARE @siden nvarchar(19);
SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1;
SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000))
FROM sys.fn_trace_gettable(@fil, DEFAULT)
WHERE EventClass IN (20, 22) AND StartTime > @siden
GO
-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE)
-- Kilde: ErrorLogCollector.RingBufferSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb
CROSS JOIN sys.dm_os_sys_info i
GO
-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer
-- Kilde: ErrorLogCollector.ConnectivitySql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -0,0 +1,117 @@
-- ===================================================================================================================
-- SQLI-LOG-003: Alvorlige fejl i SQL Servers fejllog (severity 17 og derover)
-- ===================================================================================================================
-- Regel-id: SQLI-LOG-003
-- Titel: Alvorlige fejl i SQL Servers fejllog (severity 17 og derover)
-- Alvor: High fra severity 20; ellers Medium.
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om fejlloggen indeholder alvorlige fejl (severity 17 og derover), som ikke er korruption, langsom I/O, mislykkede
-- logins, stack dumps eller AG-fejl (de har egne regler).
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fejl grupperes pr. fejlnummer; ét fund pr. fejlnummer med severity >= 17 set inden for de seneste 24 timer.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Severity >= 17; 24 timer.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde)
-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ErrorLogCollector
-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs
-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal),
-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal),
-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log)
-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql
-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : SevereErrorRule
--
-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace),
-- SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1)
-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og
-- connectivity ring buffer (kun fejlnumre).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ErrorLogCollector.CanReadErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql
SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END,
DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE()
GO
-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin)
-- Kilde: ErrorLogCollector.BuildErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal)
-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx
-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fra nvarchar(19);
DECLARE @til nvarchar(19);
SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126);
EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.DefaultTracePathSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql
SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.BuildDefaultTraceSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal)
-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle
-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid).
-- Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fil nvarchar(260);
DECLARE @siden nvarchar(19);
SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1;
SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000))
FROM sys.fn_trace_gettable(@fil, DEFAULT)
WHERE EventClass IN (20, 22) AND StartTime > @siden
GO
-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE)
-- Kilde: ErrorLogCollector.RingBufferSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb
CROSS JOIN sys.dm_os_sys_info i
GO
-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer
-- Kilde: ErrorLogCollector.ConnectivitySql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -0,0 +1,118 @@
-- ===================================================================================================================
-- SQLI-LOG-004: Mange mislykkede logins
-- ===================================================================================================================
-- Regel-id: SQLI-LOG-004
-- Titel: Mange mislykkede logins
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere (fra ring buffer kun 2008+ via connectivity ring buffer).
--
-- Hvad tjekkes, og hvorfor:
-- Om der er mange mislykkede logins (fejl 18456) på kort tid: forkert kodeord i en applikation eller et forsøg på
-- at gætte kodeord.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Kun den seneste måling (ikke døgnets sum): fund hvis antallet af fejl 18456 siden forrige måling (normalt 5
-- minutter) er mindst grænsen.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:ErrorLog:LoginFailureBurst, standard 20.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde)
-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ErrorLogCollector
-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs
-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal),
-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal),
-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log)
-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql
-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : LoginFailureBurstRule
--
-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace),
-- SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1)
-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og
-- connectivity ring buffer (kun fejlnumre).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ErrorLogCollector.CanReadErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql
SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END,
DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE()
GO
-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin)
-- Kilde: ErrorLogCollector.BuildErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal)
-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx
-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fra nvarchar(19);
DECLARE @til nvarchar(19);
SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126);
EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.DefaultTracePathSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql
SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.BuildDefaultTraceSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal)
-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle
-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid).
-- Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fil nvarchar(260);
DECLARE @siden nvarchar(19);
SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1;
SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000))
FROM sys.fn_trace_gettable(@fil, DEFAULT)
WHERE EventClass IN (20, 22) AND StartTime > @siden
GO
-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE)
-- Kilde: ErrorLogCollector.RingBufferSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb
CROSS JOIN sys.dm_os_sys_info i
GO
-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer
-- Kilde: ErrorLogCollector.ConnectivitySql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -0,0 +1,116 @@
-- ===================================================================================================================
-- SQLI-LOG-005: SQL Server har lavet et stack dump
-- ===================================================================================================================
-- Regel-id: SQLI-LOG-005
-- Titel: SQL Server har lavet et stack dump
-- Alvor: High
-- Versioner: SQL Server 2005 og nyere. Kræver at fejlloggen selv kan læses (securityadmin); ellers "ikke målbar".
--
-- Hvad tjekkes, og hvorfor:
-- Om SQL Server har lavet et stack dump (intern fejl, access violation, assertion).
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fejl 17065, 17066, 17067, 17310 eller tekst med "Stack Dump" i fejlloggen inden for de seneste 24 timer.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 24 timer.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde)
-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ErrorLogCollector
-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs
-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal),
-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal),
-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log)
-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql
-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : StackDumpRule
--
-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace),
-- SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1)
-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og
-- connectivity ring buffer (kun fejlnumre).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ErrorLogCollector.CanReadErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql
SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END,
DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE()
GO
-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin)
-- Kilde: ErrorLogCollector.BuildErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal)
-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx
-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fra nvarchar(19);
DECLARE @til nvarchar(19);
SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126);
EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.DefaultTracePathSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql
SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.BuildDefaultTraceSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal)
-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle
-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid).
-- Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fil nvarchar(260);
DECLARE @siden nvarchar(19);
SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1;
SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000))
FROM sys.fn_trace_gettable(@fil, DEFAULT)
WHERE EventClass IN (20, 22) AND StartTime > @siden
GO
-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE)
-- Kilde: ErrorLogCollector.RingBufferSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb
CROSS JOIN sys.dm_os_sys_info i
GO
-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer
-- Kilde: ErrorLogCollector.ConnectivitySql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -0,0 +1,117 @@
-- ===================================================================================================================
-- SQLI-LOG-006: Fejl i Always On availability groups
-- ===================================================================================================================
-- Regel-id: SQLI-LOG-006
-- Titel: Fejl i Always On availability groups
-- Alvor: High
-- Versioner: SQL Server 2005 og nyere (relevant fra 2012).
--
-- Hvad tjekkes, og hvorfor:
-- Om fejlloggen viser fejl fra Always On availability groups.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fejl 1480, 19406, 41131, 41142, 41143, 41144, 35201-35299, eller tekst med "availability group" og severity >= 16
-- eller "failed", inden for de seneste 24 timer. Ét fund pr. fejlnummer.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 24 timer.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde)
-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ErrorLogCollector
-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs
-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal),
-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal),
-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log)
-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql
-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : AvailabilityGroupErrorRule
--
-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace),
-- SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1)
-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og
-- connectivity ring buffer (kun fejlnumre).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ErrorLogCollector.CanReadErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql
SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END,
DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE()
GO
-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin)
-- Kilde: ErrorLogCollector.BuildErrorLogSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal)
-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx
-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fra nvarchar(19);
DECLARE @til nvarchar(19);
SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126);
EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.DefaultTracePathSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql
SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1
GO
-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)
-- Kilde: ErrorLogCollector.BuildDefaultTraceSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal)
-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle
-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid).
-- Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fil nvarchar(260);
DECLARE @siden nvarchar(19);
SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1;
SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000))
FROM sys.fn_trace_gettable(@fil, DEFAULT)
WHERE EventClass IN (20, 22) AND StartTime > @siden
GO
-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE)
-- Kilde: ErrorLogCollector.RingBufferSql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb
CROSS JOIN sys.dm_os_sys_info i
GO
-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer
-- Kilde: ErrorLogCollector.ConnectivitySql
-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql
SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())),
rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)')
FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -0,0 +1,137 @@
-- ===================================================================================================================
-- SQLI-MEM-001: Forespørgsler venter på hukommelse
-- ===================================================================================================================
-- Regel-id: SQLI-MEM-001
-- Titel: Forespørgsler venter på hukommelse
-- Alvor: High
-- Versioner: Alle (SQL Server 2000 via master.dbo.sysperfinfo).
--
-- Hvad tjekkes, og hvorfor:
-- Om forespørgsler står i kø for at få tildelt hukommelse (RESOURCE_SEMAPHORE), før de kan starte.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis tælleren 'Memory Grants Pending' (øjebliksværdi) > 0, eller RESOURCE_SEMAPHORE har ventet mindst 10.000
-- ms i intervallet mellem to ventetidsmålinger.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Pending > 0 eller >= 10.000 ms RESOURCE_SEMAPHORE (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder).
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- PerformanceCounterCollector
-- Fil: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs
-- Medlemmer: Db.StartTimeSql(version), PerformanceCounterCollector.BuildSql(dmv: true),
-- PerformanceCounterCollector.BuildSql(dmv: false)
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:performance-counters)
-- Delt script: SQL Scripts/_collectors/PerformanceCounterCollector.sql
-- WaitStatsCollector
-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs
-- Medlemmer: Db.StartTimeSql(version), WaitStatsCollector.Sql2005, WaitStatsCollector.Sql2000
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats)
-- Delt script: SQL Scripts/_collectors/WaitStatsCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/ResourceRules.cs : MemoryGrantsPendingRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000),
-- SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PerformanceCounterCollector: Udvalgte performance counters: memory grants, page life expectancy, forbindelser,
-- blokerede processer, batch requests og kompileringer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: PerformanceCounterCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: PerformanceCounterCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt
-- Kilde: PerformanceCounterCollector.StartTimeSql(2000)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: PerformanceCounterCollector.BuildSql(dmv)
-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: true)
SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint)
FROM sys.dm_os_performance_counters
WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding'))
OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy')
OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked'))
OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec'))
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: PerformanceCounterCollector.BuildSql(sysperfinfo)
-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: false)
SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint)
FROM master.dbo.sysperfinfo
WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding'))
OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy')
OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked'))
OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec'))
GO
-- -------------------------------------------------------------------------------------------------------------------
-- WaitStatsCollector: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt
-- starttidspunkt) nulstiller.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal)
-- Kilde: WaitStatsCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: WaitStatsCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt
-- Kilde: WaitStatsCollector.StartTimeSql(2000)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: WaitStatsCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005
SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE wait_time_ms > 0
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: WaitStatsCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000
DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS
GO

View file

@ -0,0 +1,66 @@
-- ===================================================================================================================
-- SQLI-OPS-001: Backup eller restore kører markant længere end normalt
-- ===================================================================================================================
-- Regel-id: SQLI-OPS-001
-- Titel: Backup eller restore kører markant længere end normalt
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om en backup eller restore kører markant længere end samme kommando på samme database plejer.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Hvert minut læses de kørende operationer med percent_complete > 0. Afsluttede operationer på mindst 1 minut
-- huskes (de seneste 10 pr. kommando og database). Fund for kørende BACKUP/RESTORE, når der er mindst 3 tidligere
-- kørsler, og den har kørt mere end 2 x medianen og mindst 5 minutter længere end medianen.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Faktor 2 (SlowBackupRule.Factor); mindst 5 minutter over medianen; mindst 3 tidligere kørsler (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- LongOperationCollector
-- Fil: src/SQLInsight.Collectors/Instance/LongOperationCollector.cs
-- Medlemmer: LongOperationCollector.Sql
-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:long-operations)
-- Delt script: SQL Scripts/_collectors/LongOperationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/OperationRules.cs : SlowBackupRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- LongOperationCollector: Operationer, SQL Server rapporterer fremdrift for (BACKUP, RESTORE, DBCC, ALTER INDEX
-- REORGANIZE, SHRINK, ROLLBACK ...).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: LongOperationCollector.Sql
-- Kode: src/SQLInsight.Collectors/Instance/LongOperationCollector.cs : LongOperationCollector.Sql
SELECT r.session_id, r.command, DB_NAME(r.database_id), s.login_name,
DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), r.start_time),
CAST(r.percent_complete AS float),
CASE WHEN r.estimated_completion_time > 0 THEN DATEADD(second, CAST(r.estimated_completion_time / 1000 AS int), GETUTCDATE()) END,
GETUTCDATE()
FROM sys.dm_exec_requests r
LEFT JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id
WHERE r.percent_complete > 0 AND r.session_id <> @@SPID
GO

View file

@ -0,0 +1,127 @@
-- ===================================================================================================================
-- SQLI-PLAN-001: Implicit konvertering forhindrer index seek
-- ===================================================================================================================
-- Regel-id: SQLI-PLAN-001
-- Titel: Implicit konvertering forhindrer index seek
-- Alvor: Medium
-- Versioner: SQL Server 2005 SP2 og nyere (sys.dm_exec_text_query_plan).
--
-- Hvad tjekkes, og hvorfor:
-- Om en tung forespørgsel har en implicit konvertering, der forhindrer index seek.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- De 50 sætninger med mest CPU (kørt det seneste døgn) får deres cachede plan analyseret i C# (ShowplanAnalyzer;
-- planer over 4 MB springes over): fund hvis planen har PlanAffectingConvert med ConvertIssue = "Seek Plan". Højst
-- 10 fund, mest CPU først.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Top 50 (PlanAnalysisCollector.TopStatements); 10 fund (PlanRule.MaxFindingsPerRule), fast i koden.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- PlanAnalysisCollector
-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs
-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis)
-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : ImplicitConversionRule
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som
-- analyseres i C# (ShowplanAnalyzer).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner)
-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb,
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3
-- Kilde: PlanAnalysisCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2005 SP2 og nyere 2005-builds
-- Kilde: PlanAnalysisCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO

View file

@ -0,0 +1,126 @@
-- ===================================================================================================================
-- SQLI-PLAN-002: Join uden join-betingelse (kartesisk produkt)
-- ===================================================================================================================
-- Regel-id: SQLI-PLAN-002
-- Titel: Join uden join-betingelse (kartesisk produkt)
-- Alvor: High
-- Versioner: SQL Server 2005 SP2 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om en tung forespørgsel joiner uden join-betingelse (utilsigtet kartesisk produkt).
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Planen analyseres i C#: fund hvis den har en Warnings-node med NoJoinPredicate = true, og sætningsteksten ikke
-- indeholder CROSS JOIN (bevidst). Højst 10 fund.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Top 50 sætninger; 10 fund (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- PlanAnalysisCollector
-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs
-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis)
-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : NoJoinPredicateRule
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som
-- analyseres i C# (ShowplanAnalyzer).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner)
-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb,
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3
-- Kilde: PlanAnalysisCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2005 SP2 og nyere 2005-builds
-- Kilde: PlanAnalysisCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO

View file

@ -0,0 +1,126 @@
-- ===================================================================================================================
-- SQLI-PLAN-003: Skalar funktion kaldes for hver række
-- ===================================================================================================================
-- Regel-id: SQLI-PLAN-003
-- Titel: Skalar funktion kaldes for hver række
-- Alvor: Medium
-- Versioner: SQL Server 2005 SP2 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om en tung forespørgsel kalder en skalar brugerfunktion for hver række.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Planen analyseres i C#: fund hvis den indeholder UserDefinedFunction-noder, og sætningens samlede CPU-tid er
-- mindst 60 sekunder. Højst 10 fund.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 60.000.000 µs CPU (ScalarUdfRule.MinTotalWorkerTimeUs, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- PlanAnalysisCollector
-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs
-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis)
-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : ScalarUdfRule
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som
-- analyseres i C# (ShowplanAnalyzer).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner)
-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb,
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3
-- Kilde: PlanAnalysisCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2005 SP2 og nyere 2005-builds
-- Kilde: PlanAnalysisCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO

View file

@ -0,0 +1,126 @@
-- ===================================================================================================================
-- SQLI-PLAN-004: Sorteringer og hash joins spilder til tempdb
-- ===================================================================================================================
-- Regel-id: SQLI-PLAN-004
-- Titel: Sorteringer og hash joins spilder til tempdb
-- Alvor: Medium
-- Versioner: SQL Server 2016 SP2 / 2017 CU3 og nyere (total_spills i sys.dm_exec_query_stats). Ældre: "tjek
-- manuelt".
--
-- Hvad tjekkes, og hvorfor:
-- Om sorteringer og hash joins i en tung forespørgsel spilder til tempdb.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis total_spills / execution_count >= 1.024 sider (ca. 8 MB) pr. udførelse. Højst 10 fund.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 1.024 sider pr. udførelse (SpillRule.MinSpillPagesPerExecution, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- PlanAnalysisCollector
-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs
-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis)
-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : SpillRule
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som
-- analyseres i C# (ShowplanAnalyzer).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner)
-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb,
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3
-- Kilde: PlanAnalysisCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2005 SP2 og nyere 2005-builds
-- Kilde: PlanAnalysisCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO

View file

@ -0,0 +1,126 @@
-- ===================================================================================================================
-- SQLI-PLAN-005: Forespørgsel får langt mere hukommelse, end den bruger
-- ===================================================================================================================
-- Regel-id: SQLI-PLAN-005
-- Titel: Forespørgsel får langt mere hukommelse, end den bruger
-- Alvor: Medium
-- Versioner: SQL Server 2016 SP2 / 2017 CU3 og nyere (total_grant_kb, total_used_grant_kb). Ældre: "tjek manuelt".
--
-- Hvad tjekkes, og hvorfor:
-- Om en forespørgsel får tildelt langt mere hukommelse, end den bruger.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund hvis gennemsnitlig tildeling (total_grant_kb / execution_count) >= 100 MB, og brugt (total_used_grant_kb) <=
-- 10 % af tildelt. Højst 10 fund.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 102.400 KB pr. udførelse (ExcessiveGrantRule.MinAverageGrantKb) og 10 % (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- PlanAnalysisCollector
-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs
-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true),
-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis)
-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : ExcessiveGrantRule
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som
-- analyseres i C# (ShowplanAnalyzer).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner)
-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb,
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3
-- Kilde: PlanAnalysisCollector.BuildSql(2008)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT CONVERT(varchar(20), qs.query_hash, 1),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO
-- Variant: SQL Server 2005 SP2 og nyere 2005-builds
-- Kilde: PlanAnalysisCollector.BuildSql(2005)
-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)
WITH top_statements AS (
SELECT TOP (50) qs.*
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE())
ORDER BY qs.total_worker_time DESC
)
SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)),
DB_NAME(CONVERT(int, pa.value)),
LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000),
qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads,
CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint),
qs.last_execution_time,
tp.query_plan
FROM top_statements qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t
OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp
OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa
ORDER BY qs.total_worker_time DESC;
GO

116
SQL Scripts/SQLI-QS-001.sql Normal file
View file

@ -0,0 +1,116 @@
-- ===================================================================================================================
-- SQLI-QS-001: Planregression: forespørgsel har fået en langsommere plan
-- ===================================================================================================================
-- Regel-id: SQLI-QS-001
-- Titel: Planregression: forespørgsel har fået en langsommere plan
-- Alvor: Medium; High når ekstra tid er mindst 10 minutter i døgnet.
-- Versioner: SQL Server 2016 og nyere (Query Store), i databaser med Query Store slået til.
--
-- Hvad tjekkes, og hvorfor:
-- Om en forespørgsel har fået en ny plan det seneste døgn, der er markant langsommere end dens bedste plan ugen
-- før.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Collectoren henter de 100 tungeste queries (samlet varighed over 8 dage) pr. database, opdelt i seneste døgn og
-- ugen før. Bedste plan = laveste gns. varighed før det seneste døgn med mindst 10 udførelser. Fund når en anden
-- plan i seneste døgn med mindst 10 udførelser er mindst 2 x langsommere og koster mindst 60 s ekstra i alt ((gns.
-- ny - gns. bedste) x udførelser). Højst 10 fund, største ekstra tid først.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 10 udførelser, faktor 2, 60 s ekstra (PlanRegressionRule-konstanter, fast i koden); 100 queries pr. database
-- (QueryStoreCollector.TopQueries).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- QueryStoreCollector
-- Fil: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs
-- Medlemmer: QueryStoreCollector.DatabasesSql, QueryStoreCollector.BuildStatsSql(database),
-- QueryStoreCollector.BuildFailedForcedSql(database)
-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:query-store)
-- Delt script: SQL Scripts/_collectors/QueryStoreCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/QueryStoreRules.cs : PlanRegressionRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt lokale variabler, SET-sessionsindstillinger. Intet ændrer data, indstillinger
-- eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- QueryStoreCollector: Query Store i hver brugerdatabase, hvor det er slået til: de 100 tungeste queries over 8 dage
-- og forcerede planer, der fejler.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 og nyere
-- Kilde: QueryStoreCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.DatabasesSql
SELECT name FROM sys.databases
WHERE is_query_store_on = 1 AND state = 0 AND database_id > 4
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for msdb)
-- Kilde: QueryStoreCollector.BuildStatsSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.BuildStatsSql(database)
DECLARE @now datetimeoffset = SYSDATETIMEOFFSET();
DECLARE @recent datetimeoffset = DATEADD(hour, -24, @now);
DECLARE @history datetimeoffset = DATEADD(day, -8, @now);
WITH stats AS (
SELECT p.query_id, p.plan_id,
CASE WHEN i.start_time >= @recent THEN 1 ELSE 0 END AS is_recent,
SUM(rs.count_executions) AS execs,
SUM(rs.avg_duration * rs.count_executions) AS total_duration,
SUM(rs.avg_cpu_time * rs.count_executions) AS total_cpu,
SUM(rs.avg_logical_io_reads * rs.count_executions) AS total_reads,
MAX(rs.stdev_duration) AS max_stdev_duration,
MAX(rs.max_duration) AS max_duration,
MIN(rs.min_duration) AS min_duration,
MAX(rs.last_execution_time) AS last_execution
FROM [msdb].sys.query_store_runtime_stats rs
JOIN [msdb].sys.query_store_runtime_stats_interval i ON i.runtime_stats_interval_id = rs.runtime_stats_interval_id
JOIN [msdb].sys.query_store_plan p ON p.plan_id = rs.plan_id
WHERE i.start_time >= @history
GROUP BY p.query_id, p.plan_id, CASE WHEN i.start_time >= @recent THEN 1 ELSE 0 END
), top_queries AS (
SELECT TOP (100) query_id FROM stats GROUP BY query_id ORDER BY SUM(total_duration) DESC
)
SELECT s.query_id, s.plan_id, s.is_recent, s.execs, s.total_duration, s.total_cpu, s.total_reads,
s.max_stdev_duration, s.max_duration, s.min_duration,
CAST(SWITCHOFFSET(s.last_execution, '+00:00') AS datetime2(0)),
p.is_forced_plan, LEFT(qt.query_sql_text, 2000),
OBJECT_SCHEMA_NAME(q.object_id, DB_ID(N'msdb')) + N'.' + OBJECT_NAME(q.object_id, DB_ID(N'msdb'))
FROM stats s
JOIN top_queries t ON t.query_id = s.query_id
JOIN [msdb].sys.query_store_plan p ON p.plan_id = s.plan_id
JOIN [msdb].sys.query_store_query q ON q.query_id = s.query_id
JOIN [msdb].sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id;
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for msdb)
-- Kilde: QueryStoreCollector.BuildFailedForcedSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.BuildFailedForcedSql(database)
SELECT p.query_id, p.plan_id, p.force_failure_count, p.last_force_failure_reason_desc, LEFT(qt.query_sql_text, 2000)
FROM [msdb].sys.query_store_plan p
JOIN [msdb].sys.query_store_query q ON q.query_id = p.query_id
JOIN [msdb].sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id
WHERE p.is_forced_plan = 1 AND p.force_failure_count > 0;
GO

111
SQL Scripts/SQLI-QS-002.sql Normal file
View file

@ -0,0 +1,111 @@
-- ===================================================================================================================
-- SQLI-QS-002: Forceret plan kan ikke bruges
-- ===================================================================================================================
-- Regel-id: SQLI-QS-002
-- Titel: Forceret plan kan ikke bruges
-- Alvor: High
-- Versioner: SQL Server 2016 og nyere (Query Store).
--
-- Hvad tjekkes, og hvorfor:
-- Om en plan, der er forceret i Query Store, ikke længere kan bruges (fx fordi et index er fjernet).
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. plan med is_forced_plan = 1 og force_failure_count > 0 (højst 10).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- QueryStoreCollector
-- Fil: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs
-- Medlemmer: QueryStoreCollector.DatabasesSql, QueryStoreCollector.BuildStatsSql(database),
-- QueryStoreCollector.BuildFailedForcedSql(database)
-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:query-store)
-- Delt script: SQL Scripts/_collectors/QueryStoreCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/QueryStoreRules.cs : FailedForcedPlanRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt lokale variabler, SET-sessionsindstillinger. Intet ændrer data, indstillinger
-- eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- QueryStoreCollector: Query Store i hver brugerdatabase, hvor det er slået til: de 100 tungeste queries over 8 dage
-- og forcerede planer, der fejler.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 og nyere
-- Kilde: QueryStoreCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.DatabasesSql
SELECT name FROM sys.databases
WHERE is_query_store_on = 1 AND state = 0 AND database_id > 4
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for msdb)
-- Kilde: QueryStoreCollector.BuildStatsSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.BuildStatsSql(database)
DECLARE @now datetimeoffset = SYSDATETIMEOFFSET();
DECLARE @recent datetimeoffset = DATEADD(hour, -24, @now);
DECLARE @history datetimeoffset = DATEADD(day, -8, @now);
WITH stats AS (
SELECT p.query_id, p.plan_id,
CASE WHEN i.start_time >= @recent THEN 1 ELSE 0 END AS is_recent,
SUM(rs.count_executions) AS execs,
SUM(rs.avg_duration * rs.count_executions) AS total_duration,
SUM(rs.avg_cpu_time * rs.count_executions) AS total_cpu,
SUM(rs.avg_logical_io_reads * rs.count_executions) AS total_reads,
MAX(rs.stdev_duration) AS max_stdev_duration,
MAX(rs.max_duration) AS max_duration,
MIN(rs.min_duration) AS min_duration,
MAX(rs.last_execution_time) AS last_execution
FROM [msdb].sys.query_store_runtime_stats rs
JOIN [msdb].sys.query_store_runtime_stats_interval i ON i.runtime_stats_interval_id = rs.runtime_stats_interval_id
JOIN [msdb].sys.query_store_plan p ON p.plan_id = rs.plan_id
WHERE i.start_time >= @history
GROUP BY p.query_id, p.plan_id, CASE WHEN i.start_time >= @recent THEN 1 ELSE 0 END
), top_queries AS (
SELECT TOP (100) query_id FROM stats GROUP BY query_id ORDER BY SUM(total_duration) DESC
)
SELECT s.query_id, s.plan_id, s.is_recent, s.execs, s.total_duration, s.total_cpu, s.total_reads,
s.max_stdev_duration, s.max_duration, s.min_duration,
CAST(SWITCHOFFSET(s.last_execution, '+00:00') AS datetime2(0)),
p.is_forced_plan, LEFT(qt.query_sql_text, 2000),
OBJECT_SCHEMA_NAME(q.object_id, DB_ID(N'msdb')) + N'.' + OBJECT_NAME(q.object_id, DB_ID(N'msdb'))
FROM stats s
JOIN top_queries t ON t.query_id = s.query_id
JOIN [msdb].sys.query_store_plan p ON p.plan_id = s.plan_id
JOIN [msdb].sys.query_store_query q ON q.query_id = s.query_id
JOIN [msdb].sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id;
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for msdb)
-- Kilde: QueryStoreCollector.BuildFailedForcedSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.BuildFailedForcedSql(database)
SELECT p.query_id, p.plan_id, p.force_failure_count, p.last_force_failure_reason_desc, LEFT(qt.query_sql_text, 2000)
FROM [msdb].sys.query_store_plan p
JOIN [msdb].sys.query_store_query q ON q.query_id = p.query_id
JOIN [msdb].sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id
WHERE p.is_forced_plan = 1 AND p.force_failure_count > 0;
GO

115
SQL Scripts/SQLI-QS-003.sql Normal file
View file

@ -0,0 +1,115 @@
-- ===================================================================================================================
-- SQLI-QS-003: Parameterfølsom forespørgsel (parameter sniffing)
-- ===================================================================================================================
-- Regel-id: SQLI-QS-003
-- Titel: Parameterfølsom forespørgsel (parameter sniffing)
-- Alvor: Medium
-- Versioner: SQL Server 2016 og nyere (Query Store).
--
-- Hvad tjekkes, og hvorfor:
-- Om en forespørgsel har meget svingende svartider eller skifter mellem meget forskellige planer (parameter
-- sniffing).
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For hver query i seneste døgn, planer med mindst 10 udførelser. Fund hvis (a) mindst 2 planer, hvor den
-- langsomste i gennemsnit er mindst 10 x den hurtigste og mindst 100 ms, eller (b) den mest brugte plan har mindst
-- 100 udførelser, max-varighed >= 1 s, max/gns. >= 20 og max stdev/gns. >= 2. Højst 10 fund, mest samlet varighed
-- først.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 100 udførelser og 1 s (ParameterSensitivityRule-konstanter) samt faktorerne 10, 20 og 2 (fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- QueryStoreCollector
-- Fil: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs
-- Medlemmer: QueryStoreCollector.DatabasesSql, QueryStoreCollector.BuildStatsSql(database),
-- QueryStoreCollector.BuildFailedForcedSql(database)
-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:query-store)
-- Delt script: SQL Scripts/_collectors/QueryStoreCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/QueryStoreRules.cs : ParameterSensitivityRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt lokale variabler, SET-sessionsindstillinger. Intet ændrer data, indstillinger
-- eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- QueryStoreCollector: Query Store i hver brugerdatabase, hvor det er slået til: de 100 tungeste queries over 8 dage
-- og forcerede planer, der fejler.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 og nyere
-- Kilde: QueryStoreCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.DatabasesSql
SELECT name FROM sys.databases
WHERE is_query_store_on = 1 AND state = 0 AND database_id > 4
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for msdb)
-- Kilde: QueryStoreCollector.BuildStatsSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.BuildStatsSql(database)
DECLARE @now datetimeoffset = SYSDATETIMEOFFSET();
DECLARE @recent datetimeoffset = DATEADD(hour, -24, @now);
DECLARE @history datetimeoffset = DATEADD(day, -8, @now);
WITH stats AS (
SELECT p.query_id, p.plan_id,
CASE WHEN i.start_time >= @recent THEN 1 ELSE 0 END AS is_recent,
SUM(rs.count_executions) AS execs,
SUM(rs.avg_duration * rs.count_executions) AS total_duration,
SUM(rs.avg_cpu_time * rs.count_executions) AS total_cpu,
SUM(rs.avg_logical_io_reads * rs.count_executions) AS total_reads,
MAX(rs.stdev_duration) AS max_stdev_duration,
MAX(rs.max_duration) AS max_duration,
MIN(rs.min_duration) AS min_duration,
MAX(rs.last_execution_time) AS last_execution
FROM [msdb].sys.query_store_runtime_stats rs
JOIN [msdb].sys.query_store_runtime_stats_interval i ON i.runtime_stats_interval_id = rs.runtime_stats_interval_id
JOIN [msdb].sys.query_store_plan p ON p.plan_id = rs.plan_id
WHERE i.start_time >= @history
GROUP BY p.query_id, p.plan_id, CASE WHEN i.start_time >= @recent THEN 1 ELSE 0 END
), top_queries AS (
SELECT TOP (100) query_id FROM stats GROUP BY query_id ORDER BY SUM(total_duration) DESC
)
SELECT s.query_id, s.plan_id, s.is_recent, s.execs, s.total_duration, s.total_cpu, s.total_reads,
s.max_stdev_duration, s.max_duration, s.min_duration,
CAST(SWITCHOFFSET(s.last_execution, '+00:00') AS datetime2(0)),
p.is_forced_plan, LEFT(qt.query_sql_text, 2000),
OBJECT_SCHEMA_NAME(q.object_id, DB_ID(N'msdb')) + N'.' + OBJECT_NAME(q.object_id, DB_ID(N'msdb'))
FROM stats s
JOIN top_queries t ON t.query_id = s.query_id
JOIN [msdb].sys.query_store_plan p ON p.plan_id = s.plan_id
JOIN [msdb].sys.query_store_query q ON q.query_id = s.query_id
JOIN [msdb].sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id;
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for msdb)
-- Kilde: QueryStoreCollector.BuildFailedForcedSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.BuildFailedForcedSql(database)
SELECT p.query_id, p.plan_id, p.force_failure_count, p.last_force_failure_reason_desc, LEFT(qt.query_sql_text, 2000)
FROM [msdb].sys.query_store_plan p
JOIN [msdb].sys.query_store_query q ON q.query_id = p.query_id
JOIN [msdb].sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id
WHERE p.is_forced_plan = 1 AND p.force_failure_count > 0;
GO

View file

@ -0,0 +1,88 @@
-- ===================================================================================================================
-- SQLI-REPL-001: Replikering fejler
-- ===================================================================================================================
-- Regel-id: SQLI-REPL-001
-- Titel: Replikering fejler
-- Alvor: High
-- Versioner: SQL Server 2005 og nyere. Kun relevant på en distributør (sys.databases.is_distributor = 1); kræver
-- SELECT på tre tabeller i distributionsdatabasen, ellers "ikke målbar" med vejledning.
--
-- Hvad tjekkes, og hvorfor:
-- Om en distributionsagent er fejlet eller bliver ved med at prøve igen.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For hver distributionsagent læses seneste historikpost (MSdistribution_history, højeste timestamp). Fund hvis
-- runstatus er 5 (retry) eller 6 (fejl).
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- Kun på en distributør, valgfrit (gives ikke af grant-least-privilege.sql):
-- USE distribution; CREATE USER [agent] FOR LOGIN [agent];
-- GRANT SELECT ON dbo.MSdistribution_agents TO [agent]; GRANT SELECT ON dbo.MSdistribution_history TO [agent]; GRANT SELECT ON dbo.MSrepl_errors TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ReplicationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs
-- Medlemmer: ReplicationCollector.DistributionDatabaseSql, Db.CurrentDatabaseSql,
-- ReplicationCollector.AgentsSql, ReplicationCollector.ErrorsSql
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:replication)
-- Delt script: SQL Scripts/_collectors/ReplicationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Availability/ReplicationRules.cs : ReplicationAgentFailedRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ReplicationCollector: På en distributør: hver distributionsagents seneste status, forsinkelse og besked samt
-- replikeringsfejl det seneste døgn.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ReplicationCollector.DistributionDatabaseSql
-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.DistributionDatabaseSql
SELECT TOP 1 name FROM sys.databases WHERE is_distributor = 1 AND state = 0
GO
-- Variant: Kun på en distributør: husk databasen, der skal skiftes tilbage til
-- Kilde: ReplicationCollector.CurrentDatabaseSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.CurrentDatabaseSql
SELECT DB_NAME()
GO
-- Variant: Kun på en distributør, i distributionsdatabasen
-- Kilde: ReplicationCollector.AgentsSql
-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.AgentsSql
-- Agenten sender "USE [<distributionsdatabasen>];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution.
USE [distribution];
SELECT TOP 500 a.name, a.publisher_db, h.runstatus, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), h.time), h.delivery_latency,
CAST(h.comments AS nvarchar(500))
FROM dbo.MSdistribution_agents a
OUTER APPLY (SELECT TOP 1 x.runstatus, x.time, x.delivery_latency, x.comments FROM dbo.MSdistribution_history x
WHERE x.agent_id = a.id ORDER BY x.timestamp DESC) h
GO
-- Variant: Kun på en distributør, i distributionsdatabasen
-- Kilde: ReplicationCollector.ErrorsSql
-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.ErrorsSql
-- Agenten sender "USE [<distributionsdatabasen>];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution.
USE [distribution];
SELECT COUNT(*) FROM dbo.MSrepl_errors WHERE time > DATEADD(hour, -24, GETDATE())
GO

View file

@ -0,0 +1,86 @@
-- ===================================================================================================================
-- SQLI-REPL-002: Replikering er forsinket
-- ===================================================================================================================
-- Regel-id: SQLI-REPL-002
-- Titel: Replikering er forsinket
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere, kun på en distributør (som SQLI-REPL-001).
--
-- Hvad tjekkes, og hvorfor:
-- Om ændringer tager lang tid om at nå abonnenten.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fund for agenter, der ikke er fejlet, hvor delivery_latency i seneste historikpost er mindst 5 minutter.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 300.000 ms (ReplicationSnapshot.LatencyThresholdMs, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- Kun på en distributør, valgfrit (gives ikke af grant-least-privilege.sql):
-- USE distribution; CREATE USER [agent] FOR LOGIN [agent];
-- GRANT SELECT ON dbo.MSdistribution_agents TO [agent]; GRANT SELECT ON dbo.MSdistribution_history TO [agent]; GRANT SELECT ON dbo.MSrepl_errors TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ReplicationCollector
-- Fil: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs
-- Medlemmer: ReplicationCollector.DistributionDatabaseSql, Db.CurrentDatabaseSql,
-- ReplicationCollector.AgentsSql, ReplicationCollector.ErrorsSql
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:replication)
-- Delt script: SQL Scripts/_collectors/ReplicationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Availability/ReplicationRules.cs : ReplicationLatencyRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data,
-- indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ReplicationCollector: På en distributør: hver distributionsagents seneste status, forsinkelse og besked samt
-- replikeringsfejl det seneste døgn.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: ReplicationCollector.DistributionDatabaseSql
-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.DistributionDatabaseSql
SELECT TOP 1 name FROM sys.databases WHERE is_distributor = 1 AND state = 0
GO
-- Variant: Kun på en distributør: husk databasen, der skal skiftes tilbage til
-- Kilde: ReplicationCollector.CurrentDatabaseSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.CurrentDatabaseSql
SELECT DB_NAME()
GO
-- Variant: Kun på en distributør, i distributionsdatabasen
-- Kilde: ReplicationCollector.AgentsSql
-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.AgentsSql
-- Agenten sender "USE [<distributionsdatabasen>];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution.
USE [distribution];
SELECT TOP 500 a.name, a.publisher_db, h.runstatus, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), h.time), h.delivery_latency,
CAST(h.comments AS nvarchar(500))
FROM dbo.MSdistribution_agents a
OUTER APPLY (SELECT TOP 1 x.runstatus, x.time, x.delivery_latency, x.comments FROM dbo.MSdistribution_history x
WHERE x.agent_id = a.id ORDER BY x.timestamp DESC) h
GO
-- Variant: Kun på en distributør, i distributionsdatabasen
-- Kilde: ReplicationCollector.ErrorsSql
-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.ErrorsSql
-- Agenten sender "USE [<distributionsdatabasen>];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution.
USE [distribution];
SELECT COUNT(*) FROM dbo.MSrepl_errors WHERE time > DATEADD(hour, -24, GETDATE())
GO

View file

@ -0,0 +1,93 @@
-- ===================================================================================================================
-- SQLI-REQ-001: Sætning har kørt længe
-- ===================================================================================================================
-- Regel-id: SQLI-REQ-001
-- Titel: Sætning har kørt længe
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om en brugersætning har kørt længe og måske holder låse, som andre venter på.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- SQL'en finder requests med total_elapsed_time over grænsen og percent_complete = 0. I C# fjernes BACKUP, RESTORE
-- og DBCC (de er langvarige operationer) og requests, der venter med en godartet ventetype (WAITFOR, Service Broker
-- m.fl.). Fund for op til 10, længst kørende først.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:Activity:RequestMinutes, standard 15 (minutter) = 900000 ms i SQL'en.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- ActivityCollector
-- Fil: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs
-- Medlemmer: ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt,
-- ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt
-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:activity)
-- Delt script: SQL Scripts/_collectors/ActivityCollector.sql
-- Regelkode: src/SQLInsight.Rules/Concurrency/ActivityRules.cs : LongRunningStatementRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- ActivityCollector: Åbne brugertransaktioner (alder, log) og brugersætninger, der har kørt længere end grænsen.
-- Agentens egne sessioner er udeladt.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere (vist med standardgrænsen 1024 MB log)
-- Kilde: ActivityCollector.BuildTransactionsSql
-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt
SELECT TOP 100 t.session_id, t.transaction_id, t.name, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), t.begin_time),
t.used, t.reserved, t.db, t.dbs, s.login_name, s.host_name, s.program_name, s.status, r.wait_type,
CAST(CASE WHEN r.sql_handle IS NOT NULL
THEN SUBSTRING(q.text, r.statement_start_offset / 2 + 1,
CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END)
ELSE LEFT(q.text, 1000) END AS nvarchar(1000))
FROM (SELECT st.session_id, at.transaction_id, MAX(at.name) AS name, MIN(at.transaction_begin_time) AS begin_time,
ISNULL(SUM(dt.database_transaction_log_bytes_used), 0) AS used, ISNULL(SUM(dt.database_transaction_log_bytes_reserved), 0) AS reserved,
MIN(DB_NAME(dt.database_id)) AS db, COUNT(DISTINCT dt.database_id) AS dbs
FROM sys.dm_tran_session_transactions st
JOIN sys.dm_tran_active_transactions at ON at.transaction_id = st.transaction_id
LEFT JOIN sys.dm_tran_database_transactions dt ON dt.transaction_id = st.transaction_id AND dt.database_id <> 32767
GROUP BY st.session_id, at.transaction_id) t
JOIN sys.dm_exec_sessions s ON s.session_id = t.session_id
OUTER APPLY (SELECT TOP 1 x.sql_handle, x.statement_start_offset, x.statement_end_offset, x.wait_type FROM sys.dm_exec_requests x
WHERE x.session_id = t.session_id ORDER BY x.request_id) r
OUTER APPLY (SELECT TOP 1 c.most_recent_sql_handle FROM sys.dm_exec_connections c WHERE c.session_id = t.session_id) c
OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) q
WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND t.begin_time IS NOT NULL
ORDER BY CASE WHEN t.used + t.reserved >= 1073741824 THEN 0 ELSE 1 END, t.begin_time
GO
-- Variant: SQL Server 2005 og nyere (vist med standardgrænsen 15 minutter)
-- Kilde: ActivityCollector.BuildLongRequestsSql
-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt
SELECT TOP 50 r.session_id, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), r.start_time), r.total_elapsed_time, r.command, r.status,
r.wait_type, r.wait_time, DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, r.blocking_session_id,
CAST(SUBSTRING(q.text, r.statement_start_offset / 2 + 1,
CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) AS nvarchar(1000))
FROM sys.dm_exec_requests r
JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) q
WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND r.total_elapsed_time > 900000 AND r.percent_complete = 0
ORDER BY r.total_elapsed_time DESC
GO

View file

@ -0,0 +1,106 @@
-- ===================================================================================================================
-- SQLI-SEC-001: Ny forhøjet rettighed på serveren
-- ===================================================================================================================
-- Regel-id: SQLI-SEC-001
-- Titel: Ny forhøjet rettighed på serveren
-- Alvor: High for sysadmin, securityadmin og CONTROL SERVER; Medium for de øvrige faste serverroller.
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om nogen har fået sysadmin eller en anden forhøjet serverrettighed - direkte eller ved at blive meldt ind i en
-- Windows-gruppe, der har den.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Medlemmer af de faste serverroller (sysadmin, securityadmin, serveradmin, setupadmin, processadmin, diskadmin,
-- dbcreator, bulkadmin) og indehavere af CONTROL SERVER (GRANT/WITH GRANT) sammenlignes med forrige måling;
-- Windows-grupper foldes ud til personer via Active Directory af agenten (ikke SQL). Nye rettigheder inden for de
-- seneste 7 dage giver ét fund hver. Hvem og hvornår slås op i default trace (EventClass 104, 105, 108) fra en time
-- før forrige måling - kun med ALTER TRACE; uden står "ukendt".
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Fundet holdes åbent i 7 dage (PrivilegeAnalysis.RecentWindow, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde)
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- PrivilegeCollector
-- Fil: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs
-- Medlemmer: PrivilegeCollector.MembersSql, PrivilegeCollector.SqlLoginsSql,
-- PrivilegeCollector.DefaultTraceEnabledSql, PrivilegeCollector.DefaultTracePathSql,
-- PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:privileges)
-- Delt script: SQL Scripts/_collectors/PrivilegeCollector.sql
-- Regelkode: src/SQLInsight.Rules/Security/PrivilegeRules.cs : NewPrivilegeRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt sys.fn_trace_gettable (læser default trace), SET-sessionsindstillinger. Intet
-- ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PrivilegeCollector: Medlemmer af de faste serverroller og indehavere af CONTROL SERVER, SQL-logins'
-- password-politik og - med ALTER TRACE - hvem der ændrede rettighederne (default trace).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: PrivilegeCollector.MembersSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.MembersSql
SELECT p.name, p.type, r.name, p.is_disabled
FROM sys.server_role_members m
JOIN sys.server_principals r ON r.principal_id = m.role_principal_id
JOIN sys.server_principals p ON p.principal_id = m.member_principal_id
WHERE r.name IN (N'sysadmin', N'securityadmin', N'serveradmin', N'setupadmin', N'processadmin', N'diskadmin', N'dbcreator', N'bulkadmin')
UNION ALL
SELECT p.name, p.type, N'CONTROL SERVER', p.is_disabled
FROM sys.server_permissions sp
JOIN sys.server_principals p ON p.principal_id = sp.grantee_principal_id
WHERE sp.permission_name = N'CONTROL SERVER' AND sp.state IN ('G', 'W')
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: PrivilegeCollector.SqlLoginsSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.SqlLoginsSql
SELECT name, is_disabled, is_policy_checked, is_expiration_checked, CASE WHEN principal_id = 1 THEN 1 ELSE 0 END
FROM sys.sql_logins WHERE name NOT LIKE N'##%'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: PrivilegeCollector.DefaultTraceEnabledSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTraceEnabledSql
SELECT CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'default trace enabled'
GO
-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE)
-- Kilde: PrivilegeCollector.DefaultTracePathSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql
SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1
GO
-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE)
-- Kilde: PrivilegeCollector.BuildTraceSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal)
-- Agenten indsætter default trace-filens sti (mappen + log.trc, så alle rollover-filer læses) og serverens lokale tid
-- for en time før forrige måling som literaler. Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fil nvarchar(260);
DECLARE @siden nvarchar(19);
SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1;
SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SELECT StartTime, EventClass, EventSubClass, LoginName, TargetLoginName, RoleName
FROM sys.fn_trace_gettable(@fil, DEFAULT)
WHERE EventClass IN (104, 105, 108) AND StartTime >= @siden
GO

View file

@ -0,0 +1,104 @@
-- ===================================================================================================================
-- SQLI-SEC-002: For mange personer og konti med sysadmin
-- ===================================================================================================================
-- Regel-id: SQLI-SEC-002
-- Titel: For mange personer og konti med sysadmin
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om flere personer og konti har sysadmin end kundens egen grænse.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Aktive konti med sysadmin tælles: SQL- og Windows-logins direkte (ikke tjeneste-identiteter som NT SERVICE\...,
-- NT AUTHORITY\..., ##MS_...## og konti, der ender på $) og de aktive personer i Windows-grupper med sysadmin
-- (foldet ud via Active Directory af agenten). Fund hvis antallet > grænsen. Grupper, der ikke kunne foldes ud,
-- nævnes i målingerne.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:Privileges:MaxSysadminAccounts, standard 3.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde)
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- PrivilegeCollector
-- Fil: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs
-- Medlemmer: PrivilegeCollector.MembersSql, PrivilegeCollector.SqlLoginsSql,
-- PrivilegeCollector.DefaultTraceEnabledSql, PrivilegeCollector.DefaultTracePathSql,
-- PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:privileges)
-- Delt script: SQL Scripts/_collectors/PrivilegeCollector.sql
-- Regelkode: src/SQLInsight.Rules/Security/PrivilegeRules.cs : TooManySysadminsRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt sys.fn_trace_gettable (læser default trace), SET-sessionsindstillinger. Intet
-- ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- PrivilegeCollector: Medlemmer af de faste serverroller og indehavere af CONTROL SERVER, SQL-logins'
-- password-politik og - med ALTER TRACE - hvem der ændrede rettighederne (default trace).
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere
-- Kilde: PrivilegeCollector.MembersSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.MembersSql
SELECT p.name, p.type, r.name, p.is_disabled
FROM sys.server_role_members m
JOIN sys.server_principals r ON r.principal_id = m.role_principal_id
JOIN sys.server_principals p ON p.principal_id = m.member_principal_id
WHERE r.name IN (N'sysadmin', N'securityadmin', N'serveradmin', N'setupadmin', N'processadmin', N'diskadmin', N'dbcreator', N'bulkadmin')
UNION ALL
SELECT p.name, p.type, N'CONTROL SERVER', p.is_disabled
FROM sys.server_permissions sp
JOIN sys.server_principals p ON p.principal_id = sp.grantee_principal_id
WHERE sp.permission_name = N'CONTROL SERVER' AND sp.state IN ('G', 'W')
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: PrivilegeCollector.SqlLoginsSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.SqlLoginsSql
SELECT name, is_disabled, is_policy_checked, is_expiration_checked, CASE WHEN principal_id = 1 THEN 1 ELSE 0 END
FROM sys.sql_logins WHERE name NOT LIKE N'##%'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: PrivilegeCollector.DefaultTraceEnabledSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTraceEnabledSql
SELECT CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'default trace enabled'
GO
-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE)
-- Kilde: PrivilegeCollector.DefaultTracePathSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql
SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1
GO
-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE)
-- Kilde: PrivilegeCollector.BuildTraceSql
-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal)
-- Agenten indsætter default trace-filens sti (mappen + log.trc, så alle rollover-filer læses) og serverens lokale tid
-- for en time før forrige måling som literaler. Her er de variabler, så scriptet kan køres i SSMS.
DECLARE @fil nvarchar(260);
DECLARE @siden nvarchar(19);
SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1;
SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126);
SELECT StartTime, EventClass, EventSubClass, LoginName, TargetLoginName, RoleName
FROM sys.fn_trace_gettable(@fil, DEFAULT)
WHERE EventClass IN (104, 105, 108) AND StartTime >= @siden
GO

View file

@ -0,0 +1,159 @@
-- ===================================================================================================================
-- SQLI-STAT-001: Forældede statistikker på store tabeller
-- ===================================================================================================================
-- Regel-id: SQLI-STAT-001
-- Titel: Forældede statistikker på store tabeller
-- Alvor: Low
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om statistikker på store tabeller er forældede eller aldrig opdateret.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Fra sys.sysindexes (rowmodctr, som kun kræver metadata-adgang): fund hvis STATS_DATE er NULL og tabellen har
-- rækker, eller tabellen har mindst 10.000 rækker og rowmodctr >= 20 % af rækkerne. Højst 10, aldrig opdaterede
-- først.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- 10.000 rækker (MinRows) og 20 % (MinModifiedShare), fast i koden.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- IndexCollector
-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs
-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql,
-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database,
-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database)
-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes)
-- Delt script: SQL Scripts/_collectors/IndexCollector.sql
-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : StaleStatisticsRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase.
-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: IndexCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.DatabasesSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql
SELECT d.name FROM sys.databases d
WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1
AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: IndexCollector.MissingSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql
SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns,
gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek
FROM sys.dm_db_missing_index_details d
JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle
JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle
GO
-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2008+)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
i.filter_definition,
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildIndexSql(2005)
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false)
SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled,
CAST(NULL AS nvarchar(max)),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END
FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''),
ps.row_count, ps.used_pages,
us.user_seeks, us.user_scans, us.user_lookups, us.user_updates,
fw.forwarded
FROM [msdb].sys.indexes i
JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages
FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps
LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id
OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded
FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw;
GO
-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb)
-- Kilde: IndexCollector.BuildStatisticsSql
-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database)
SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid),
COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr
FROM [msdb].sys.sysindexes si
JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0
JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id
WHERE si.indid > 0 AND si.indid < 255;
GO

View file

@ -0,0 +1,222 @@
-- ===================================================================================================================
-- SQLI-STAT-002: Automatisk statistik er slået fra
-- ===================================================================================================================
-- Regel-id: SQLI-STAT-002
-- Titel: Automatisk statistik er slået fra
-- Alvor: Medium
-- Versioner: Alle (SQL Server 2000 via DATABASEPROPERTYEX).
--
-- Hvad tjekkes, og hvorfor:
-- Om automatisk oprettelse eller opdatering af statistik er slået fra.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. ONLINE, ikke read-only database, hvor is_auto_create_stats_on = 0 eller is_auto_update_stats_on = 0.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data
-- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent];
-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent];
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- DatabaseCollector
-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs
-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(),
-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql,
-- DatabaseCollector.BuildQueryStoreSql(database)
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases)
-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql
-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : AutoStatisticsRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er,
-- Query Store-tilstand og filer.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)
-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2012 – 2016 SP1
-- Kilde: DatabaseCollector.BuildSql2005(2012)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id
WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2)
THEN 1 ELSE 0 END,
CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 – 2008 R2
-- Kilde: DatabaseCollector.BuildSql2005(2005)
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities)
SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc,
d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date,
CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2)
THEN 1 ELSE 0 END,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END,
CAST(NULL AS datetime),
d.is_auto_create_stats_on, d.is_auto_update_stats_on
FROM sys.databases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.BuildSql2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000()
SELECT d.name, d.dbid,
CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)),
CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)),
d.cmptlevel,
CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END,
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int),
CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END,
0,
d.crdate,
0,
0,
CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END,
CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END,
CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END,
CAST(NULL AS datetime),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int),
CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int)
FROM master.dbo.sysdatabases d
LEFT JOIN (
SELECT database_name,
MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full,
MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff,
MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log,
MIN(backup_finish_date) AS first_backup
FROM msdb.dbo.backupset
GROUP BY database_name
) b ON b.database_name = d.name
WHERE d.name <> N'tempdb'
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: DatabaseCollector.Files2005
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005
SELECT database_id, name, type_desc, physical_name,
CAST(size AS bigint) / 128,
CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END,
is_percent_growth,
CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END
FROM sys.master_files
WHERE type_desc IN (N'ROWS', N'LOG')
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti)
-- Kilde: DatabaseCollector.Files2000
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000
SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename,
CAST(size AS bigint) / 128,
CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END,
CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END,
CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END
FROM master.dbo.sysaltfiles
GO
-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)
-- Kilde: Db.UtcOffsetSql
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql
SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())
GO
-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)
-- Kilde: DatabaseCollector.VlfSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql
SELECT d.database_id, COUNT(*)
FROM sys.databases d
CROSS APPLY sys.dm_db_log_info(d.database_id) li
WHERE d.state = 0
GROUP BY d.database_id
GO
-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb)
-- Kilde: DatabaseCollector.BuildQueryStoreSql
-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database)
SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb
FROM [msdb].sys.database_query_store_options
GO

View file

@ -0,0 +1,118 @@
-- ===================================================================================================================
-- SQLI-SVC-001: En SQL Server-tjeneste, der skal starte automatisk, kører ikke
-- ===================================================================================================================
-- Regel-id: SQLI-SVC-001
-- Titel: En SQL Server-tjeneste, der skal starte automatisk, kører ikke
-- Alvor: High
-- Versioner: SQL Server 2008 R2 SP1 og nyere (sys.dm_server_services).
--
-- Hvad tjekkes, og hvorfor:
-- Om en SQL Server-tjeneste (fx SQL Server Agent, Full-text launcher, Launchpad), der er sat til at starte
-- automatisk, ikke kører.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- Ét fund pr. tjeneste i sys.dm_server_services med startup_type_desc = Automatic og status_desc <> Running. Kunne
-- tjenesterne ikke læses: "ikke målbar".
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- Ingen.
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- InstanceConfigurationCollector
-- Fil: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs
-- Medlemmer: InstanceConfigurationCollector.PropertiesSql, InstanceConfigurationCollector.TempdbSql,
-- InstanceConfigurationCollector.ColumnsSql, InstanceConfigurationCollector.BuildServicesSql(true),
-- InstanceConfigurationCollector.BuildServicesSql(false), InstanceConfigurationCollector.MemoryModelSql
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:instance-config)
-- Delt script: SQL Scripts/_collectors/InstanceConfigurationCollector.sql
-- Regelkode: src/SQLInsight.Rules/Instance/ServiceRules.cs : StoppedServiceRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt DBCC TRACESTATUS (læser trace flags), SET-sessionsindstillinger. Intet ændrer
-- data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- InstanceConfigurationCollector: Instansens opsætning til konfigurationssiden og ændringsloggen: egenskaber,
-- tempdb-filer, tjenester og konti, Instant File Initialization, memory model, sp_configure og trace flags.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2005 og nyere (samme forespørgsel som ConfigurationCollector)
-- Kilde: ConfigurationCollector.Sql2005
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005
SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations
GO
-- Variant: SQL Server 2000 (legacy ODBC-sti) (samme forespørgsel som ConfigurationCollector)
-- Kilde: ConfigurationCollector.Sql2000
-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000
SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint)
FROM master.dbo.spt_values v
JOIN master.dbo.sysconfigures c ON c.config = v.number
JOIN master.dbo.syscurconfigs r ON r.config = v.number
WHERE v.type = 'C'
GO
-- Variant: Alle versioner (SQL Server 2000 og nyere) (samme forespørgsel som TraceFlagCollector)
-- Kilde: TraceFlagCollector.Sql
-- Kode: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs : TraceFlagCollector.Sql
DBCC TRACESTATUS(-1) WITH NO_INFOMSGS
GO
-- Variant: Alle versioner
-- Kilde: InstanceConfigurationCollector.PropertiesSql
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.PropertiesSql
SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)),
CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), CAST(SERVERPROPERTY('Collation') AS nvarchar(128)),
CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)), CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260))
GO
-- Variant: SQL Server 2005 og nyere
-- Kilde: InstanceConfigurationCollector.TempdbSql
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.TempdbSql
SELECT name, type_desc, CAST(size AS bigint) / 128, growth, is_percent_growth, max_size
FROM sys.master_files WHERE database_id = 2 ORDER BY file_id
GO
-- Variant: SQL Server 2008 R2 SP1 og nyere
-- Kilde: InstanceConfigurationCollector.ColumnsSql
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.ColumnsSql
SELECT COL_LENGTH('sys.dm_server_services', 'instant_file_initialization_enabled'), COL_LENGTH('sys.dm_os_sys_info', 'sql_memory_model_desc')
GO
-- Variant: SQL Server 2008 R2 SP1 og nyere, builds med kolonnen instant_file_initialization_enabled (se ColumnsSql)
-- Kilde: InstanceConfigurationCollector.BuildServicesSql(med IFI)
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(true)
SELECT servicename, service_account, startup_type_desc, status_desc, instant_file_initialization_enabled FROM sys.dm_server_services
GO
-- Variant: SQL Server 2008 R2 SP1 og nyere, builds uden kolonnen instant_file_initialization_enabled
-- Kilde: InstanceConfigurationCollector.BuildServicesSql(uden IFI)
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(false)
SELECT servicename, service_account, startup_type_desc, status_desc, CAST(NULL AS nvarchar(1)) FROM sys.dm_server_services
GO
-- Variant: Builds med kolonnen sql_memory_model_desc (se ColumnsSql)
-- Kilde: InstanceConfigurationCollector.MemoryModelSql
-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.MemoryModelSql
SELECT sql_memory_model_desc FROM sys.dm_os_sys_info
GO

View file

@ -0,0 +1,91 @@
-- ===================================================================================================================
-- SQLI-SYN-001: Et syntetisk tjek fejler
-- ===================================================================================================================
-- Regel-id: SQLI-SYN-001
-- Titel: Et syntetisk tjek fejler
-- Alvor: High.
-- Versioner: SQL: SQL Server 2005 og nyere (SqlClient, ODBC-fallback for 2005). AX-logon: AX 2009 (kræver Windows PowerShell
-- 2.0-motoren) og AX 2012. Windows-tjenester: Windows Server 2008 R2 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Agenten gør det, en bruger eller applikation ville gøre – forbinder til en port, henter en side, logger på SQL Server
-- og kører en forespørgsel, slår et navn op, logger på Dynamics AX – og det fejler. Er tjekket knyttet til en tjeneste
-- på en Windows-server, og kører tjenesten, siges det direkte: "… svarer ikke, selv om tjenesten kører" – den fejl,
-- en tjenesteovervågning alene ikke ser.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke på den overvågede server):
-- Har de seneste FailuresBeforeFinding kørsler alle fejlet, er det et fund. Titlen siger, om den tilknyttede tjeneste
-- kører, er stoppet (eller i en anden tilstand) eller ikke findes, eller at tjekket fejler N gange i træk. Fejlteksten
-- står aldrig i titlen (den sendes uredigeret til API og notifikationer), kun som måling (følsom). Er alle de
-- resultater, agenten har givet reglen, fejl, siges "mindst N" gange, og "Fejler siden" er "… eller tidligere".
--
-- Grænseværdier (agentens appsettings.json og tjekkets indstillinger):
-- SQLInsight:SyntheticChecks:FailuresBeforeFinding standard 2 (fejl i træk før et fund: ét tabt svar er ingen alarm)
-- Pr. tjek: interval (standard 300 s, 60–86400) og timeout (standard 15 s, AX 60 s, højst 120).
--
-- Rettigheder:
-- TCP, HTTP(S), DNS: ingen ud over netværksadgang fra agentens server. HTTP med Windows-godkendelse: agentens konto.
-- SQL: login'et i tjekket (agentens Windows-konto eller et SQL-login, hvis adgangskode krypteres til agenten som i 1.3)
-- skal kunne logge ind og køre forespørgslen; least-privilege-login'et fra grant-least-privilege.sql er nok til standardforespørgslen.
-- AX-logon: agentens Windows-konto skal være AX-bruger (i firmaet), og .NET Business Connector skal være installeret på
-- agentens server; ellers testes kun AOS-porten (og resultatet siger det).
-- Tjenestens tilstand: WMI Remote Enable på root\cimv2 + læseret på Service Control Manager på tjenestens server.
--
-- Data kommer fra (collector, fil – medlemmer, interval):
-- src/SQLInsight.Servers/Synthetic/SyntheticCheckRunner.cs – SyntheticCheckRunner.RunAsync (TcpAsync, HttpAsync, SqlAsync,
-- DnsAsync, AxAsync), AX-logon: src/SQLInsight.Servers/Synthetic/AxBusinessConnector.cs og ax-logon.ps1. Køres efter
-- tjekkets interval af src/SQLInsight.Agent/Servers/SyntheticCheckService.cs – RunCheckAsync, som også slår den tilknyttede
-- tjenestes tilstand op (CimQueries.SingleServiceQuery).
-- Regelkode: src/SQLInsight.Rules/Servers/SyntheticCheckRules.cs : SyntheticCheckFailingRule
--
-- Read-only: Ja. Ved et SQL-tjek valideres forespørgslen som én skrivebeskyttet SELECT, både når tjekket gemmes og
-- før hver kørsel (src/SQLInsight.Core/CustomRules/ReadOnlyQuery.cs), og den køres i en transaktion, der
-- altid rulles tilbage. Andre typer tjek sender ingen SQL og ændrer intet.
-- Kørsel: Kør i SSMS som tjekkets login. Hver GO er en separat kommando på samme forbindelse; ret [msdb] til
-- tjekkets database, eller udelad USE, hvis tjekket ingen database har.
-- ===================================================================================================================
-- SQL: Ved et SQL-tjek kører agenten præcis dette på den instans, tjekket peger på (ændrer intet: forespørgslen
-- valideres som én skrivebeskyttet SELECT, både når den gemmes og før den køres, og køres i en transaktion, der altid
-- rulles tilbage). Hver GO er en separat kommando på samme forbindelse.
-- -------------------------------------------------------------------------------------------------------------------
-- SyntheticCheckRunner: SQL-tjekket (login, sessionsindstillinger, forespørgslen i en transaktion, der rulles tilbage)
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner (også tjekkets)
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- Variant: Kun når tjekket har en database (vist for msdb)
-- Kilde: SyntheticCheckRunner.UseDatabaseSql
-- Kode: src/SQLInsight.Servers/Synthetic/SyntheticCheckRunner.cs : SyntheticCheckRunner.UseDatabaseSql(database)
USE [msdb];
GO
-- Variant: Alle versioner fra SQL Server 2005: før forespørgslen
-- Kilde: SyntheticCheckRunner.BeginTransactionSql
-- Kode: src/SQLInsight.Servers/Synthetic/SyntheticCheckRunner.cs : SyntheticCheckRunner.BeginTransactionSql
BEGIN TRANSACTION;
GO
-- Variant: Standardforespørgslen, når tjekket ikke har sin egen (en egen forespørgsel køres her i stedet, højst 10.000 rækker læses)
-- Kilde: SyntheticCheckSettings.DefaultQuery
-- Kode: src/SQLInsight.Core/SyntheticChecks/SyntheticCheckDefinitions.cs : SyntheticCheckSettings.DefaultQuery
SELECT 1
GO
-- Variant: Alle versioner fra SQL Server 2005: altid efter forespørgslen, også når den fejler
-- Kilde: SyntheticCheckRunner.RollbackSql
-- Kode: src/SQLInsight.Servers/Synthetic/SyntheticCheckRunner.cs : SyntheticCheckRunner.RollbackSql
IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
GO
-- Ikke-SQL-tjek, efterprøv med PowerShell (skrivebeskyttet; kør på agentens server som agentens konto):
-- TCP: Test-NetConnection -ComputerName aos01 -Port 2712
-- HTTP: Invoke-WebRequest -Uri https://ep.firma.local/ -UseDefaultCredentials -UseBasicParsing | Select-Object StatusCode
-- DNS: Resolve-DnsName aos01.firma.local
-- AX: Test-NetConnection -ComputerName aos01 -Port 2712 (logon: se src/SQLInsight.Servers/Synthetic/ax-logon.ps1)
-- Tjeneste: Get-CimInstance -ComputerName aos01 -Query "SELECT Name, DisplayName, State, StartMode, ExitCode FROM Win32_Service WHERE Name = 'AOS60`$01'"

Some files were not shown because too many files have changed in this diff Show more