Australian cloud for business software — built and run in Sydney since 2003

NetLeverage
NetLeverage

Mapping dependencies before a cloud migration: flow data, DNS logs and SQL connections

Technical article
Cloud

About this article: AI-assisted writing, reviewed against the cited sources. This is source research, not a lab validation of the procedures. Check the stated versions and your own recovery requirements before making changes.

A dependency map built from one data source will be wrong in a predictable way. Flow data shows which addresses talked. DNS logs show which names were asked for. SQL Server metadata shows which clients, jobs and linked servers rely on a database, including those that were silent during the capture. Use all three, then treat every server pair as unproven until a named owner confirms it or a test proves it.

The commands below were not run in a lab. Names, paths and addresses are examples. Most are read-only, but the capture steps add load or write files on production servers, so each one needs a change record.

Interpretations are editorial; no lab testing was performed.

Why it matters

The cost of a missed dependency depends on when it surfaces. A missed web-to-database path appears at the first smoke test. A missed month-end file drop from an old finance server can appear weeks after you have decommissioned the source (our example, not a measured case).

How it works

Flow records: who talked to whom

A flow record summarises packets that share a key such as addresses, ports and protocol. It does not carry the payload, so it shows that a conversation happened and not what it meant.

A NetFlow version 9 exporter sends long-lived flows on a regular basis, so one session appears as several records. Count distinct server pairs and ports, not records.

On vSphere, a Distributed Switch exports IPFIX, which is NetFlow version 10, to a collector you name by address and port, with active and idle timeouts and a sampling rate. The rate is the number of packets dropped after each collected packet: 0 collects every packet and 1 collects every second packet.

A single small connection once a day may not be sampled at all. Sampled flow data can prove that a path exists. It cannot prove that one does not.

Three more limits apply to flow collection:

If some workloads already run in AWS, VPC Flow Logs do not record traffic to the Amazon DNS server, though traffic to your own DNS server is logged, nor instance metadata, the Amazon Time Sync Service or DHCP.

Host connection tools: which process owns the socket

Migration discovery tools work from inside or against the guest, so they can name the process. That closes the biggest gap in flow data.

Azure Migrate's agentless dependency analysis works by capturing TCP connection data. The appliance polls every five minutes, sends a combined data point every six hours, and Microsoft recommends waiting at least 24 hours for enough data. It is enabled automatically for up to 3,000 servers per appliance on VMware and 1,000 on Hyper-V or physical servers, provided they pass the prerequisite checks. CSV export is limited to the last 30 days. Plan to export before that window rolls over.

On AWS, check your entitlement first. Application Discovery Service closed to new customers on 7 November 2025, and AWS points them to AWS Transform; existing customers can continue. Transform lists discovery and dependency identification, but we did not confirm what it captures. For existing customers, the Discovery Agent runs with root privileges and collects TCP connections and process data, polling about every 15 seconds and sending to AWS every 15 minutes.

Both tools describe TCP. Neither page opened claims UDP coverage, so treat UDP services such as syslog, SNMP and some licence checks as unmapped unless flow data shows them. A connection that opens and closes between two polls can also be missed.

When you cannot install a tool, sample the operating system directly. On Windows, with Get-NetTCPConnection and Export-Csv -Append:

Get-NetTCPConnection -State Established |
  Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, OwningProcess,
    @{n='Process';e={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}},
    @{n='Sampled';e={Get-Date -Format s}} |
  Export-Csv -Path C:\Temp\deps.csv -Append -NoTypeInformation

On Linux, with ss:

ss -H -t -n -p state established >> /var/tmp/deps-$(hostname).txt

Run it as root so process names appear. Run either command from a scheduled task at a short interval, and add a timestamp on Linux. Both are point-in-time samples with the same blind spot for short connections, and neither shows direction. Also record listening ports with Get-NetTCPConnection -State Listen and ss -H -t -l -n -p, so the map can say who called whom.

DNS logs: which names are in use

DNS logs show which names clients ask for, which decides whether a cutover by changing a record will work.

