-
PowerShell 자동화 완성편: JSON 설정부터 이벤트 알림까지 엔터프라이즈 급 스크립트[프로그램] 2025. 8. 11. 13:00728x90

PowerShell 고급 자동화 및 운영 관리 시스템
이전 포스팅에서 기본적인 자동화 스크립트를 다뤘다면, 이번에는 엔터프라이즈 환경에서 안정적으로 운영할 수 있는 고급 기능들을 구현해보겠습니다. JSON 설정 파일 기반의 유연한 구성부터 이벤트 알림, 작업 스케줄링까지 완벽한 운영 관리 시스템을 구축해보겠습니다.
📋 JSON 설정 파일 기반 스크립트
설정 파일 구조 (config.json)
모든 설정을 외부 JSON 파일로 분리하여 스크립트 수정 없이 환경을 변경할 수 있습니다.
{ "backup": { "sources": [ { "name": "WebServerLogs", "path": "C:\\inetpub\\logs\\LogFiles", "filePattern": "*.log", "retention": { "backupDays": 7, "archiveDays": 30 } }, { "name": "ApplicationLogs", "path": "C:\\MyApp\\logs", "filePattern": "*.log", "retention": { "backupDays": 14, "archiveDays": 90 } } ], "destination": "\\\\BackupServer\\Backups", "compression": "Optimal", "maxParallelJobs": 3 }, "deployment": { "projects": [ { "name": "WebAPI", "sourcePath": "C:\\Projects\\WebAPI", "excludePatterns": ["*.pdb", "*.tmp", "bin\\Debug", "obj", ".vs", "Tests"], "includeConfig": true, "buildConfiguration": "Release" } ], "outputPath": "C:\\Deploy", "createDeployScript": true }, "notifications": { "email": { "enabled": true, "smtpServer": "smtp.company.com", "smtpPort": 587, "from": "automation@company.com", "to": ["admin@company.com", "ops@company.com"], "subject": "[Automation] {TaskName} - {Status}" }, "slack": { "enabled": true, "webhookUrl": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL", "channel": "#operations", "username": "PowerShell Bot" }, "eventLog": { "enabled": true, "source": "PowerShellAutomation", "logName": "Application" } }, "monitoring": { "healthCheck": { "enabled": true, "services": ["IIS", "MSSQLSERVER", "MyAppService"], "diskSpaceThreshold": 85, "memoryThreshold": 80 } } }고급 백업 스크립트 (AdvancedBackup.ps1)
JSON 설정을 기반으로 하는 완전한 백업 솔루션입니다.
# AdvancedBackup.ps1 param( [string]$ConfigPath = ".\config.json", [switch]$TestMode = $false ) # 설정 파일 로드 함수 function Get-Configuration { param([string]$ConfigFile) if (!(Test-Path $ConfigFile)) { throw "설정 파일을 찾을 수 없습니다: $ConfigFile" } try { $config = Get-Content $ConfigFile -Raw | ConvertFrom-Json return $config } catch { throw "설정 파일 파싱 오류: $($_.Exception.Message)" } } # 이벤트 로그 기록 함수 function Write-EventLogEntry { param( [string]$Message, [ValidateSet("Information", "Warning", "Error")] [string]$EntryType = "Information", [int]$EventId = 1001 ) try { if ($config.notifications.eventLog.enabled) { $source = $config.notifications.eventLog.source $logName = $config.notifications.eventLog.logName # 이벤트 소스가 없으면 생성 if (![System.Diagnostics.EventLog]::SourceExists($source)) { [System.Diagnostics.EventLog]::CreateEventSource($source, $logName) } Write-EventLog -LogName $logName -Source $source -EntryType $EntryType -EventId $EventId -Message $Message } } catch { Write-Warning "이벤트 로그 기록 실패: $($_.Exception.Message)" } } # 이메일 알림 함수 function Send-EmailNotification { param( [string]$Subject, [string]$Body, [ValidateSet("Success", "Warning", "Error")] [string]$Status = "Success" ) try { if ($config.notifications.email.enabled) { $emailConfig = $config.notifications.email $finalSubject = $emailConfig.subject -replace '\{TaskName\}', 'Backup Process' -replace '\{Status\}', $Status $smtpParams = @{ SmtpServer = $emailConfig.smtpServer Port = $emailConfig.smtpPort From = $emailConfig.from To = $emailConfig.to Subject = $finalSubject Body = $Body BodyAsHtml = $true } Send-MailMessage @smtpParams Write-Host "이메일 알림 전송 완료" -ForegroundColor Green } } catch { Write-Warning "이메일 전송 실패: $($_.Exception.Message)" } } # Slack 알림 함수 function Send-SlackNotification { param( [string]$Message, [string]$Color = "good" ) try { if ($config.notifications.slack.enabled) { $slackConfig = $config.notifications.slack $payload = @{ channel = $slackConfig.channel username = $slackConfig.username attachments = @( @{ color = $Color fields = @( @{ title = "백업 작업 결과" value = $Message short = $false } ) footer = "PowerShell Automation" ts = [int][double]::Parse((Get-Date -UFormat %s)) } ) } | ConvertTo-Json -Depth 4 Invoke-RestMethod -Uri $slackConfig.webhookUrl -Method Post -Body $payload -ContentType "application/json" Write-Host "Slack 알림 전송 완료" -ForegroundColor Green } } catch { Write-Warning "Slack 알림 전송 실패: $($_.Exception.Message)" } } # 시스템 상태 확인 함수 function Test-SystemHealth { $healthResults = @() if ($config.monitoring.healthCheck.enabled) { # 서비스 상태 확인 foreach ($serviceName in $config.monitoring.healthCheck.services) { $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($service) { $healthResults += @{ Type = "Service" Name = $serviceName Status = $service.Status IsHealthy = $service.Status -eq "Running" } } } # 디스크 공간 확인 $disks = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } foreach ($disk in $disks) { $freePercent = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2) $threshold = $config.monitoring.healthCheck.diskSpaceThreshold $healthResults += @{ Type = "Disk" Name = $disk.DeviceID FreePercent = $freePercent IsHealthy = $freePercent -gt (100 - $threshold) } } # 메모리 사용률 확인 $memory = Get-WmiObject -Class Win32_OperatingSystem $memoryUsed = [math]::Round((($memory.TotalVisibleMemorySize - $memory.FreePhysicalMemory) / $memory.TotalVisibleMemorySize) * 100, 2) $memoryThreshold = $config.monitoring.healthCheck.memoryThreshold $healthResults += @{ Type = "Memory" Name = "System Memory" UsedPercent = $memoryUsed IsHealthy = $memoryUsed -lt $memoryThreshold } } return $healthResults } # 백업 실행 함수 function Start-BackupProcess { param([object]$BackupConfig) $results = @() $startTime = Get-Date Write-Host "=== 고급 백업 프로세스 시작 ===" -ForegroundColor Cyan Write-Host "시작 시간: $($startTime.ToString('yyyy-MM-dd HH:mm:ss'))" -ForegroundColor Cyan # 시스템 상태 확인 Write-Host "`n시스템 상태 확인 중..." -ForegroundColor Yellow $healthResults = Test-SystemHealth $unhealthyItems = $healthResults | Where-Object { -not $_.IsHealthy } if ($unhealthyItems.Count -gt 0) { $healthWarning = "시스템 상태 경고 발견:`n" foreach ($item in $unhealthyItems) { $healthWarning += "- $($item.Type): $($item.Name) - " if ($item.Type -eq "Service") { $healthWarning += "Status: $($item.Status)`n" } elseif ($item.Type -eq "Disk") { $healthWarning += "Free: $($item.FreePercent)%`n" } elseif ($item.Type -eq "Memory") { $healthWarning += "Used: $($item.UsedPercent)%`n" } } Write-Warning $healthWarning Write-EventLogEntry -Message $healthWarning -EntryType Warning } # 병렬 처리를 위한 작업 큐 $jobs = @() $maxJobs = $BackupConfig.maxParallelJobs foreach ($source in $BackupConfig.sources) { # 실행 중인 작업 수 확인 while (@($jobs | Where-Object { $_.State -eq "Running" }).Count -ge $maxJobs) { Start-Sleep -Seconds 5 $jobs = $jobs | Where-Object { $_.State -ne "Completed" } } # 백업 작업 시작 $job = Start-Job -ScriptBlock { param($sourceConfig, $destinationPath, $compressionLevel, $testMode) $result = @{ SourceName = $sourceConfig.name Success = $false FileCount = 0 CompressedSize = 0 Duration = 0 ErrorMessage = "" } try { $startTime = Get-Date if (Test-Path $sourceConfig.path) { # 백업 대상 파일 검색 $filesToBackup = Get-ChildItem -Path $sourceConfig.path -Filter $sourceConfig.filePattern -Recurse | Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-$sourceConfig.retention.backupDays) } if ($filesToBackup.Count -gt 0) { $backupDate = Get-Date -Format "yyyy-MM-dd_HH-mm" $backupFileName = "$($sourceConfig.name)_$backupDate.zip" $backupPath = Join-Path $destinationPath $backupFileName if (!$testMode) { # 대상 디렉토리 생성 $destDir = Split-Path $backupPath -Parent if (!(Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null } # 압축 실행 $filesToBackup | Compress-Archive -DestinationPath $backupPath -CompressionLevel $compressionLevel -Force $result.CompressedSize = (Get-Item $backupPath).Length } $result.FileCount = $filesToBackup.Count $result.Success = $true } else { $result.ErrorMessage = "백업할 파일이 없습니다." } } else { $result.ErrorMessage = "소스 경로를 찾을 수 없습니다: $($sourceConfig.path)" } $result.Duration = (Get-Date) - $startTime } catch { $result.ErrorMessage = $_.Exception.Message } return $result } -ArgumentList $source, $BackupConfig.destination, $BackupConfig.compression, $TestMode $jobs += $job Write-Host "백업 작업 시작: $($source.name)" -ForegroundColor Green } # 모든 작업 완료 대기 Write-Host "`n모든 백업 작업 완료 대기 중..." -ForegroundColor Yellow $jobs | Wait-Job | Out-Null # 결과 수집 foreach ($job in $jobs) { $result = Receive-Job $job $results += $result Remove-Job $job } # 결과 보고서 생성 $endTime = Get-Date $totalDuration = $endTime - $startTime $successCount = ($results | Where-Object { $_.Success }).Count $totalFiles = ($results | Measure-Object -Property FileCount -Sum).Sum $totalSize = ($results | Measure-Object -Property CompressedSize -Sum).Sum $report = @" === 백업 작업 완료 보고서 === 시작 시간: $($startTime.ToString('yyyy-MM-dd HH:mm:ss')) 완료 시간: $($endTime.ToString('yyyy-MM-dd HH:mm:ss')) 총 소요 시간: $($totalDuration.ToString('hh\:mm\:ss')) 성공한 백업: $successCount / $($results.Count) 총 파일 수: $totalFiles 총 압축 크기: $([math]::Round($totalSize / 1MB, 2)) MB === 상세 결과 === "@ foreach ($result in $results) { $report += "`n[$($result.SourceName)] " if ($result.Success) { $report += "성공 - $($result.FileCount)개 파일, $([math]::Round($result.CompressedSize / 1MB, 2)) MB" } else { $report += "실패 - $($result.ErrorMessage)" } $report += " (소요시간: $($result.Duration.ToString('mm\:ss')))" } Write-Host $report -ForegroundColor Cyan # 로그 및 알림 $logMessage = "백업 작업 완료 - 성공: $successCount/$($results.Count), 파일: $totalFiles개, 크기: $([math]::Round($totalSize / 1MB, 2))MB" Write-EventLogEntry -Message $logMessage if ($results | Where-Object { -not $_.Success }) { Send-SlackNotification -Message $report -Color "warning" Send-EmailNotification -Subject "백업 작업 경고" -Body $report.Replace("`n", "<br>") -Status "Warning" } else { Send-SlackNotification -Message $report -Color "good" Send-EmailNotification -Subject "백업 작업 성공" -Body $report.Replace("`n", "<br>") -Status "Success" } return $results } # 메인 실행 부분 try { Write-Host "설정 파일 로드 중: $ConfigPath" -ForegroundColor Cyan $config = Get-Configuration -ConfigFile $ConfigPath if ($TestMode) { Write-Host "*** 테스트 모드로 실행 (실제 파일 생성 안 함) ***" -ForegroundColor Yellow } $results = Start-BackupProcess -BackupConfig $config.backup Write-Host "`n=== 작업 완료 ===" -ForegroundColor Green } catch { $errorMessage = "백업 프로세스 오류: $($_.Exception.Message)" Write-Host $errorMessage -ForegroundColor Red Write-EventLogEntry -Message $errorMessage -EntryType Error Send-SlackNotification -Message $errorMessage -Color "danger" Send-EmailNotification -Subject "백업 작업 실패" -Body $errorMessage -Status "Error" }📅 작업 스케줄러 연동
스케줄러 등록 스크립트 (RegisterScheduledTask.ps1)
# RegisterScheduledTask.ps1 param( [Parameter(Mandatory=$true)] [string]$TaskName, [Parameter(Mandatory=$true)] [string]$ScriptPath, [string]$Schedule = "Daily", [string]$StartTime = "02:00", [string]$UserAccount = "SYSTEM" ) function Register-BackupScheduledTask { param( [string]$Name, [string]$Script, [string]$ScheduleType, [string]$Time, [string]$User ) try { # 기존 작업이 있으면 삭제 $existingTask = Get-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue if ($existingTask) { Unregister-ScheduledTask -TaskName $Name -Confirm:$false Write-Host "기존 작업 삭제됨: $Name" -ForegroundColor Yellow } # 작업 액션 정의 $action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$Script`"" # 트리거 정의 switch ($ScheduleType.ToLower()) { "daily" { $trigger = New-ScheduledTaskTrigger -Daily -At $Time } "weekly" { $trigger = New-ScheduledTaskTrigger -Weekly -WeeksInterval 1 -DaysOfWeek Monday -At $Time } "monthly" { $trigger = New-ScheduledTaskTrigger -Weekly -WeeksInterval 4 -DaysOfWeek Monday -At $Time } default { throw "지원되지 않는 스케줄 타입: $ScheduleType" } } # 작업 설정 $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable # 보안 주체 설정 if ($User -eq "SYSTEM") { $principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest } else { $principal = New-ScheduledTaskPrincipal -UserID $User -LogonType Password -RunLevel Highest } # 작업 등록 Register-ScheduledTask -TaskName $Name -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description "PowerShell 자동화 백업 작업" Write-Host "예약된 작업이 성공적으로 등록되었습니다." -ForegroundColor Green Write-Host "작업 이름: $Name" -ForegroundColor Cyan Write-Host "실행 스크립트: $Script" -ForegroundColor Cyan Write-Host "스케줄: $ScheduleType at $Time" -ForegroundColor Cyan # 작업 정보 확인 $task = Get-ScheduledTask -TaskName $Name Write-Host "작업 상태: $($task.State)" -ForegroundColor Green } catch { Write-Host "작업 등록 실패: $($_.Exception.Message)" -ForegroundColor Red throw } } # 배치 등록 스크립트 $tasks = @( @{ Name = "DailyLogBackup" Script = "C:\Scripts\AdvancedBackup.ps1" Schedule = "Daily" Time = "02:00" }, @{ Name = "WeeklyFullBackup" Script = "C:\Scripts\AdvancedBackup.ps1" Schedule = "Weekly" Time = "01:00" } ) Write-Host "=== PowerShell 자동화 작업 스케줄러 등록 ===" -ForegroundColor Cyan foreach ($task in $tasks) { Write-Host "`n등록 중: $($task.Name)" -ForegroundColor Yellow Register-BackupScheduledTask -Name $task.Name -Script $task.Script -ScheduleType $task.Schedule -Time $task.Time -User $UserAccount } Write-Host "`n=== 등록된 작업 목록 ===" -ForegroundColor Green Get-ScheduledTask | Where-Object { $_.TaskName -like "*Backup*" } | Select-Object TaskName, State, @{Name="NextRun";Expression={$_.Triggers[0].StartBoundary}} | Format-Table -AutoSize🔄 에러 복구 및 재시도 로직
복원력 있는 백업 스크립트 (ResilientBackup.ps1)
# ResilientBackup.ps1 function Invoke-WithRetry { param( [ScriptBlock]$ScriptBlock, [int]$MaxRetries = 3, [int]$DelaySeconds = 30, [string]$OperationName = "Operation" ) $attempt = 1 do { try { Write-Host "[$OperationName] 시도 $attempt/$MaxRetries" -ForegroundColor Yellow $result = & $ScriptBlock Write-Host "[$OperationName] 성공" -ForegroundColor Green return $result } catch { Write-Warning "[$OperationName] 시도 $attempt 실패: $($_.Exception.Message)" if ($attempt -eq $MaxRetries) { Write-Host "[$OperationName] 모든 재시도 실패" -ForegroundColor Red throw "최대 재시도 횟수 초과: $($_.Exception.Message)" } Write-Host "[$OperationName] $DelaySeconds초 후 재시도..." -ForegroundColor Yellow Start-Sleep -Seconds $DelaySeconds $attempt++ } } while ($attempt -le $MaxRetries) } function Start-ResilientBackup { param([string]$ConfigPath = ".\config.json") try { # 설정 로드 (재시도 로직 적용) $config = Invoke-WithRetry -ScriptBlock { Get-Configuration -ConfigFile $ConfigPath } -OperationName "설정 파일 로드" -MaxRetries 2 foreach ($source in $config.backup.sources) { # 각 소스별로 독립적인 재시도 처리 Invoke-WithRetry -ScriptBlock { # 소스 경로 접근 가능성 확인 if (!(Test-Path $source.path)) { throw "소스 경로에 접근할 수 없습니다: $($source.path)" } # 대상 경로 접근 가능성 확인 $testPath = Join-Path $config.backup.destination "test_$(Get-Random).tmp" try { New-Item -Path $testPath -ItemType File -Force | Out-Null Remove-Item $testPath -Force } catch { throw "대상 경로에 쓰기 권한이 없습니다: $($config.backup.destination)" } # 실제 백업 수행 $files = Get-ChildItem -Path $source.path -Filter $source.filePattern -Recurse | Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-$source.retention.backupDays) } if ($files.Count -eq 0) { Write-Warning "[$($source.name)] 백업할 파일이 없습니다." return } $backupDate = Get-Date -Format "yyyy-MM-dd_HH-mm" $backupFileName = "$($source.name)_$backupDate.zip" $backupPath = Join-Path $config.backup.destination $backupFileName # 압축 실행 (가장 실패하기 쉬운 부분) $files | Compress-Archive -DestinationPath $backupPath -CompressionLevel $config.backup.compression -Force # 압축 파일 검증 if (!(Test-Path $backupPath)) { throw "백업 파일이 생성되지 않았습니다: $backupPath" } $backupSize = (Get-Item $backupPath).Length if ($backupSize -eq 0) { throw "백업 파일이 비어있습니다: $backupPath" } Write-Host "[$($source.name)] 백업 성공: $($files.Count)개 파일, $([math]::Round($backupSize / 1MB, 2)) MB" -ForegroundColor Green } -OperationName $source.name -MaxRetries 3 -DelaySeconds 60 } } catch { $errorMsg = "복원력 있는 백업 프로세스 실패: $($_.Exception.Message)" Write-Host $errorMsg -ForegroundColor Red # 중요한 실패는 즉시 알림 if ($config.notifications.email.enabled) { Send-EmailNotification -Subject "긴급: 백업 시스템 실패" -Body $errorMsg -Status "Error" } if ($config.notifications.slack.enabled) { Send-SlackNotification -Message ":rotating_light: **긴급 알림**`n$errorMsg" -Color "danger" } throw } } # 자동 복구 시도 function Start-AutoRecovery { param([string]$ConfigPath = ".\config.json") Write-Host "=== 자동 복구 프로세스 시작 ===" -ForegroundColor Cyan # 디스크 공간 정리 Write-Host "임시 파일 정리 중..." -ForegroundColor Yellow try { Get-ChildItem -Path $env:TEMP -Recurse -File | Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-7) } | Remove-Item -Force -ErrorAction SilentlyContinue Write-Host "임시 파일 정리 완료" -ForegroundColor Green } catch { Write-Warning "임시 파일 정리 중 오류: $($_.Exception.Message)" } # 네트워크 연결 확인 Write-Host "네트워크 연결 확인 중..." -ForegroundColor Yellow $config = Get-Content $ConfigPath -Raw | ConvertFrom-Json $backupServer = ($config.backup.destination -split '\\')[2] if ($backupServer) { try { $ping = Test-Connection -ComputerName $backupServer -Count 2 -Quiet if ($ping) { Write-Host "백업 서버 연결 정상: $backupServer" -ForegroundColor Green } else { Write-Warning "백업 서버 연결 실패: $backupServer" } } catch { Write-Warning "네트워크 테스트 중 오류: $($_.Exception.Message)" } } # 서비스 상태 확인 및 재시작 Write-Host "서비스 상태 확인 중..." -ForegroundColor Yellow if ($config.monitoring.healthCheck.enabled) { foreach ($serviceName in $config.monitoring.healthCheck.services) { $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($service -and $service.Status -ne "Running") { try { Write-Host "서비스 재시작 시도: $serviceName" -ForegroundColor Yellow Start-Service -Name $serviceName Write-Host "서비스 재시작 성공: $serviceName" -ForegroundColor Green } catch { Write-Warning "서비스 재시작 실패: $serviceName - $($_.Exception.Message)" } } } } } # 메인 실행 함수 Start-ResilientBackup -ConfigPath ".\config.json"📊 모니터링 및 리포팅 시스템
통합 모니터링 스크립트 (SystemMonitor.ps1)
# SystemMonitor.ps1 param( [string]$ConfigPath = ".\config.json", [switch]$GenerateReport = $false, [string]$ReportPath = ".\Reports" ) function Get-SystemMetrics { param([object]$Config) $metrics = @{ Timestamp = Get-Date System = @{} Services = @() Storage = @() BackupStatus = @{} } # 시스템 정보 수집 $os = Get-WmiObject -Class Win32_OperatingSystem $computer = Get-WmiObject -Class Win32_ComputerSystem $processor = Get-WmiObject -Class Win32_Processor $metrics.System = @{ ComputerName = $computer.Name OS = $os.Caption TotalMemoryGB = [math]::Round($computer.TotalPhysicalMemory / 1GB, 2) FreeMemoryGB = [math]::Round($os.FreePhysicalMemory / 1MB / 1024, 2) MemoryUsagePercent = [math]::Round((($computer.TotalPhysicalMemory - ($os.FreePhysicalMemory * 1KB)) / $computer.TotalPhysicalMemory) * 100, 2) CPUUsagePercent = (Get-Counter "\Processor(_Total)\% Processor Time").CounterSamples.CookedValue Uptime = (Get-Date) - $os.ConvertToDateTime($os.LastBootUpTime) } # 서비스 상태 수집 if ($Config.monitoring.healthCheck.enabled) { foreach ($serviceName in $Config.monitoring.healthCheck.services) { $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($service) { $metrics.Services += @{ Name = $service.Name DisplayName = $service.DisplayName Status = $service.Status.ToString() StartType = $service.StartType.ToString() } } } } # 저장소 정보 수집 $disks = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } foreach ($disk in $disks) { $metrics.Storage += @{ Drive = $disk.DeviceID TotalGB = [math]::Round($disk.Size / 1GB, 2) FreeGB = [math]::Round($disk.FreeSpace / 1GB, 2) UsedGB = [math]::Round(($disk.Size - $disk.FreeSpace) / 1GB, 2) FreePercent = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2) IsHealthy = (($disk.FreeSpace / $disk.Size) * 100) -gt (100 - $Config.monitoring.healthCheck.diskSpaceThreshold) } } # 백업 상태 확인 if (Test-Path $Config.backup.destination) { $backupFiles = Get-ChildItem -Path $Config.backup.destination -Filter "*.zip" | Sort-Object CreationTime -Descending $latestBackup = $backupFiles | Select-Object -First 1 $metrics.BackupStatus = @{ LatestBackupDate = if ($latestBackup) { $latestBackup.CreationTime } else { $null } TotalBackupFiles = $backupFiles.Count TotalBackupSizeGB = [math]::Round(($backupFiles | Measure-Object -Property Length -Sum).Sum / 1GB, 2) BackupHealth = if ($latestBackup -and $latestBackup.CreationTime -gt (Get-Date).AddDays(-1)) { "Healthy" } else { "Warning" } } } return $metrics } function New-HTMLReport { param( [object]$Metrics, [string]$OutputPath ) $html = @" <!DOCTYPE html> <html> <head> <title>시스템 모니터링 보고서</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .header { background-color: #2c3e50; color: white; padding: 20px; border-radius: 5px; } .section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; } .healthy { color: green; font-weight: bold; } .warning { color: orange; font-weight: bold; } .critical { color: red; font-weight: bold; } table { width: 100%; border-collapse: collapse; margin: 10px 0; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } .metric-value { font-size: 1.2em; font-weight: bold; } </style> </head> <body> <div class="header"> <h1>시스템 모니터링 보고서</h1> <p>생성일시: $($Metrics.Timestamp.ToString('yyyy-MM-dd HH:mm:ss'))</p> <p>시스템: $($Metrics.System.ComputerName)</p> </div> <div class="section"> <h2>시스템 개요</h2> <table> <tr><th>항목</th><th>값</th><th>상태</th></tr> <tr> <td>메모리 사용률</td> <td class="metric-value">$($Metrics.System.MemoryUsagePercent)%</td> <td class="$(if($Metrics.System.MemoryUsagePercent -lt 80){'healthy'}elseif($Metrics.System.MemoryUsagePercent -lt 90){'warning'}else{'critical'})"> $(if($Metrics.System.MemoryUsagePercent -lt 80){'정상'}elseif($Metrics.System.MemoryUsagePercent -lt 90){'주의'}else{'위험'}) </td> </tr> <tr> <td>시스템 가동시간</td> <td class="metric-value">$($Metrics.System.Uptime.Days)일 $($Metrics.System.Uptime.Hours)시간</td> <td class="healthy">정상</td> </tr> <tr> <td>총 메모리</td> <td class="metric-value">$($Metrics.System.TotalMemoryGB) GB</td> <td class="healthy">-</td> </tr> </table> </div> <div class="section"> <h2>서비스 상태</h2> <table> <tr><th>서비스명</th><th>표시명</th><th>상태</th><th>시작 유형</th></tr> "@ foreach ($service in $Metrics.Services) { $statusClass = if ($service.Status -eq "Running") { "healthy" } else { "critical" } $html += @" <tr> <td>$($service.Name)</td> <td>$($service.DisplayName)</td> <td class="$statusClass">$($service.Status)</td> <td>$($service.StartType)</td> </tr> "@ } $html += @" </table> </div> <div class="section"> <h2>저장소 상태</h2> <table> <tr><th>드라이브</th><th>총 용량</th><th>사용량</th><th>여유 공간</th><th>여유율</th><th>상태</th></tr> "@ foreach ($storage in $Metrics.Storage) { $statusClass = if ($storage.IsHealthy) { "healthy" } else { "warning" } $statusText = if ($storage.IsHealthy) { "정상" } else { "주의" } $html += @" <tr> <td>$($storage.Drive)</td> <td>$($storage.TotalGB) GB</td> <td>$($storage.UsedGB) GB</td> <td>$($storage.FreeGB) GB</td> <td>$($storage.FreePercent)%</td> <td class="$statusClass">$statusText</td> </tr> "@ } $html += @" </table> </div> <div class="section"> <h2>백업 상태</h2> <table> <tr><th>항목</th><th>값</th><th>상태</th></tr> <tr> <td>최근 백업 일시</td> <td class="metric-value">$(if($Metrics.BackupStatus.LatestBackupDate){$Metrics.BackupStatus.LatestBackupDate.ToString('yyyy-MM-dd HH:mm:ss')}else{'없음'})</td> <td class="$(if($Metrics.BackupStatus.BackupHealth -eq 'Healthy'){'healthy'}else{'warning'})">$($Metrics.BackupStatus.BackupHealth)</td> </tr> <tr> <td>총 백업 파일 수</td> <td class="metric-value">$($Metrics.BackupStatus.TotalBackupFiles)</td> <td class="healthy">-</td> </tr> <tr> <td>총 백업 크기</td> <td class="metric-value">$($Metrics.BackupStatus.TotalBackupSizeGB) GB</td> <td class="healthy">-</td> </tr> </table> </div> <div class="section"> <h2>권장 사항</h2> <ul> "@ # 권장 사항 생성 $recommendations = @() if ($Metrics.System.MemoryUsagePercent -gt 85) { $recommendations += "메모리 사용률이 높습니다. 불필요한 프로세스를 확인하세요." } $unhealthyStorage = $Metrics.Storage | Where-Object { -not $_.IsHealthy } if ($unhealthyStorage) { foreach ($disk in $unhealthyStorage) { $recommendations += "$($disk.Drive) 드라이브의 여유 공간이 부족합니다. ($($disk.FreePercent)%)" } } $stoppedServices = $Metrics.Services | Where-Object { $_.Status -ne "Running" } if ($stoppedServices) { foreach ($service in $stoppedServices) { $recommendations += "$($service.DisplayName) 서비스가 중지되어 있습니다." } } if ($Metrics.BackupStatus.BackupHealth -ne "Healthy") { $recommendations += "백업이 정상적으로 수행되지 않고 있습니다. 백업 시스템을 점검하세요." } if ($recommendations.Count -eq 0) { $recommendations += "현재 시스템 상태가 양호합니다." } foreach ($recommendation in $recommendations) { $html += " <li>$recommendation</li>`n" } $html += @" </ul> </div> <div class="section"> <small>이 보고서는 PowerShell 자동화 시스템에 의해 자동 생성되었습니다.</small> </div> </body> </html> "@ # 보고서 파일 저장 if (!(Test-Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null } $reportFileName = "SystemReport_$(Get-Date -Format 'yyyy-MM-dd_HH-mm').html" $reportFilePath = Join-Path $OutputPath $reportFileName $html | Out-File -FilePath $reportFilePath -Encoding UTF8 Write-Host "HTML 보고서 생성 완료: $reportFilePath" -ForegroundColor Green return $reportFilePath } function Send-MonitoringAlert { param( [object]$Metrics, [object]$Config ) $alerts = @() # 메모리 사용률 체크 if ($Metrics.System.MemoryUsagePercent -gt $Config.monitoring.healthCheck.memoryThreshold) { $alerts += "⚠️ 메모리 사용률 경고: $($Metrics.System.MemoryUsagePercent)%" } # 디스크 공간 체크 $unhealthyDisks = $Metrics.Storage | Where-Object { -not $_.IsHealthy } foreach ($disk in $unhealthyDisks) { $alerts += "💾 디스크 공간 부족: $($disk.Drive) - $($disk.FreePercent)% 여유" } # 서비스 상태 체크 $stoppedServices = $Metrics.Services | Where-Object { $_.Status -ne "Running" } foreach ($service in $stoppedServices) { $alerts += "🚫 서비스 중지: $($service.DisplayName)" } # 백업 상태 체크 if ($Metrics.BackupStatus.BackupHealth -ne "Healthy") { $alerts += "📦 백업 상태 이상: 최근 백업이 정상적으로 수행되지 않음" } # 알림 전송 if ($alerts.Count -gt 0) { $alertMessage = "🚨 **시스템 모니터링 알림**`n`n" + ($alerts -join "`n") if ($Config.notifications.slack.enabled) { Send-SlackNotification -Message $alertMessage -Color "warning" } if ($Config.notifications.email.enabled) { $emailBody = $alertMessage -replace "`n", "<br>" Send-EmailNotification -Subject "시스템 모니터링 알림" -Body $emailBody -Status "Warning" } Write-EventLogEntry -Message ($alerts -join "; ") -EntryType Warning } } # 메인 실행 부분 try { Write-Host "=== 시스템 모니터링 시작 ===" -ForegroundColor Cyan $config = Get-Configuration -ConfigFile $ConfigPath $metrics = Get-SystemMetrics -Config $config # 콘솔 출력 Write-Host "`n=== 시스템 상태 요약 ===" -ForegroundColor Green Write-Host "시스템: $($metrics.System.ComputerName)" Write-Host "메모리 사용률: $($metrics.System.MemoryUsagePercent)%" Write-Host "가동시간: $($metrics.System.Uptime.Days)일 $($metrics.System.Uptime.Hours)시간" Write-Host "`n서비스 상태:" -ForegroundColor Yellow foreach ($service in $metrics.Services) { $color = if ($service.Status -eq "Running") { "Green" } else { "Red" } Write-Host " $($service.DisplayName): $($service.Status)" -ForegroundColor $color } Write-Host "`n저장소 상태:" -ForegroundColor Yellow foreach ($storage in $metrics.Storage) { $color = if ($storage.IsHealthy) { "Green" } else { "Yellow" } Write-Host " $($storage.Drive) $($storage.FreePercent)% 여유 ($($storage.FreeGB)GB)" -ForegroundColor $color } # HTML 보고서 생성 if ($GenerateReport) { $reportPath = New-HTMLReport -Metrics $metrics -OutputPath $ReportPath Write-Host "`nHTML 보고서 생성됨: $reportPath" -ForegroundColor Cyan } # 알림 체크 및 전송 Send-MonitoringAlert -Metrics $metrics -Config $config Write-Host "`n=== 모니터링 완료 ===" -ForegroundColor Green } catch { $errorMessage = "시스템 모니터링 오류: $($_.Exception.Message)" Write-Host $errorMessage -ForegroundColor Red Write-EventLogEntry -Message $errorMessage -EntryType Error }🎯 통합 관리 스크립트
마스터 제어 스크립트 (MasterController.ps1)
# MasterController.ps1 - 모든 자동화 작업을 통합 관리 param( [Parameter(Mandatory=$true)] [ValidateSet("Backup", "Monitor", "Deploy", "Health", "All")] [string]$Operation, [string]$ConfigPath = ".\config.json", [switch]$TestMode = $false, [switch]$GenerateReport = $false ) function Start-MasterController { param( [string]$Op, [string]$Config, [bool]$Test, [bool]$Report ) $startTime = Get-Date Write-Host "=== PowerShell 자동화 마스터 컨트롤러 ===" -ForegroundColor Magenta Write-Host "시작 시간: $($startTime.ToString('yyyy-MM-dd HH:mm:ss'))" -ForegroundColor Cyan Write-Host "작업: $Op" -ForegroundColor Cyan Write-Host "테스트 모드: $Test" -ForegroundColor Cyan $results = @{ Operation = $Op StartTime = $startTime Success = $false Results = @{} Errors = @() } try { switch ($Op) { "Backup" { Write-Host "`n🔄 백업 작업 실행 중..." -ForegroundColor Yellow if ($Test) { & ".\AdvancedBackup.ps1" -ConfigPath $Config -TestMode } else { & ".\AdvancedBackup.ps1" -ConfigPath $Config } $results.Results.Backup = "완료" } "Monitor" { Write-Host "`n📊 모니터링 작업 실행 중..." -ForegroundColor Yellow if ($Report) { & ".\SystemMonitor.ps1" -ConfigPath $Config -GenerateReport -ReportPath ".\Reports" } else { & ".\SystemMonitor.ps1" -ConfigPath $Config } $results.Results.Monitor = "완료" } "Deploy" { Write-Host "`n📦 배포 패키지 생성 중..." -ForegroundColor Yellow $config = Get-Content $Config -Raw | ConvertFrom-Json foreach ($project in $config.deployment.projects) { & ".\WebDeployPackage.ps1" -ProjectPath $project.sourcePath -ProjectName $project.name -OutputPath $config.deployment.outputPath } $results.Results.Deploy = "완료" } "Health" { Write-Host "`n🏥 시스템 상태 검사 중..." -ForegroundColor Yellow & ".\SystemMonitor.ps1" -ConfigPath $Config Start-AutoRecovery -ConfigPath $Config $results.Results.Health = "완료" } "All" { Write-Host "`n🚀 전체 작업 실행 중..." -ForegroundColor Yellow # 순서대로 실행 $operations = @("Health", "Backup", "Monitor", "Deploy") foreach ($subOp in $operations) { try { Write-Host "`n--- $subOp 실행 ---" -ForegroundColor Cyan Start-MasterController -Op $subOp -Config $Config -Test $Test -Report $Report $results.Results[$subOp] = "성공" } catch { $results.Results[$subOp] = "실패: $($_.Exception.Message)" $results.Errors += "$subOp`: $($_.Exception.Message)" Write-Warning "$subOp 작업 실패: $($_.Exception.Message)" } } } } $results.Success = $true } catch { $results.Errors += $_.Exception.Message Write-Host "작업 실패: $($_.Exception.Message)" -ForegroundColor Red throw } finally { $results.EndTime = Get-Date $results.Duration = $results.EndTime - $results.StartTime # 실행 결과 요약 Write-Host "`n=== 실행 결과 요약 ===" -ForegroundColor Green Write-Host "작업: $($results.Operation)" Write-Host "시작: $($results.StartTime.ToString('HH:mm:ss'))" Write-Host "완료: $($results.EndTime.ToString('HH:mm:ss'))" Write-Host "소요시간: $($results.Duration.ToString('mm\:ss'))" Write-Host "상태: $(if($results.Success){'성공'}else{'실패'})" -ForegroundColor $(if($results.Success){'Green'}else{'Red'}) if ($results.Results.Count -gt 0) { Write-Host "`n세부 결과:" -ForegroundColor Yellow foreach ($key in $results.Results.Keys) { Write-Host " $key`: $($results.Results[$key])" } } if ($results.Errors.Count -gt 0) { Write-Host "`n오류 목록:" -ForegroundColor Red foreach ($error in $results.Errors) { Write-Host " - $error" -ForegroundColor Red } } } return $results } # 실행 try { $result = Start-MasterController -Op $Operation -Config $ConfigPath -Test $TestMode -Report $GenerateReport # 최종 상태에 따른 종료 코드 설정 if ($result.Success) { exit 0 } else { exit 1 } } catch { Write-Host "마스터 컨트롤러 실행 실패: $($_.Exception.Message)" -ForegroundColor Red exit 1 }🔧 배치 파일 통합 실행
통합 실행 배치 파일 (RunAutomation.bat)
@echo off setlocal enabledelayedexpansion :: 설정 set SCRIPT_DIR=%~dp0 set CONFIG_FILE=%SCRIPT_DIR%config.json set LOG_DIR=%SCRIPT_DIR%Logs set REPORT_DIR=%SCRIPT_DIR%Reports :: 로그 디렉토리 생성 if not exist "%LOG_DIR%" mkdir "%LOG_DIR%" if not exist "%REPORT_DIR%" mkdir "%REPORT_DIR%" :: 현재 시간 for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a" set "YY=%dt:~2,2%" & set "YYYY=%dt:~0,4%" & set "MM=%dt:~4,2%" & set "DD=%dt:~6,2%" set "HH=%dt:~8,2%" & set "Min=%dt:~10,2%" & set "Sec=%dt:~12,2%" set "timestamp=%YYYY%-%MM%-%DD%_%HH%-%Min%" echo ================================================ echo PowerShell 자동화 시스템 실행 echo ================================================ echo 시작 시간: %YYYY%-%MM%-%DD% %HH%:%Min%:%Sec% echo 스크립트 위치: %SCRIPT_DIR% echo ================================================ if "%1"=="" ( echo. echo 사용법: RunAutomation.bat [작업] [옵션] echo. echo 작업: echo backup - 백업 작업 실행 echo monitor - 시스템 모니터링 echo deploy - 배포 패키지 생성 echo health - 시스템 상태 검사 echo all - 모든 작업 실행 echo. echo 옵션: echo test - 테스트 모드로 실행 echo report - HTML 보고서 생성 echo. echo 예제: echo RunAutomation.bat backup echo RunAutomation.bat monitor report echo RunAutomation.bat all test echo. pause exit /b 1 ) set OPERATION=%1 set TEST_MODE= set GENERATE_REPORT= :: 옵션 파싱 :parse_args if "%2"=="test" ( set TEST_MODE=-TestMode shift ) if "%2"=="report" ( set GENERATE_REPORT=-GenerateReport shift ) if not "%2"=="" ( shift goto parse_args ) echo 실행 작업: %OPERATION% if defined TEST_MODE echo 테스트 모드: 활성화 if defined GENERATE_REPORT echo 보고서 생성: 활성화 echo. :: PowerShell 실행 set PS_COMMAND=powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%MasterController.ps1" -Operation "%OPERATION%" -ConfigPath "%CONFIG_FILE%" %TEST_MODE% %GENERATE_REPORT% echo PowerShell 실행 중... echo 명령어: %PS_COMMAND% echo. %PS_COMMAND% 2>&1 | tee "%LOG_DIR%\automation_%timestamp%.log" set EXIT_CODE=%ERRORLEVEL% echo. echo ================================================ if %EXIT_CODE% EQU 0 ( echo 작업이 성공적으로 완료되었습니다. echo 상태: 성공 ) else ( echo 작업 실행 중 오류가 발생했습니다. echo 상태: 실패 ^(코드: %EXIT_CODE%^) ) echo ================================================ echo 로그 파일: %LOG_DIR%\automation_%timestamp%.log if defined GENERATE_REPORT ( if exist "%REPORT_DIR%" ( echo 보고서 폴더: %REPORT_DIR% start "" "%REPORT_DIR%" ) ) echo. pause exit /b %EXIT_CODE%🎯 마무리
이번 2편에서는 PowerShell 자동화를 한 단계 더 발전시켜 엔터프라이즈 환경에서도 안정적으로 운영할 수 있는 고급 기능들을 구현했습니다. JSON 설정 파일을 통한 유연한 구성 관리부터 이벤트 로그 연동, Slack 알림, 작업 스케줄러 등록, 에러 복구 로직까지 완전한 운영 시스템을 구축했습니다.
이제 여러분은 PowerShell의 Compress-Archive를 활용한 완전한 자동화 솔루션을 보유하게 되었습니다. 이 시스템은 실제 운영 환경에서 안정적으로 작동하며, 문제 발생 시 자동으로 복구를 시도하고 관리자에게 적절한 알림을 전송합니다.
다음 단계로는:
- 클라우드 스토리지 연동 (Azure Blob, AWS S3)
- Docker 컨테이너 환경에서의 자동화
- PowerShell DSC를 활용한 구성 관리
등을 고려해볼 수 있으며, 이는 추후 포스팅에서 다뤄보겠습니다.
📁 프로젝트 구조
완성된 자동화 시스템의 파일 구조는 다음과 같습니다:
PowerShellAutomation/ ├── config.json # 전체 시스템 설정 ├── MasterController.ps1 # 통합 제어 스크립트 ├── AdvancedBackup.ps1 # 고급 백업 스크립트 ├── SystemMonitor.ps1 # 시스템 모니터링 ├── ResilientBackup.ps1 # 복원력 있는 백업 ├── RegisterScheduledTask.ps1 # 스케줄러 등록 ├── RunAutomation.bat # 통합 실행 배치 ├── Logs/ # 실행 로그 ├── Reports/ # HTML 보고서 └── Scripts/ # 보조 스크립트 ├── WebDeployPackage.ps1 # 웹 배포 패키지 └── ViewLogs.ps1 # 로그 조회🚀 빠른 시작 가이드
1단계: 파일 준비
- 위의 모든 스크립트를 같은 폴더에 저장
- config.json 파일을 환경에 맞게 수정
- 필요한 폴더 생성 (Logs, Reports, Scripts)
2단계: 권한 설정
# PowerShell 실행 정책 설정 (관리자 권한 필요) Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine3단계: 첫 실행 테스트
# 테스트 모드로 실행 RunAutomation.bat all test # 실제 실행 RunAutomation.bat backup4단계: 스케줄러 등록
# 일일 백업 스케줄 등록 .\RegisterScheduledTask.ps1 -TaskName "DailyBackup" -ScriptPath "C:\Scripts\MasterController.ps1" -Schedule "Daily" -StartTime "02:00"🔍 트러블슈팅
일반적인 문제점과 해결방법
1. 실행 정책 오류
오류: 이 시스템에서 스크립트를 실행할 수 없습니다. 해결: Set-ExecutionPolicy RemoteSigned2. 네트워크 경로 접근 오류
오류: 네트워크 경로에 접근할 수 없습니다. 해결: - 네트워크 드라이브 매핑 확인 - 자격 증명 저장 (cmdkey 명령어 사용) - UNC 경로 권한 확인3. 이벤트 로그 권한 오류
오류: 이벤트 로그에 쓸 수 없습니다. 해결: 관리자 권한으로 스크립트 실행4. JSON 파싱 오류
오류: JSON 설정 파일을 읽을 수 없습니다. 해결: - JSON 문법 검증 (online JSON validator 사용) - 백슬래시 이스케이프 확인 (\\ 사용) - UTF-8 인코딩 확인📚 확장 가능한 기능들
이 시스템을 기반으로 다음과 같은 기능을 추가할 수 있습니다:
데이터베이스 백업 연동
# SQL Server 백업 통합 예제 function Backup-SQLDatabase { param([string]$ServerName, [string]$DatabaseName) $backupPath = "C:\Backup\Database\$DatabaseName_$(Get-Date -Format 'yyyy-MM-dd').bak" $query = "BACKUP DATABASE [$DatabaseName] TO DISK = '$backupPath'" Invoke-Sqlcmd -ServerInstance $ServerName -Query $query # .bak 파일을 zip으로 압축 Compress-Archive -Path $backupPath -DestinationPath "$backupPath.zip" -CompressionLevel Optimal Remove-Item $backupPath # 원본 .bak 파일 삭제 }웹 API 연동
# REST API 상태 모니터링 예제 function Test-WebAPIHealth { param([string]$ApiUrl) try { $response = Invoke-RestMethod -Uri "$ApiUrl/health" -Method Get -TimeoutSec 30 return @{ Status = "Healthy"; Response = $response } } catch { return @{ Status = "Unhealthy"; Error = $_.Exception.Message } } }성능 카운터 수집
# 성능 데이터 수집 및 압축 저장 function Collect-PerformanceData { $counters = @( "\Processor(_Total)\% Processor Time", "\Memory\Available MBytes", "\PhysicalDisk(_Total)\Disk Reads/sec" ) $data = Get-Counter -Counter $counters -SampleInterval 1 -MaxSamples 60 $csv = $data | Export-Csv -Path "perf_$(Get-Date -Format 'yyyyMMdd_HHmm').csv" -NoTypeInformation -PassThru # CSV를 압축하여 저장 공간 절약 Compress-Archive -Path $csv -DestinationPath "performance_data.zip" -Update Remove-Item $csv }💡 모범 사례
1. 보안 고려사항
- 스크립트는 최소 권한으로 실행
- 자격 증명은 Windows Credential Manager 사용
- 중요한 설정은 암호화하여 저장
2. 성능 최적화
- 대용량 파일은 병렬 처리 활용
- 압축 레벨을 용도에 맞게 조정
- 메모리 사용량 모니터링
3. 로깅 전략
- 구조화된 로그 형식 사용 (JSON, XML)
- 로그 순환(rotation) 정책 적용
- 중요도별 로그 레벨 구분
4. 모니터링 및 알림
- 임계값 기반 알림 설정
- 에스컬레이션 정책 수립
- 대시보드를 통한 시각화
이제 여러분은 PowerShell을 활용한 완전한 자동화 솔루션을 구축할 수 있는 모든 도구와 지식을 갖추었습니다. 이 시스템을 기반으로 조직의 요구사항에 맞게 확장하고 개선해나가시기 바랍니다!
728x90