$ErrorActionPreference = 'Continue'
$ProgressPreference   = 'SilentlyContinue'

function Section($name) { "`n===== $name =====" }

Section 'STEP 1: Service + process view'
Get-Service 'MSSQL$SQLEXPRESS','SQLAgent$SQLEXPRESS','SQLBrowser','SQLWriter' -ErrorAction SilentlyContinue | Format-Table Name,Status,StartType -AutoSize

$svcCim = Get-CimInstance Win32_Service -Filter "Name='MSSQL$SQLEXPRESS'" -ErrorAction SilentlyContinue
$sqlexp_pid = $null
if ($svcCim) { $sqlexp_pid = [int]$svcCim.ProcessId }
"MSSQL`$SQLEXPRESS PID (live from CIM): $sqlexp_pid"

if ($sqlexp_pid) {
    Get-Process -Id $sqlexp_pid -ErrorAction SilentlyContinue | Select-Object Id,StartTime,@{n='WSMB';e={[math]::Round($_.WorkingSet64/1MB,1)}},@{n='VMMB';e={[math]::Round($_.VirtualMemorySize64/1MB,1)}},@{n='PrivateMB';e={[math]::Round($_.PrivateMemorySize64/1MB,1)}},Handles,@{n='Threads';e={$_.Threads.Count}},CPU | Format-List
} else {
    "No live PID for MSSQL`$SQLEXPRESS"
}

Section 'STEP 2: Registry — installed SQL bits'
Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -ErrorAction SilentlyContinue | Select-Object * -ExcludeProperty PS* | Format-List

"--- Per-instance Setup keys ---"
Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\*\Setup' -ErrorAction SilentlyContinue |
    Select-Object PSChildName, Edition, Version, PatchLevel, SqlProgramDir, SqlDataRoot, SQLBinRoot |
    Format-Table -AutoSize -Wrap

Section 'STEP 3: Locate sqlcmd'
$sqlcmd = (Get-Command sqlcmd -ErrorAction SilentlyContinue).Source
if (-not $sqlcmd) {
    foreach ($p in @(
        'C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\sqlcmd.exe',
        'C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\sqlcmd.exe',
        'C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\sqlcmd.exe',
        'C:\Program Files\Microsoft SQL Server\150\Tools\Binn\SQLCMD.EXE',
        'C:\Program Files\Microsoft SQL Server\140\Tools\Binn\SQLCMD.EXE',
        'C:\Program Files\Microsoft SQL Server\130\Tools\Binn\SQLCMD.EXE'
    )) {
        if (Test-Path $p) { $sqlcmd = $p; break }
    }
}
"sqlcmd: $sqlcmd"

Section 'STEP 3a: @@VERSION / SERVERPROPERTY (Windows auth via .\SQLEXPRESS)'
$loginOk = $false
if ($sqlcmd) {
    $verOut = & $sqlcmd -S '.\SQLEXPRESS' -E -d master -h-1 -W -l 5 -Q "SET NOCOUNT ON; SELECT @@VERSION; SELECT CAST(@@SERVERNAME AS varchar(200)) + '|' + CAST(SERVERPROPERTY('InstanceName') AS varchar(200)) + '|' + CAST(SERVERPROPERTY('Edition') AS varchar(200)) + '|' + CAST(SERVERPROPERTY('ProductVersion') AS varchar(200)) + '|' + CAST(SERVERPROPERTY('Collation') AS varchar(200));" 2>&1
    $verOut
    if ($LASTEXITCODE -eq 0 -and ($verOut -join "`n") -notmatch 'Login failed') { $loginOk = $true }
}
"loginOk=$loginOk"

Section 'STEP 3b: ERRORLOG location + tail (always run)'
$el = Get-ChildItem -Path 'C:\Program Files\Microsoft SQL Server\','C:\Program Files (x86)\Microsoft SQL Server\','S:\','D:\','E:\' -Recurse -Filter 'ERRORLOG' -ErrorAction SilentlyContinue |
      Where-Object { $_.FullName -like '*SQLEXPRESS*MSSQL\Log\ERRORLOG' -or $_.FullName -like '*MSSQL*SQLEXPRESS*Log*ERRORLOG*' }
$el | Select-Object FullName,LastWriteTime,Length | Format-Table -AutoSize

# Pick most-recently-written one for the tail
$primary = $el | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($primary) {
    "--- ERRORLOG path: $($primary.FullName) (LastWriteTime: $($primary.LastWriteTime)) ---"
    "--- Tail 400 lines ---"
    Get-Content $primary.FullName -Tail 400 -ErrorAction SilentlyContinue
} else {
    "No SQLEXPRESS ERRORLOG located"
}