On Windows Server 2016 to 2025, analytic logging is off by default. Microsoft's example is a 5% degradation at 100,000 queries per second and no apparent impact at 50,000 or lower, and it recommends monitoring performance whenever logging is enabled.

To enable it, in Event Viewer open Applications and Services Logs > Microsoft > Windows > DNS-Server, choose View > Show Analytic and Debug Logs, then tick Enable logging in the properties of Analytical. Choosing "Overwrite as needed" gives circular logging and a harmless query error. Choosing "Do not overwrite events (Clear logs manually)" lets you view events in Event Viewer. The default file is %SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-DNSServer%4Analytical.etl.

For a multi-week capture, plan bounded retention, automated export and capacity alerts. A non-overwriting log needs enough space and an export schedule to avoid stopping; base sizing on a representative test day. Circular logging quietly loses the start of your window. A full log stops recording, which you can at least see (our reading; Microsoft does not compare them).

Event 257 is a successful response with fields including Destination, QNAME, QTYPE and RCODE. Event 258 is a failed response, with Reason placed before Destination. For this work, Destination is the address the response was sent to, which is the querying client or a forwarder (our reading of the field).

Get-WinEvent -Path reads an .etl file, and -Oldest is required for that format. We have not tested reading the live file, so stop logging or work from a copy. A starting point:

Get-WinEvent -Path 'D:\Capture\dns-analytic-copy.etl' -Oldest |
  Where-Object { $_.Id -in 257, 258 } |
  ForEach-Object {
    if ($_.Message -match 'Destination=([^;]+);.*QNAME=([^;]+);.*QTYPE=([^;]+);.*RCODE=([^;]+);') {
      [pscustomobject]@{ Event=$_.Id; Client=$Matches[1]; Name=$Matches[2]; Type=$Matches[3]; RCODE=$Matches[4] }
    }
  } |
  Group-Object Event, Client, Name, Type, RCODE -NoElement |
  Sort-Object Count -Descending

The pattern follows the event templates Microsoft publishes and reads fields by name, so it fits both events. Check it against a real event before trusting the totals. RCODE 3 is a name error, meaning the name does not exist.

Three cautions apply when reading DNS logs:

  • Caching hides demand. A TTL sets how long a record may be cached before the source is consulted again. A client holding a cached answer sends no query, so counts understate use. Query counts are not connection counts.
  • Forwarders hide clients. If branch resolvers forward to a central server, the central log shows the forwarder. Capture on the resolvers that clients actually use.
  • Absence is ambiguous. A flow with no matching query may mean a hard-coded address, but it may also mean a cached answer or a hosts-file entry.

Take each flow destination and ask whether any DNS name resolved to it during the capture. Addresses with no observed name are candidates for configuration review. Confirm hard-coded use before concluding that they will not follow a DNS cutover.

Repeated failures for an old name can indicate stale configuration, but also resolver faults or temporary unavailability; correlate the source and response code.

SQL Server: clients, links and scheduled work

Flow data will not show which database, which login, or which job on the database server reaches somewhere else. It may also miss the instance entirely if you filter on 1433, because a named instance by default uses a dynamic port.

Start with who is connected now:

SELECT c.client_net_address,
       c.local_net_address,
       c.local_tcp_port,
       s.host_name,
       s.program_name,
       s.login_name,
       DB_NAME(s.database_id) AS current_database,
       COUNT(*)               AS connections,
       MIN(c.connect_time)    AS earliest_connect,
       MAX(s.last_request_end_time) AS latest_request
FROM sys.dm_exec_connections AS c
JOIN sys.dm_exec_sessions    AS s ON c.session_id = s.session_id
WHERE s.is_user_process = 1
GROUP BY c.client_net_address, c.local_net_address, c.local_tcp_port,
         s.host_name, s.program_name, s.login_name, s.database_id;

sys.dm_exec_connections provides the client address and local port. That view requires VIEW SERVER STATE, or VIEW SERVER PERFORMANCE STATE on SQL Server 2022 and later. Without it, expect a permissions error, not a shorter list.

