Sample report

Dependency & risk report: SQLHOST01

Target SQLHOST01 Generated 2026-09-17 Source graph.json Nodes 23 · Edges 41

The one thing to know

An enabled nightly job, OrphanTest_NightlyLoad, silently fails half its work every night, and depends on an external server that no catalog tool can even see it using.

01

Executive summary

SQLHOST01 hosts one live database, OrphanTestDB, with six objects and two SQL Agent jobs. Around it sit six loose files, a batch script, a PowerShell script, a T-SQL script, an Excel workbook, an SSIS package, and an SSRS report, each of which reaches into the database through a hardcoded connection string. Nothing on this box is documented; the map below was reconstructed entirely from what the server and the files disclose.

One thing is already broken. The nightly job OrphanTest_NightlyLoad is enabled and runs every night, but its second step fails every time, and has on both of its two most recent runs. The first step succeeds, so the job looks like it is working to anyone glancing at a job list. It is not: the half that pulls legacy data never completes.

The failing step depends on a server outside this box, ARCHIVE-SQL, through a reference that a normal catalog inventory cannot see, because it is assembled as dynamic SQL at runtime. A second, independent path to that same external server exists inside the SSIS package. Both are single points of failure that no one currently owns: the jobs were last invoked by SQLHOST01\olddev, a login that appears nowhere else.

02

Inventory snapshot

CountTypeDetail
1SQL ServerSQLHOST01, SQL Server 2022 Developer Edition (16.0.1000.6)
2DatabasesOrphanTestDB (local, online) · LegacyDB (referenced on ARCHIVE-SQL)
6SQL objects2 tables, 1 view, 1 scalar function, 2 stored procedures
1Linked serverARCHIVE-SQL (SQLNCLI)
2Agent jobsboth enabled · 6 history records read
3Job steps2 T-SQL, 1 CmdExec
6Files.bat · .ps1 · .sql · .xlsx · .rdl · .dtsx
2Server names in fileslocalhost, ARCHIVE-SQL

03

The map

The full graph holds 23 nodes and 41 edges. Below is the part that carries the risk: the two jobs, what they run, and the two independent paths that leave this box for an external server. Amber marks what is failing or unconfirmed; green marks what runs and works.

critical paths · SQLHOST01
dynamic SQL · line 6 sysjobsteps · step 2 sysjobsteps · step 1 sqlcmd localhost .dtsx line 9 OrphanTest_NightlyLoadagent job · failing step 2 · PullLegacyfails, error 2812 usp_PullLegacyprocedure ARCHIVE-SQL / LegacyDBexternal · unconfirmed step 1 · RunSummarysucceeds usp_CustomerSummaryprocedure · live OrphanTestDBdatabase · live · hub OrphanTest_FileLoadagent job nightly_load.batfile · CmdExec refresh_customers.sqlfile maint.ps1 SalesReport.xlsx CustomerReport.rdl LoadCustomers.dtsx
live & working failing / unconfirmed data hub edge · each carries a citation

04

Risk register

Ranked highest severity first. Severity is computed from signals in the graph, not written by hand, so it is repeatable. Every finding carries its evidence.

High

A nightly job fails every run, and looks like it works

enabled · failing

OrphanTest_NightlyLoad is enabled and scheduled. Step 1 (RunSummary) succeeds; step 2 (PullLegacy) fails with error 2812 on every run in the history. Because step 1 reports success, the job's failure is invisible from a normal job-status glance. The legacy data pull has not completed on any recent run.

Evidence

msdb.dbo.sysjobhistory · run 20260917, step 2 (PullLegacy): "Could not find stored procedure 'dbo.usp_PullLegacy'. [SQLSTATE 42000] (Error 2812). The step failed."

msdb.dbo.sysjobhistory · run 20260917, job outcome: "The job failed. The last step to run was step 2 (PullLegacy)."

Do first. Decide whether step 2 still needs to run. If it does, fix the ARCHIVE-SQL dependency below and add failure alerting to the job so the next silent failure surfaces. If it does not, disable the step so the job stops reporting failure every night.

effort: low
High

A linked-server dependency the catalog cannot see

hidden reach · cross-boundary

