315 lines
12 KiB
PowerShell
315 lines
12 KiB
PowerShell
# Modules\SqlService.ps1
|
|
# Microsoft SQL Server Management Service Module (.NET System.Data.SqlClient Live & Fallback)
|
|
|
|
function Build-SqlConnectionString {
|
|
param(
|
|
[string]$Server,
|
|
[string]$AuthType = "Integrated",
|
|
[string]$Username = "",
|
|
[string]$Password = "",
|
|
[string]$DefaultDatabase = "master",
|
|
[int]$Timeout = 10
|
|
)
|
|
|
|
$builder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
|
|
$builder["Data Source"] = $Server
|
|
$builder["Initial Catalog"] = if ([string]::IsNullOrWhiteSpace($DefaultDatabase)) { "master" } else { $DefaultDatabase }
|
|
$builder["Connect Timeout"] = $Timeout
|
|
$builder["Application Name"] = "EnterpriseAdminStudio"
|
|
|
|
if ($AuthType -eq "Integrated" -or [string]::IsNullOrWhiteSpace($Username)) {
|
|
$builder["Integrated Security"] = $true
|
|
} else {
|
|
$builder["Integrated Security"] = $false
|
|
$builder["User ID"] = $Username
|
|
$builder["Password"] = $Password
|
|
}
|
|
|
|
return $builder.ConnectionString
|
|
}
|
|
|
|
function Test-SqlConnection {
|
|
param(
|
|
[string]$Server,
|
|
[string]$AuthType = "Integrated",
|
|
[string]$Username = "",
|
|
[string]$Password = "",
|
|
[int]$Timeout = 5
|
|
)
|
|
|
|
$connStr = Build-SqlConnectionString -Server $Server -AuthType $AuthType -Username $Username -Password $Password -Timeout $Timeout
|
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
|
|
|
|
try {
|
|
$conn.Open()
|
|
$cmd = $conn.CreateCommand()
|
|
$cmd.CommandText = "SELECT @@VERSION AS [Version], SERVERPROPERTY('ProductVersion') AS [ProductVersion], SERVERPROPERTY('Edition') AS [Edition]"
|
|
$reader = $cmd.ExecuteReader()
|
|
if ($reader.Read()) {
|
|
$ver = $reader["ProductVersion"].ToString()
|
|
$edition = $reader["Edition"].ToString()
|
|
return [PSCustomObject]@{
|
|
Success = $true
|
|
Message = "SQL Server 연결 성공 - 버전: $ver ($edition)"
|
|
Version = "$ver ($edition)"
|
|
}
|
|
}
|
|
return [PSCustomObject]@{ Success = $true; Message = "SQL Server 연결 성공" }
|
|
} catch {
|
|
return [PSCustomObject]@{
|
|
Success = $false
|
|
Message = "SQL Server 연결 실패: $($_.Exception.Message)"
|
|
}
|
|
} finally {
|
|
if ($conn.State -eq [System.Data.ConnectionState]::Open) {
|
|
$conn.Close()
|
|
}
|
|
$conn.Dispose()
|
|
}
|
|
}
|
|
|
|
function Get-SqlDatabases {
|
|
param(
|
|
[string]$Filter = "",
|
|
[object]$Config = $null
|
|
)
|
|
|
|
$useLive = if ($Config -and $Config.MsSql) { [bool]$Config.MsSql.UseLive } else { $false }
|
|
|
|
if ($useLive) {
|
|
$server = $Config.MsSql.Server
|
|
$auth = $Config.MsSql.AuthType
|
|
$user = $Config.MsSql.Username
|
|
$pass = $Config.MsSql.Password
|
|
$timeout = if ($Config.MsSql.ConnectionTimeout) { [int]$Config.MsSql.ConnectionTimeout } else { 3 }
|
|
|
|
$connStr = Build-SqlConnectionString -Server $server -AuthType $auth -Username $user -Password $pass -Timeout $timeout
|
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
|
|
|
|
try {
|
|
$conn.Open()
|
|
$cmd = $conn.CreateCommand()
|
|
$cmd.CommandText = @"
|
|
SELECT
|
|
d.name AS DatabaseName,
|
|
d.state_desc AS StateDesc,
|
|
d.recovery_model_desc AS RecoveryModel,
|
|
d.collation_name AS CollationName,
|
|
CAST(ROUND(SUM(CAST(mf.size AS bigint)) * 8.0 / 1024, 1) AS varchar(30)) + ' MB' AS TotalSizeMB,
|
|
ISNULL(CONVERT(varchar(20), MAX(b.backup_finish_date), 120) + ' (' + b.type + ')', '백업 없음') AS LastBackupDate
|
|
FROM sys.databases d
|
|
LEFT JOIN sys.master_files mf ON d.database_id = mf.database_id
|
|
LEFT JOIN msdb.dbo.backupset b ON d.name = b.database_name AND b.type = 'D'
|
|
GROUP BY d.name, d.state_desc, d.recovery_model_desc, d.collation_name, b.type
|
|
ORDER BY d.name;
|
|
"@
|
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
|
$table = New-Object System.Data.DataTable
|
|
$adapter.Fill($table) | Out-Null
|
|
|
|
$list = [System.Collections.Generic.List[PSCustomObject]]::new()
|
|
foreach ($row in $table.Rows) {
|
|
$dbName = $row["DatabaseName"]
|
|
if ([string]::IsNullOrWhiteSpace($Filter) -or $dbName -like "*$Filter*") {
|
|
$list.Add([PSCustomObject]@{
|
|
DatabaseName = $dbName
|
|
StateDesc = $row["StateDesc"]
|
|
TotalSizeMB = $row["TotalSizeMB"]
|
|
FreeSpaceMB = "실시간 계산"
|
|
RecoveryModel = $row["RecoveryModel"]
|
|
CollationName = $row["CollationName"]
|
|
LastBackupDate = $row["LastBackupDate"]
|
|
IsLive = $true
|
|
})
|
|
}
|
|
}
|
|
return $list.ToArray()
|
|
} catch {
|
|
Write-Warning "실제 SQL Server 조회 실패: $($_.Exception.Message) (Mock 데이터로 전환합니다)"
|
|
} finally {
|
|
if ($conn.State -eq [System.Data.ConnectionState]::Open) { $conn.Close() }
|
|
$conn.Dispose()
|
|
}
|
|
}
|
|
|
|
return Get-SqlSampleDatabases -Filter $Filter
|
|
}
|
|
|
|
function Invoke-SqlBackupAction {
|
|
param(
|
|
[string]$DatabaseName,
|
|
[string]$BackupType = "FULL",
|
|
[object]$Config = $null
|
|
)
|
|
|
|
$useLive = if ($Config -and $Config.MsSql) { [bool]$Config.MsSql.UseLive } else { $false }
|
|
|
|
if ($useLive) {
|
|
$server = $Config.MsSql.Server
|
|
$auth = $Config.MsSql.AuthType
|
|
$user = $Config.MsSql.Username
|
|
$pass = $Config.MsSql.Password
|
|
$backupDir = if ($Config.MsSql.BackupDirectory) { $Config.MsSql.BackupDirectory } else { "C:\Backups" }
|
|
|
|
if (-not (Test-Path $backupDir)) {
|
|
New-Item -ItemType Directory -Path $backupDir -Force | Out-Null
|
|
}
|
|
|
|
$timeStamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
|
|
$fileName = "$DatabaseName`_$timeStamp.bak"
|
|
$targetPath = Join-Path $backupDir $fileName
|
|
|
|
$connStr = Build-SqlConnectionString -Server $server -AuthType $auth -Username $user -Password $pass -Timeout 30
|
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
|
|
|
|
try {
|
|
$conn.Open()
|
|
$cmd = $conn.CreateCommand()
|
|
$cmd.CommandTimeout = 300 # 백업은 5분 타임아웃
|
|
$cmd.CommandText = "BACKUP DATABASE [$DatabaseName] TO DISK = N'$targetPath' WITH INIT, STATS = 10, NAME = N'$DatabaseName-Full Backup'"
|
|
$cmd.ExecuteNonQuery() | Out-Null
|
|
|
|
return [PSCustomObject]@{
|
|
Success = $true
|
|
Database = $DatabaseName
|
|
BackupPath = $targetPath
|
|
Message = "실제 SQL Server 데이터베이스 [$DatabaseName] 백업이 완료되었습니다.`n저장 경로: $targetPath"
|
|
}
|
|
} catch {
|
|
return [PSCustomObject]@{
|
|
Success = $false
|
|
Database = $DatabaseName
|
|
Message = "SQL 백업 실패: $($_.Exception.Message)"
|
|
}
|
|
} finally {
|
|
if ($conn.State -eq [System.Data.ConnectionState]::Open) { $conn.Close() }
|
|
$conn.Dispose()
|
|
}
|
|
}
|
|
|
|
return [PSCustomObject]@{
|
|
Success = $true
|
|
Database = $DatabaseName
|
|
BackupPath = "\\backup-nas.corp.local\sqlbackups\$DatabaseName-$((Get-Date).ToString('yyyyMMdd_HHmmss')).bak"
|
|
Message = "[데모 모드] 데이터베이스 [$DatabaseName]의 $BackupType 백업이 가상 완료되었습니다."
|
|
}
|
|
}
|
|
|
|
function Invoke-SqlQuerySnippet {
|
|
param(
|
|
[string]$Query,
|
|
[object]$Config = $null
|
|
)
|
|
|
|
$useLive = if ($Config -and $Config.MsSql) { [bool]$Config.MsSql.UseLive } else { $false }
|
|
|
|
if ($useLive) {
|
|
$server = $Config.MsSql.Server
|
|
$auth = $Config.MsSql.AuthType
|
|
$user = $Config.MsSql.Username
|
|
$pass = $Config.MsSql.Password
|
|
|
|
$connStr = Build-SqlConnectionString -Server $server -AuthType $auth -Username $user -Password $pass -Timeout 15
|
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
|
|
|
|
try {
|
|
$conn.Open()
|
|
$cmd = $conn.CreateCommand()
|
|
$cmd.CommandText = $Query
|
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
|
$table = New-Object System.Data.DataTable
|
|
$adapter.Fill($table) | Out-Null
|
|
|
|
$sw = [System.IO.StringWriter]::new()
|
|
$table | Format-Table -AutoSize | Out-String | ForEach-Object { $sw.WriteLine($_) }
|
|
|
|
return [PSCustomObject]@{
|
|
Success = $true
|
|
RowCount = $table.Rows.Count
|
|
Output = "(성공: $($table.Rows.Count)개 행 반환됨 - $((Get-Date).ToString('HH:mm:ss')))`n$($sw.ToString())"
|
|
}
|
|
} catch {
|
|
return [PSCustomObject]@{
|
|
Success = $false
|
|
RowCount = 0
|
|
Output = "(오류 발생)`n$($_.Exception.Message)"
|
|
}
|
|
} finally {
|
|
if ($conn.State -eq [System.Data.ConnectionState]::Open) { $conn.Close() }
|
|
$conn.Dispose()
|
|
}
|
|
}
|
|
|
|
return [PSCustomObject]@{
|
|
Success = $true
|
|
RowCount = 10
|
|
Output = "(데모 모드 성공: 10개 행 반환됨 - $((Get-Date).ToString('HH:mm:ss')))`n1. tbl_Users (Row: 14,200)`n2. tbl_AuditLogs (Row: 240,119)`n3. tbl_Orders (Row: 82,900)"
|
|
}
|
|
}
|
|
|
|
function Get-SqlSampleDatabases {
|
|
param([string]$Filter = "")
|
|
|
|
$databases = @(
|
|
[PSCustomObject]@{
|
|
DatabaseName = "ERP_PRODUCTION"
|
|
StateDesc = "ONLINE"
|
|
TotalSizeMB = "56,370 MB"
|
|
FreeSpaceMB = "6,410 MB"
|
|
RecoveryModel = "FULL"
|
|
CollationName = "Korean_Wansung_CI_AS"
|
|
LastBackupDate = "2026-10-20 02:00:15 (Full)"
|
|
IsLive = $false
|
|
},
|
|
[PSCustomObject]@{
|
|
DatabaseName = "HR_PORTAL_DB"
|
|
StateDesc = "ONLINE"
|
|
TotalSizeMB = "12,450 MB"
|
|
FreeSpaceMB = "2,120 MB"
|
|
RecoveryModel = "FULL"
|
|
CollationName = "Korean_Wansung_CI_AS"
|
|
LastBackupDate = "2026-10-20 02:30:00 (Full)"
|
|
IsLive = $false
|
|
},
|
|
[PSCustomObject]@{
|
|
DatabaseName = "WAREHOUSE_ANALYTICS"
|
|
StateDesc = "ONLINE"
|
|
TotalSizeMB = "184,000 MB"
|
|
FreeSpaceMB = "18,900 MB"
|
|
RecoveryModel = "SIMPLE"
|
|
CollationName = "Korean_Wansung_CI_AS"
|
|
LastBackupDate = "2026-10-19 23:00:12 (Full)"
|
|
IsLive = $false
|
|
},
|
|
[PSCustomObject]@{
|
|
DatabaseName = "DEV_STAGING_TEST"
|
|
StateDesc = "ONLINE"
|
|
TotalSizeMB = "4,200 MB"
|
|
FreeSpaceMB = "850 MB"
|
|
RecoveryModel = "SIMPLE"
|
|
CollationName = "Korean_Wansung_CI_AS"
|
|
LastBackupDate = "2026-10-18 10:15:00 (Full)"
|
|
IsLive = $false
|
|
},
|
|
[PSCustomObject]@{
|
|
DatabaseName = "ARCHIVE_2025"
|
|
StateDesc = "READ_ONLY"
|
|
TotalSizeMB = "92,500 MB"
|
|
FreeSpaceMB = "45 MB"
|
|
RecoveryModel = "SIMPLE"
|
|
CollationName = "Korean_Wansung_CI_AS"
|
|
LastBackupDate = "2026-01-01 00:00:00 (Full)"
|
|
IsLive = $false
|
|
}
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Filter)) {
|
|
return $databases
|
|
} else {
|
|
return @($databases | Where-Object {
|
|
$_.DatabaseName -like "*$Filter*" -or
|
|
$_.StateDesc -like "*$Filter*"
|
|
})
|
|
}
|
|
}
|