Prefer client_net_address to host_name, except behind a NAT device or proxy, where it shows that device. Microsoft warns that the client application supplies host_name and can provide inaccurate data. A connection pool may also hold one connection open for days, so a snapshot shows the pool and not each use.

To capture clients over time, not just at one moment, use a login audit. SUCCESSFUL_LOGIN_GROUP is raised by new connections and by connections reused from a pool, so expect volume on busy application servers. Logins to contained databases raise a separate group, SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP, so the specification includes both. Creating an audit needs ALTER ANY SERVER AUDIT or CONTROL SERVER.

USE master;
GO
CREATE SERVER AUDIT DependencyMap
  TO FILE (FILEPATH = 'D:\Audit\', MAXSIZE = 256 MB, MAX_ROLLOVER_FILES = 20)
  WITH (ON_FAILURE = CONTINUE);
GO
CREATE SERVER AUDIT SPECIFICATION DependencyMapSpec
  FOR SERVER AUDIT DependencyMap
  ADD (SUCCESSFUL_LOGIN_GROUP),
  ADD (SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP)
  WITH (STATE = ON);
GO
ALTER SERVER AUDIT DependencyMap WITH (STATE = ON);
GO

Warning: this writes to disk on a production instance. Check free space first. Do not change ON_FAILURE here: SHUTDOWN stops SQL Server if it cannot write the audit, while CONTINUE keeps operations running and audit records are not retained during the failure. The file size and rollover count are examples. They bound the capture, so a busy instance may roll over before your window ends.

Read the result with sys.fn_get_audit_file, whose client_ip and application_name columns apply to SQL Server 2017 and later, and which needs CONTROL SERVER, or VIEW SERVER SECURITY AUDIT from 2022:

SELECT client_ip, application_name, server_principal_name,
       COUNT(*) AS logins, MIN(event_time) AS first_seen, MAX(event_time) AS last_seen
FROM sys.fn_get_audit_file('D:\Audit\*', DEFAULT, DEFAULT)
GROUP BY client_ip, application_name, server_principal_name
ORDER BY logins DESC;

event_time is in UTC. When the capture ends, remove the audit in this order (not lab tested):

USE master;
GO
ALTER SERVER AUDIT SPECIFICATION DependencyMapSpec WITH (STATE = OFF);
GO
DROP SERVER AUDIT SPECIFICATION DependencyMapSpec;
GO
ALTER SERVER AUDIT DependencyMap WITH (STATE = OFF);
GO
DROP SERVER AUDIT DependencyMap;
GO

Dropping an audit that is still on fails, and the drop "removes the metadata for the Audit, but not the audit data". So delete the .sqlaudit files in D:\Audit yourself on the agreed date, because they name logins.

These catalogue queries find what a capture window can miss.

Linked servers:

SELECT name, product, provider, data_source, provider_string, catalog, modify_date
FROM sys.servers WHERE is_linked = 1;

provider_string is NULL unless the caller has ALTER ANY LINKED SERVER, so a low-privilege account can miss connection details. If the default login mapping has been removed, such an account may not see the linked servers at all.

Cross-database and cross-server references, run in each user database:

SELECT OBJECT_SCHEMA_NAME(referencing_id) AS referencing_schema,
       OBJECT_NAME(referencing_id)        AS referencing_object,
       referenced_server_name, referenced_database_name,
       referenced_schema_name, referenced_entity_name
FROM sys.sql_expression_dependencies
WHERE referenced_server_name IS NOT NULL
   OR referenced_database_name IS NOT NULL;

The view reports names that appear in a persisted SQL expression, referenced_server_name only for a valid four-part name, and by default only db_owner members can read it. A name built at run time in dynamic SQL is not a persisted expression, so search job steps and procedure text as well.

SQL Server Agent is often where the surprises are:

SELECT j.name AS job_name, j.enabled, st.step_id, st.subsystem,
       st.database_name, st.command, sc.freq_type, sc.freq_interval,
       sc.freq_recurrence_factor, sc.enabled AS schedule_enabled
FROM msdb.dbo.sysjobs AS j
JOIN msdb.dbo.sysjobsteps AS st ON j.job_id = st.job_id
LEFT JOIN msdb.dbo.sysjobschedules AS js ON j.job_id = js.job_id
LEFT JOIN msdb.dbo.sysschedules AS sc ON js.schedule_id = sc.schedule_id
ORDER BY j.name, st.step_id;

Read command for UNC paths, server names and addresses, particularly in CmdExec and PowerShell job steps. freq_type 16 is monthly and 32 is monthly relative, and freq_recurrence_factor sets the weeks or months between runs for those and for weekly schedules. A monthly job, or a weekly job with a factor above 1, is a dependency that a two-week capture cannot be trusted to show. Treat command output as sensitive, because job steps sometimes contain embedded credentials.

What to choose and why

Microsoft's 24-hour figure is a minimum for useful data, not for finding everything. Set the observation window by the business cycle and not the tool:

Pattern Minimum capture How to cover what falls outside it
Interactive and API traffic One full week including a Monday morning Flow and host sampling
Nightly batch, backups Two weeks Agent job catalogue, schedulers
Month-end processing One full month-end Agent freq_type, finance owner sign-off
Quarterly or annual jobs Usually longer than you can wait Catalogues and owner interviews only

Bring the sources together in one table, one row per dependency:

Column Example Source
Source server and process APP-03, w3wp Host sampling, discovery tool
Destination address and port 10.20.4.17:1433 Flow, host sampling
Name used erpdb.corp.example DNS logs, connection strings
Time pattern Weekdays, 01:00 peak Flow, audit event_time
Owner confirmation Name and date Application owner
Migration action Move together, or re-point at cutover Your decision

All example values are hypothetical. Check Windows Task Scheduler and cron on every server too, because neither appears in network or SQL data until the moment it runs.

Grade each row. These grades are our suggestion and not a standard:

  • Confirmed: seen in at least two independent sources, or named by the owner and seen once.
  • Observed: seen once in one source.
  • Declared: named by the owner or a catalogue query and not seen in traffic. Scheduled jobs often stay here.
  • Excluded: reviewed and recorded as noise, such as monitoring, patching or backup agents. Exclude only after a person has looked.

Then build move groups. Put servers with high-volume or latency-sensitive traffic in the same group. Keep shared services such as domain controllers, DNS and licence servers available on both sides, so they do not pull every application into one giant group.

Prove the map before cutover where you can. Isolate a test copy of a group with no route back to production and start the application. Its connection failures list what the map missed.

Risks and limits

The map proves presence, never absence. Every source has a gap. Say so in the migration plan, and keep the source servers available for a defined period after cutover.

The data is sensitive. DNS logs reveal what users and systems look up. Audit files name logins. Job steps and connection strings can hold credentials. Restrict access, set a deletion date, and check your organisation's privacy and monitoring policies before collecting user-attributable data.

Do not run the discovery tool on a low-privilege account and accept the result. Azure Migrate says Windows dependency data needs administrator credentials. Linux needs a sudo account with NOPASSWD allowed to run ls and netstat, or an account with CAP_DAC_READ_SEARCH and CAP_SYS_PTRACE on /bin/netstat and /bin/ls. Reduced privileges can lose network attributes.

Plan around tool changes. Agent-based analysis is available only in the classic Azure Migrate view, which Microsoft has scheduled for deprecation by the end of 2026 and which cannot onboard new servers. Start new work on the agentless path.

Do not delete old names when the map looks finished. Keep the old DNS records and aliases until logs show that nothing still asks for them over a full business cycle. Removing them early turns an unknown dependency into an outage.

A safe order of work (editorial, assembled from the sources above):

  1. Agree scope and data handling, and record who owns each application.
  2. Run the SQL catalogue queries and export schedulers first. They need no capture window.
  3. Enable flow export and whichever discovery tool you are entitled to. Set DNS and audit capture sizes from a test day.
  4. Hold the capture through at least one full business cycle.
  5. Build the table, grade each row and walk it with application owners.
  6. Build move groups and decide the action per dependency.
  7. Test a group in isolation and record what failed.
  8. Remove all capture settings and delete the collected data, including audit files, on the agreed date.

Sources

Opened 2026-11-10.