dbo.usp_PullLegacy reads from ARCHIVE-SQL.LegacyDB.dbo.ExternalCustomers, an external server, but it builds that query as dynamic SQL at runtime. sys.sql_expression_dependencies records nothing for it, so a catalog-only inventory shows this procedure depending on nothing external. orphanmap caught it by scanning the module text and cites the exact line. This is also the procedure the failing job step above calls.

Evidence

module line · OrphanTestDB.dbo.usp_PullLegacy, line 6: DECLARE @sql NVARCHAR(400) = N'SELECT * FROM [ARCHIVE-SQL].[LegacyDB].[dbo].[ExternalCustomers]';

Confirm ARCHIVE-SQL exists and is reachable from this server, then decide: repoint the linked server, or retire the procedure. Because the reference is dynamic SQL, a schema-comparison or catalog dependency tool will not flag it, document it manually once fixed.

effort: medium
Medium

Two independent paths reach the same external server

cross-boundary · blast radius

Beyond the procedure, the SSIS package LoadCustomers.dtsx also connects to ARCHIVE-SQL / LegacyDB, through its own connection string. If ARCHIVE-SQL is renamed or retired, both the nightly procedure and the SSIS load break, and one of them is already the failing step. Neither dependency is documented anywhere on the box.

Evidence

file, XML · LoadCustomers.dtsx, line 9: DTS:ConnectionString="Data Source=ARCHIVE-SQL;Initial Catalog=LegacyDB;Provider=SQLNCLI11.1;Integrated Security=SSPI;..."

module line · usp_PullLegacy, line 6 (see above)

Treat ARCHIVE-SQL as a shared single point of failure. Both callers need updating together in any migration or rename. Add both to your runbook for that server so a future retirement does not take out the nightly load and the SSIS package by surprise.

effort: low
Medium

Six loose files hardcode this server, invisibly to SQL

blast radius · undocumented

Six files outside the database, a batch script, a PowerShell script, a T-SQL script, an Excel workbook, an SSRS report, and the SSIS package, each carry a connection string naming localhost / OrphanTestDB. None of them are visible from inside SQL Server. Move, rename, or migrate the box and every one of them fails silently, with no error surfaced to the database.

Evidence (one line per file)

nightly_load.bat, line 6: sqlcmd -S localhost -d OrphanTestDB -E -i "...\refresh_customers.sql"

maint.ps1, line 3: $connStr = "Server=localhost;Database=OrphanTestDB;Integrated Security=True;..."

SalesReport.xlsx · xl/connections.xml: Provider=SQLOLEDB.1;Data Source=localhost;Initial Catalog=OrphanTestDB;...

CustomerReport.rdl, line 7: <ConnectString>Data Source=localhost;Initial Catalog=OrphanTestDB</ConnectString>

Before any server rename or migration, inventory these six files. Each hardcodes localhost, so they only work while everything stays on one box. Replace with an alias or a shared config, or at minimum list them as migration blockers.

effort: medium
Low

Both jobs owned by a login that appears nowhere else

orphaned ownership

The Agent job history records both jobs as last invoked by SQLHOST01\olddev. That account does not own any other object the inventory found, the fingerprint of a departed developer. Whoever inherits this box inherits jobs no current login is responsible for.

Evidence

msdb.dbo.sysjobhistory: "The Job was invoked by User SQLHOST01\olddev."

Reassign ownership of both jobs to a current, monitored account and confirm the olddev login can be disabled without breaking a schedule. This is low urgency but it is exactly the loose end that turns into a 2am incident later.

effort: low

05

Dead vs live

What is actually running today versus what is referenced but not confirmed reachable. A read-only inventory of one box can prove a dependency exists; it cannot cross a linked server to prove the far end is alive, so external targets are marked accordingly rather than guessed.

Live and working
  • usp_CustomerSummaryRuns nightly via step 1, succeeds; depends on vCustomerOrders and fnOrderCount, both present.
  • vCustomerOrders → Customers, OrdersCatalog-confirmed object dependencies, all resolve.
  • SalesReport.xlsx, CustomerReport.rdlPoint at the live local database; targets exist.