Section 'STEP 4: sys.databases + sys.master_files (if loginOk)'
if ($loginOk -and $sqlcmd) {
    & $sqlcmd -S '.\SQLEXPRESS' -E -d master -h-1 -W -l 5 -Q "SET NOCOUNT ON; SELECT CAST(name AS varchar(80)) + '|' + CAST(database_id AS varchar(10)) + '|' + state_desc + '|' + recovery_model_desc + '|' + CAST(create_date AS varchar(30)) + '|' + CAST(compatibility_level AS varchar(10)) FROM sys.databases ORDER BY database_id;" 2>&1
    "--- master_files (user DBs only) ---"
    & $sqlcmd -S '.\SQLEXPRESS' -E -d master -h-1 -W -l 5 -Q "SET NOCOUNT ON; SELECT CAST(DB_NAME(database_id) AS varchar(80)) + '|' + type_desc + '|' + CAST(name AS varchar(80)) + '|' + CAST(physical_name AS varchar(260)) + '|' + CAST(size*8/1024 AS varchar(20)) + 'MB' FROM sys.master_files WHERE database_id > 4 ORDER BY database_id, type;" 2>&1
} else {
    "Skipped (login failed) — will use file-system fallback below"
}

Section 'STEP 4b: File-system DB enumeration (fallback / cross-check)'
# Pull SQLDataRoot from registry for SQLEXPRESS
$sqlexpReg = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\*\Setup' -ErrorAction SilentlyContinue |
             Where-Object { $_.PSChildName -match '^MSSQL\d+\.' } |
             ForEach-Object {
                 $instKey = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($_.PSChildName)"
                 $instName = (Get-ItemProperty -Path $instKey -ErrorAction SilentlyContinue).Name
                 [PSCustomObject]@{ Folder=$_.PSChildName; InstanceName=$instName; SqlDataRoot=$_.SqlDataRoot; SQLBinRoot=$_.SQLBinRoot }
             }
$sqlexpReg | Format-Table -AutoSize -Wrap

$dataDirs = @()
$dataDirs += $sqlexpReg | Where-Object { $_.SqlDataRoot } | ForEach-Object { Join-Path $_.SqlDataRoot 'MSSQL\DATA' }
$dataDirs += 'C:\Program Files\Microsoft SQL Server\MSSQL*.SQLEXPRESS\MSSQL\DATA'
$dataDirs += 'C:\Program Files (x86)\Microsoft SQL Server\MSSQL*.SQLEXPRESS\MSSQL\DATA'
$dataDirs = $dataDirs | Where-Object { $_ } | Select-Object -Unique

"--- DATA dir candidates ---"
$dataDirs

"--- .mdf / .ldf / .ndf under SQLEXPRESS DATA dirs ---"
foreach ($dir in $dataDirs) {
    Get-ChildItem -Path $dir -Include *.mdf,*.ldf,*.ndf -Recurse -ErrorAction SilentlyContinue |
        Select-Object FullName,@{n='SizeMB';e={[math]::Round($_.Length/1MB,1)}},LastWriteTime |
        Format-Table -AutoSize
}

Section 'STEP 5: Active sessions (if loginOk)'
if ($loginOk -and $sqlcmd) {
    & $sqlcmd -S '.\SQLEXPRESS' -E -d master -h-1 -W -l 5 -Q "SET NOCOUNT ON; SELECT CAST(s.session_id AS varchar(10)) + '|' + ISNULL(CAST(s.login_name AS varchar(80)),'-') + '|' + ISNULL(CAST(s.host_name AS varchar(80)),'-') + '|' + ISNULL(CAST(s.program_name AS varchar(120)),'-') + '|' + ISNULL(CAST(s.client_interface_name AS varchar(60)),'-') + '|' + CAST(s.login_time AS varchar(30)) + '|' + CAST(s.last_request_end_time AS varchar(30)) + '|' + ISNULL(CAST(s.status AS varchar(20)),'-') + '|' + ISNULL(CAST(c.client_net_address AS varchar(50)),'-') + '|' + ISNULL(CAST(DB_NAME(s.database_id) AS varchar(80)),'-') FROM sys.dm_exec_sessions s LEFT JOIN sys.dm_exec_connections c ON s.session_id=c.session_id WHERE s.is_user_process=1 ORDER BY s.login_time;" 2>&1
} else {
    "Skipped (login failed) — TCP step below provides remote IP evidence"
}

