ABOUT ME

-

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

    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단계: 파일 준비

    1. 위의 모든 스크립트를 같은 폴더에 저장
    2. config.json 파일을 환경에 맞게 수정
    3. 필요한 폴더 생성 (Logs, Reports, Scripts)

    2단계: 권한 설정

    # PowerShell 실행 정책 설정 (관리자 권한 필요)
    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine
    

    3단계: 첫 실행 테스트

    # 테스트 모드로 실행
    RunAutomation.bat all test
    
    # 실제 실행
    RunAutomation.bat backup
    

    4단계: 스케줄러 등록

    # 일일 백업 스케줄 등록
    .\RegisterScheduledTask.ps1 -TaskName "DailyBackup" -ScriptPath "C:\Scripts\MasterController.ps1" -Schedule "Daily" -StartTime "02:00"
    

    🔍 트러블슈팅

    일반적인 문제점과 해결방법

    1. 실행 정책 오류

    오류: 이 시스템에서 스크립트를 실행할 수 없습니다.
    해결: Set-ExecutionPolicy RemoteSigned
    

    2. 네트워크 경로 접근 오류

    오류: 네트워크 경로에 접근할 수 없습니다.
    해결: 
    - 네트워크 드라이브 매핑 확인
    - 자격 증명 저장 (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

    댓글

Designed by Tistory.