Broken or unconfirmed
  • Step 2, PullLegacyFails every run (error 2812). Broken today, confirmed from job history.
  • ARCHIVE-SQL / LegacyDBReferenced on two paths; external, so reachability cannot be confirmed from this box. Treat as at-risk.
  • usp_PullLegacyPresent, but its one job caller fails and its external target is unconfirmed.

06

Dependency narratives

The critical chains in plain English. Each sentence maps to a cited edge above.

OrphanTest_NightlyLoad (Agent job)

Runs on a schedule. Step 1 executes usp_CustomerSummary inside OrphanTestDB and succeeds. Step 2 executes usp_PullLegacy, which tries to read ExternalCustomers from the ARCHIVE-SQL linked server and fails. If step 2 is meant to feed the summary, the summary is running on stale data every night.

OrphanTest_FileLoad (Agent job)

A CmdExec step runs nightly_load.bat from a file share. The batch file calls sqlcmd against localhost/OrphanTestDB and runs refresh_customers.sql, which in turn executes usp_CustomerSummary. So the same procedure is driven two ways, once from Agent T-SQL and once from a batch file on a share, and only one of those paths is visible from inside SQL Server.

07

Coverage & confidence

What the collector saw directly, and what it could not, so you know which findings to lean on before you act. These caveats are stamped from the tool's own coverage table, not softened.

Read directly

SQL catalog & Agent jobs. Databases, objects, dependencies, linked servers, job steps, and job history read live from system views. High confidence.

Read directly

File connection strings. .bat, .ps1, .sql, .rdl, .xlsx, .dtsx opened read-only and parsed; each reference cites a line or part. High confidence in this sample.

Partial

Access (.accdb/.mdb). Extraction is exercised on synthetic bytes in the test estate, not yet against a broad set of real files. Treat Access findings as leads to verify.

Not attempted

The far side of a link. A read-only scan of one box cannot cross a linked server or UNC path to confirm the target is alive. External targets are reported as referenced, never as confirmed.

08

Appendix: raw cited edges

For the skeptic. A sample of the graph edges behind the findings above, exactly as the tool emitted them, so any dependency can be verified by hand.

// the linked-server reference the catalog misses
{ "src": "obj:OrphanTestDB.dbo.usp_PullLegacy",
  "dst": "lnk:ARCHIVE-SQL",
  "type": "REFERENCES_LINKED_SERVER",
  "source": { "type": "module_line", "line": 6,
             "text": "...SELECT * FROM [ARCHIVE-SQL].[LegacyDB].[dbo].[ExternalCustomers]..." } }

// the CmdExec step that invokes a batch file on a share
{ "src": "step:OrphanTest_FileLoad#1",
  "dst": "file:\\FILESERVER\\Legacy\\legacyshare\\nightly_load.bat",
  "type": "INVOKES",
  "source": { "type": "jobstep", "object": "msdb.dbo.sysjobsteps",
             "key": "job_id=7601F29E..., step_id=1" } }

// SSIS package connection string, second path to the external server
{ "src": "file:...\\loadcustomers.dtsx",
  "dst": "db:LegacyDB",
  "type": "CONNECTS_TO",
  "source": { "type": "file_xml", "line": 9,
             "text": "Data Source=ARCHIVE-SQL;Initial Catalog=LegacyDB;..." } }

The report is in the works

Get this for your own box

The free tool draws the graph today. This report, ranked risk, dead vs live, a citation on every line, is what's coming next. Leave your email and I'll tell you the moment it's ready.

One upload, one PDF, no call and no consultant. $225 a report, when the upload launches.

One email when the report launches, and that is the only reason I keep your address. I will never sell it, rent it, or share it with anyone, and I will not use it for anything I have not spelled out to you here in writing. No list-swapping, no spam, and every email carries a one-click unsubscribe.

On your data. The free tool runs entirely on your own machine. The only connection it makes is to the SQL Server you point it at: no telemetry, no phone-home, and passwords are never written to its output. When the report launches, an uploaded graph contains hostnames, paths, and object names, never passwords, the tool strips those before anything leaves your box. It will be encrypted, deleted within 7 days, and never used to train anything.

Or get the free graph tool on GitHub →