Section 'STEP 6: TCP — listening port + established connections for SQLEXPRESS PID'
if ($sqlexp_pid) {
    "--- State summary ---"
    Get-NetTCPConnection -OwningProcess $sqlexp_pid -ErrorAction SilentlyContinue | Group-Object State | Select-Object Name,Count | Format-Table -AutoSize

    "--- Listening sockets ---"
    Get-NetTCPConnection -OwningProcess $sqlexp_pid -State Listen -ErrorAction SilentlyContinue |
        Select-Object LocalAddress,LocalPort | Format-Table -AutoSize

    "--- Established connections (remote talking to SQLEXPRESS) ---"
    $est = Get-NetTCPConnection -OwningProcess $sqlexp_pid -State Established -ErrorAction SilentlyContinue
    if ($est) {
        $est | Select-Object RemoteAddress,RemotePort,LocalAddress,LocalPort,CreationTime | Sort-Object RemoteAddress | Format-Table -AutoSize

        "--- Reverse-resolve unique remote IPs ---"
        $est.RemoteAddress | Sort-Object -Unique | ForEach-Object {
            $ip = $_
            $name = $null
            try { $name = [System.Net.Dns]::GetHostEntry($ip).HostName } catch {}
            [PSCustomObject]@{ RemoteIP=$ip; HostName=$name }
        } | Format-Table -AutoSize
    } else {
        "(no established connections to SQLEXPRESS right now)"
    }

    "--- UDP (SQL Browser / instance discovery) for this PID ---"
    Get-NetUDPEndpoint -OwningProcess $sqlexp_pid -ErrorAction SilentlyContinue |
        Select-Object LocalAddress,LocalPort | Format-Table -AutoSize
} else {
    "No PID — skipping TCP enumeration"
}

Section 'STEP 7: Installed apps (filtered)'
$apps = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*, HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue |
    Where-Object { $_.DisplayName } |
    Select-Object DisplayName, Publisher, InstallDate, DisplayVersion |
    Sort-Object DisplayName -Unique

"--- Likely SQL-Express-using apps (AIM, Tri-Tech, MSP360, Cloudberry, Syncro, QuickBooks, Sage, Peachtree, Veeam, Backup, Music, POS, Retail, ScreenConnect) ---"
$apps | Where-Object { $_.DisplayName -match 'AIM|Tri-Tech|TriTech|MSP360|Cloudberry|Syncro|QuickBooks|Sage|Peachtree|Veeam|Backup|Music|POS|Retail|ScreenConnect|ConnectWise|Datto|N-able|Acronis|StorageCraft|ShadowProtect|Mozy|Carbonite|WSUS|RDS|Reporting Services|SSRS|Telerik|OpenAccess|Crystal' } |
    Format-Table -AutoSize -Wrap

"--- Full SQL-related installs ---"
$apps | Where-Object { $_.DisplayName -match 'SQL|Express|Database' } | Format-Table -AutoSize -Wrap

"--- TOTAL apps installed: $($apps.Count) ---"

Section 'STEP 8: Connection-string grep — find apps with SQLEXPRESS in their config'
$paths = @('C:\ProgramData','C:\Program Files\','C:\Program Files (x86)\','C:\inetpub')
$hits = @()
foreach ($root in $paths) {
    if (-not (Test-Path $root)) { continue }
    try {
        $files = Get-ChildItem -Path $root -Recurse -Include *.config,*.ini,*.xml,*.json,*.udl -ErrorAction SilentlyContinue -Force -File
        foreach ($f in $files) {
            try {
                $m = Select-String -Path $f.FullName -Pattern 'SQLEXPRESS' -List -ErrorAction SilentlyContinue
                if ($m) {
                    $hits += [PSCustomObject]@{ Path = $f.FullName; LastWriteTime = $f.LastWriteTime }
                }
            } catch {}
            if ($hits.Count -ge 60) { break }
        }
    } catch {}
    if ($hits.Count -ge 60) { break }
}
"--- Files referencing SQLEXPRESS (first 60) ---"
$hits | Format-Table -AutoSize -Wrap

# For top hits, show the actual matching line
"--- Sample matching lines (first 25 hits) ---"
foreach ($h in ($hits | Select-Object -First 25)) {
    try {
        $line = (Select-String -Path $h.Path -Pattern 'SQLEXPRESS' -ErrorAction SilentlyContinue | Select-Object -First 1).Line
        if ($line) {
            $line = $line.Trim()
            if ($line.Length -gt 240) { $line = $line.Substring(0,240) + '...' }
            "$($h.Path) :: $line"
        }
    } catch {}
}

Section 'STEP 9: Memory cap (read-only — sp_configure show only, NO RECONFIGURE)'
if ($loginOk -and $sqlcmd) {
    & $sqlcmd -S '.\SQLEXPRESS' -E -d master -h-1 -W -l 5 -Q "SET NOCOUNT ON; SELECT CAST(name AS varchar(60)) + '|' + CAST(value AS varchar(20)) + '|' + CAST(value_in_use AS varchar(20)) + '|' + CAST(minimum AS varchar(20)) + '|' + CAST(maximum AS varchar(20)) FROM sys.configurations WHERE name IN ('max server memory (MB)','min server memory (MB)','show advanced options') ORDER BY name;" 2>&1
} else {
    "Skipped (login failed) — sys.configurations needs auth"
}

Section 'DONE'
"Completed at: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')"
