202 lines
11 KiB
PowerShell
202 lines
11 KiB
PowerShell
$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')"
|