446 lines
20 KiB
PowerShell
446 lines
20 KiB
PowerShell
# PowerShell 5.1용 하드웨어 정보 수집 + 무결성 검증 스크립트
|
|
# (일반 사용자 권한 실행 가능, SSD/HDD 구분 개선, 가상 부품 필터링)
|
|
|
|
$results = @()
|
|
$hostname = $env:COMPUTERNAME
|
|
# 실제 환경에 맞게 저장 경로 수정 (사용자가 접근 가능한 공유 폴더 또는 로컬 경로)
|
|
$saveDir = "\\storage\PCInfo" # 예시: 사용자가 쓰기 가능한 공유 폴더
|
|
# $saveDir = "$env:TEMP\HWInfo" # 예시: 사용자 임시 폴더 (로컬 저장 시)
|
|
$outPath = Join-Path $saveDir "HWInfo_$hostname.json"
|
|
$timestamp = (Get-Date).ToString("s") # ISO 8601 형식 (YYYY-MM-DDTHH:MM:SS)
|
|
$logFile = Join-Path $saveDir "logs\HWInfo_ErrorLog_$hostname.txt" # 에러 로그 파일 경로
|
|
|
|
# --- 함수 정의 ---
|
|
|
|
# 로그 폴더 생성 (스크립트 시작 시)
|
|
$logDir = Join-Path $saveDir "logs"
|
|
if (-not (Test-Path $logDir)) {
|
|
try {
|
|
# New-Item은 해당 경로에 대한 쓰기 권한 필요
|
|
New-Item -Path $logDir -ItemType Directory -Force -ErrorAction Stop
|
|
Write-Host "✅ 로그 폴더 생성: $logDir"
|
|
} catch {
|
|
# 로그 폴더 생성 실패는 경고로 처리하고 계속 진행 (로그 파일 생성 불가)
|
|
Write-Warning "❌ 로그 폴더 생성 실패 ($logDir): $($_.Exception.Message). 에러 로그를 저장할 수 없습니다."
|
|
# 로그 파일 경로를 null로 설정하여 이후 로그 기록 시도 방지 (선택 사항)
|
|
$logFile = $null
|
|
}
|
|
}
|
|
|
|
# 에러 로그 기록 함수
|
|
function Write-ErrorLog {
|
|
param ([string]$message)
|
|
# 로그 파일 경로가 유효할 때만 기록 시도
|
|
if ($logFile -and (Test-Path (Split-Path $logFile -Parent))) {
|
|
$timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
|
|
$logMessage = "[$timestamp] [$hostname] $message" # 호스트명 포함
|
|
try {
|
|
# Add-Content는 해당 파일에 대한 쓰기 권한 필요
|
|
Add-Content -Path $logFile -Value "$logMessage`r`n" -Encoding UTF8 -ErrorAction Stop
|
|
} catch {
|
|
Write-Warning "!! 로그 파일 쓰기 실패 ($logFile): $($_.Exception.Message)"
|
|
}
|
|
} else {
|
|
# 로그 파일 경로가 없거나 상위 폴더가 없으면 콘솔에 경고만 출력
|
|
Write-Warning "!! 로그 파일 경로 문제로 에러 로그 기록 불가: $message"
|
|
}
|
|
}
|
|
|
|
# 저장 경로 확인 (스크립트 시작 시)
|
|
if (-not (Test-Path $saveDir)) {
|
|
$errorMsg = "❌ 저장 폴더 경로($saveDir)를 찾을 수 없습니다. 스크립트를 종료합니다."
|
|
# 로그 기록 시도
|
|
Write-ErrorLog $errorMsg
|
|
Write-Error $errorMsg
|
|
exit 1 # 저장 경로 없으면 진행 불가
|
|
}
|
|
# 저장 경로 쓰기 권한 확인 (간단 체크)
|
|
try {
|
|
$testFile = Join-Path $saveDir "write_test_$hostname.tmp"
|
|
"test" | Out-File -FilePath $testFile -Encoding UTF8 -Force -ErrorAction Stop
|
|
Remove-Item $testFile -Force -ErrorAction SilentlyContinue
|
|
} catch {
|
|
$errorMsg = "❌ 저장 폴더($saveDir)에 쓰기 권한이 없습니다: $($_.Exception.Message). 스크립트를 종료합니다."
|
|
Write-ErrorLog $errorMsg
|
|
Write-Error $errorMsg
|
|
exit 1 # 쓰기 권한 없으면 진행 불가
|
|
}
|
|
|
|
|
|
# 문자열 정리 함수
|
|
function Clean-String {
|
|
param ([string]$str)
|
|
if ([string]::IsNullOrWhiteSpace($str)) { return "N/A" }
|
|
# 여러 공백을 하나로, 앞뒤 공백 제거
|
|
return ($str -replace '\s{2,}', ' ').Trim()
|
|
}
|
|
|
|
# 안전한 CIM 호출 래퍼
|
|
function Safe-Get {
|
|
param([ScriptBlock]$Block)
|
|
try {
|
|
# 일반 사용자 권한에서는 일부 WMI/CIM 클래스 접근이 제한될 수 있음
|
|
# ErrorAction Stop으로 오류 발생 시 catch 블록으로 넘김
|
|
return & $Block -ErrorAction Stop
|
|
} catch {
|
|
# 권한 부족 또는 기타 오류 로깅
|
|
$errorMsg = "⚠️ WMI/CIM 쿼리 오류: $($_.Exception.Message) (쿼리: $($Block.ToString()))"
|
|
Write-ErrorLog $errorMsg
|
|
Write-Warning $errorMsg # 콘솔에도 경고 표시
|
|
return $null # 오류 발생 시 null 반환
|
|
}
|
|
}
|
|
|
|
# PSCustomObject 생성 함수
|
|
function Get-HWInfoObject {
|
|
param (
|
|
[string]$Category,
|
|
[string]$Manufacturer,
|
|
[string]$Model,
|
|
[string]$Serial,
|
|
[string]$Spec,
|
|
[string]$Unit,
|
|
[string]$PortOrSlot
|
|
)
|
|
# 입력 값 정리
|
|
$Manufacturer = Clean-String $Manufacturer
|
|
$Model = Clean-String $Model
|
|
$Serial = Clean-String $Serial # 시리얼은 이후에 필터링에서 사용됨
|
|
# Spec 값이 숫자인 경우 문자열로 변환, 아니면 정리
|
|
$SpecValueStr = if ($Spec -is [double] -or $Spec -is [int] -or $Spec -is [long] -or $Spec -is [decimal]) {
|
|
$Spec.ToString()
|
|
} elseif ($Spec -is [System.Management.Automation.PSCustomObject]) {
|
|
# Spec 값이 복잡한 객체일 경우 기본 문자열로 (오류 방지)
|
|
"[Object]"
|
|
}
|
|
else {
|
|
Clean-String $Spec
|
|
}
|
|
$Unit = Clean-String $Unit
|
|
$PortOrSlot = Clean-String $PortOrSlot
|
|
|
|
# PSCustomObject 반환
|
|
return [PSCustomObject]@{
|
|
Hostname = $hostname
|
|
Category = $Category
|
|
Manufacturer = $Manufacturer
|
|
Model = $Model
|
|
Serial = $Serial
|
|
SpecValue = $SpecValueStr
|
|
SpecUnit = $Unit
|
|
Timestamp = $timestamp
|
|
PortOrSlot = $PortOrSlot
|
|
}
|
|
}
|
|
|
|
# 가상 부품 식별 함수
|
|
function Test-IsVirtualComponent {
|
|
param(
|
|
[string]$Manufacturer,
|
|
[string]$Model,
|
|
[string]$SerialNumber,
|
|
[string]$PnpDeviceID # 선택적 파라미터
|
|
)
|
|
|
|
# 입력값 null/공백 체크
|
|
$mfr = if ($Manufacturer) { $Manufacturer.ToLower() } else { "" }
|
|
$mdl = if ($Model) { $Model.ToLower() } else { "" }
|
|
$ser = if ($SerialNumber) { $SerialNumber.Trim() } else { "" } # Trim() 먼저 적용
|
|
$pnp = if ($PnpDeviceID) { $PnpDeviceID.ToLower() } else { "" }
|
|
|
|
# 1. 제조사/모델명 기반 가상화 키워드 확인
|
|
$virtualKeywords = "virtual", "vmware", "hyper-v", "qemu", "parallels", "microsoft virtual", `
|
|
"basic display", "standard vga", `
|
|
"luminoncore", "demoforge", "mirage", "iddcx"
|
|
foreach ($keyword in $virtualKeywords) {
|
|
# 모델명 또는 제조사명에 키워드가 포함되어 있는지 확인
|
|
# (제조사가 '(표준 디스크 드라이브)' 같은 일반적인 이름일 수 있으므로 모델명 우선)
|
|
if ($mdl.Contains($keyword) -or $mfr.Contains($keyword)) {
|
|
Write-Verbose "가상 부품 의심 (키워드): Mfr='$Manufacturer', Model='$Model'"
|
|
return $true
|
|
}
|
|
}
|
|
|
|
# 2. 특정 PNP Device ID 확인 (VGA)
|
|
if ($pnp -match "ven_15ad") { # VMware VEN_ID
|
|
Write-Verbose "가상 부품 의심 (PNP ID): $PnpDeviceID"
|
|
return $true
|
|
}
|
|
# 다른 가상 하드웨어 VEN/DEV ID 추가 가능 (예: ven_80ee for VirtualBox)
|
|
if ($pnp -match "ven_80ee") {
|
|
Write-Verbose "가상 부품 의심 (PNP ID - VirtualBox?): $PnpDeviceID"
|
|
return $true
|
|
}
|
|
|
|
|
|
# 3. 시리얼 번호 기반 필터링 (확실한 경우)
|
|
# "N/A"는 Clean-String에서 처리
|
|
$invalidSerials = @("&&", "0", "00000000", "none", "not available", "default string", "to be filled by o.e.m.")
|
|
if ($ser -in $invalidSerials -or $ser -eq "") {
|
|
Write-Verbose "가상/유효하지 않음 의심 (시리얼): Serial='$SerialNumber'"
|
|
return $true
|
|
}
|
|
# "FFFF..." 패턴은 NVMe 가능성으로 제외
|
|
|
|
# 위 조건 모두 해당 없으면 가상 부품 아님
|
|
return $false
|
|
}
|
|
|
|
|
|
# === 정보 수집 시작 ===
|
|
Write-Host "--------------------------------------------------"
|
|
Write-Host "하드웨어 정보 수집 시작 ($hostname) - 사용자: $($env:USERNAME)"
|
|
Write-Host "저장 경로: $saveDir"
|
|
Write-Host "--------------------------------------------------"
|
|
|
|
# --- 요약 정보 (항상 포함) ---
|
|
$osCaptionForSummary = (Get-CimInstance Win32_OperatingSystem).Caption # OS 이름 미리 가져오기
|
|
$domainForSummary = (Get-CimInstance Win32_ComputerSystem).Domain # 도메인 이름 미리 가져오기
|
|
$results += Get-HWInfoObject "Summary" "N/A" "OS" "N/A" $osCaptionForSummary "" ""
|
|
$results += Get-HWInfoObject "Summary" "N/A" "Domain" "N/A" $domainForSummary "" ""
|
|
$results += Get-HWInfoObject "Summary" "N/A" "User" "N/A" $env:USERNAME "" ""
|
|
# IP 주소 요약은 아래에서 채움
|
|
$results += Get-HWInfoObject "Summary" "N/A" "IPv4" "N/A" "N/A" "" ""
|
|
|
|
|
|
# --- OS 정보 ---
|
|
$os = Safe-Get { Get-CimInstance Win32_OperatingSystem }
|
|
if ($os) {
|
|
$results += Get-HWInfoObject "OS" "Microsoft" $os.Caption $os.SerialNumber $os.Version "" ""
|
|
} else { Write-Warning "OS 정보 수집 실패" }
|
|
|
|
# --- Domain 정보 ---
|
|
$cs = Safe-Get { Get-CimInstance Win32_ComputerSystem }
|
|
if ($cs) {
|
|
$results += Get-HWInfoObject "Domain" "N/A" $cs.Domain $cs.Name $cs.PartOfDomain "" "" # Serial 자리에 PC Name 추가
|
|
} else { Write-Warning "컴퓨터 시스템 정보 수집 실패" }
|
|
|
|
# --- AD Site 정보 ---
|
|
# nltest는 AD 환경 및 경로 설정 필요, 일반 사용자 실행 어려울 수 있음 -> 제외 고려 또는 다른 방법 모색
|
|
# $site = Safe-Get { nltest /dsgetsite }
|
|
# if ($site -is [string] -and $site -notmatch "The command completed successfully") {
|
|
# $siteName = ($site -split ':')[1].Trim()
|
|
# $results += Get-HWInfoObject "AD Site" "N/A" $siteName "N/A" "" "" ""
|
|
# } else { Write-Verbose "AD Site 정보 수집 실패 또는 해당 없음" }
|
|
|
|
|
|
# --- IPv4 주소 ---
|
|
$ipv4s = @()
|
|
$adapters = Safe-Get { Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true -and $_.DefaultIPGateway -ne $null } } # Default Gateway 있는 어댑터 우선
|
|
if ($adapters) {
|
|
foreach ($adapter in $adapters) {
|
|
# 가상 네트워크 어댑터 제외 강화 (Description + Manufacturer)
|
|
$adapterInfo = Safe-Get { Get-CimInstance Win32_NetworkAdapter -Filter "DeviceID='$($adapter.Index)'" }
|
|
$isVirtualAdapter = $false
|
|
if ($adapterInfo) {
|
|
if ($adapter.Description -match "virtual|vmware|hyper-v|vpn|loopback|bluetooth|teredo|isatap" -or `
|
|
$adapterInfo.Manufacturer -match "vmware|microsoft|tunnel|juniper|fortinet|cisco") {
|
|
$isVirtualAdapter = $true
|
|
Write-Verbose "가상 네트워크 어댑터 건너뜀: $($adapter.Description)"
|
|
}
|
|
} else {
|
|
# Win32_NetworkAdapter 정보 못 가져오면 Description만으로 판단
|
|
if ($adapter.Description -match "virtual|vmware|hyper-v|vpn|loopback|bluetooth|teredo|isatap"){
|
|
$isVirtualAdapter = $true
|
|
Write-Verbose "가상 네트워크 어댑터 건너뜀 (Description 기준): $($adapter.Description)"
|
|
}
|
|
}
|
|
|
|
if (-not $isVirtualAdapter) {
|
|
foreach ($ip in $adapter.IPAddress) {
|
|
# IPv6 주소 제외, 유효 IPv4 형식, 0.0.0.0 제외, APIPA 제외
|
|
if ($ip -match "^\d{1,3}(\.\d{1,3}){3}$" -and $ip -ne "0.0.0.0" -and $ip -notlike "169.254.*") {
|
|
$ipv4s += $ip
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$ipv4s = $ipv4s | Select-Object -Unique # Sort-Object 대신 Select -Unique 사용
|
|
if ($ipv4s) {
|
|
$ipv4String = $ipv4s -join ", "
|
|
$results += Get-HWInfoObject "IPv4" "N/A" "All" "N/A" $ipv4String "" ""
|
|
# Summary 업데이트
|
|
$summaryIPv4 = $results | Where-Object { $_.Category -eq "Summary" -and $_.Model -eq "IPv4" }
|
|
if ($summaryIPv4) { $summaryIPv4.SpecValue = $ipv4String }
|
|
} else { Write-Warning "유효한 IPv4 주소를 찾을 수 없습니다."}
|
|
} else { Write-Warning "네트워크 어댑터 구성 정보 수집 실패"}
|
|
|
|
|
|
# --- CPU 정보 ---
|
|
$cpu = Safe-Get { Get-CimInstance Win32_Processor | Select-Object -First 1 }
|
|
if ($cpu) {
|
|
# CPU 정보는 필터링 없이 추가
|
|
$results += Get-HWInfoObject "CPU" $cpu.Manufacturer $cpu.Name $cpu.ProcessorId $cpu.MaxClockSpeed "MHz" "N/A"
|
|
} else { Write-Warning "CPU 정보 수집 실패" }
|
|
|
|
|
|
# --- 메모리 정보 ---
|
|
$mems = Safe-Get { Get-CimInstance Win32_PhysicalMemory }
|
|
if ($mems) {
|
|
foreach ($mem in $mems) {
|
|
# 메모리 정보는 필터링 없이 추가 (필요시 Test-IsVirtualComponent 사용 가능)
|
|
$sizeGB = try { [math]::Round($mem.Capacity / 1GB, 2) } catch { 0 } # 용량 계산 오류 방지
|
|
$slot = if ($mem.DeviceLocator) { Clean-String $mem.DeviceLocator } else { "N/A" }
|
|
$results += Get-HWInfoObject "Memory" $mem.Manufacturer $mem.PartNumber $mem.SerialNumber $sizeGB "GB" $slot
|
|
}
|
|
} else { Write-Warning "메모리 정보 수집 실패"}
|
|
|
|
|
|
# --- 메인보드 정보 ---
|
|
$mb = Safe-Get { Get-CimInstance Win32_BaseBoard | Select-Object -First 1 }
|
|
if ($mb) {
|
|
if (-not (Test-IsVirtualComponent -Manufacturer $mb.Manufacturer -Model $mb.Product -SerialNumber $mb.SerialNumber)) {
|
|
$results += Get-HWInfoObject "Mainboard" $mb.Manufacturer $mb.Product $mb.SerialNumber "0" "" "N/A"
|
|
} else {
|
|
Write-Host " [필터링] 가상 메인보드 건너뜀: $($mb.Product)"
|
|
}
|
|
} else { Write-Warning "메인보드 정보 수집 실패" }
|
|
|
|
|
|
# --- 디스크 정보 (Win32_DiskDrive 사용 및 Model 확인) ---
|
|
$disks = Safe-Get { Get-CimInstance Win32_DiskDrive | Where-Object { $_.Size -gt 0 -and $_.Status -eq 'OK'} } # Status 'OK' 추가
|
|
|
|
if ($disks) {
|
|
foreach ($disk in $disks) {
|
|
$manufacturer = $disk.Manufacturer
|
|
$model = $disk.Model
|
|
$serial = $disk.SerialNumber
|
|
$sizeGB = try {[math]::Round($disk.Size / 1GB, 2)} catch {0}
|
|
$port = if ($disk.Index -ne $null) { "Disk_$($disk.Index)" } else { "N/A" }
|
|
|
|
# Category 결정
|
|
$category = "HDD" # 기본값 HDD
|
|
$modelLower = $model.ToLower()
|
|
if ($disk.MediaType -like "*ssd*") { $category = "SSD" }
|
|
elseif ($modelLower -like "*ssd*" -and $modelLower -notlike "*sshd*") { $category = "SSD" }
|
|
|
|
# 가상 디스크 필터링
|
|
if (-not (Test-IsVirtualComponent -Manufacturer $manufacturer -Model $model -SerialNumber $serial)) {
|
|
$results += Get-HWInfoObject $category $manufacturer $model $serial $sizeGB "GB" $port
|
|
} else {
|
|
Write-Host " [필터링] 가상 디스크 건너뜀: $($model)"
|
|
}
|
|
}
|
|
} else { Write-Warning "디스크 정보 수집 실패 또는 찾을 수 없음" }
|
|
|
|
|
|
# --- VGA 정보 ---
|
|
$vgas = Safe-Get { Get-CimInstance Win32_VideoController }
|
|
if ($vgas) {
|
|
$slotCounter = 1
|
|
foreach ($vga in $vgas) {
|
|
$manufacturer = $vga.AdapterCompatibility
|
|
$model = $vga.Name
|
|
$pnp = $vga.PNPDeviceID
|
|
$ven = ""; $dev = ""; $sub = "" # 초기화
|
|
# VEN/DEV/SUBSYS 추출 (정규식 오류 방지 강화)
|
|
try { $ven = ([regex]::Match($pnp, "VEN_([0-9A-Fa-f]{4})")).Groups[1].Value } catch {}
|
|
try { $dev = ([regex]::Match($pnp, "DEV_([0-9A-Fa-f]{4})")).Groups[1].Value } catch {}
|
|
try { $sub = ([regex]::Match($pnp, "SUBSYS_([0-9A-Fa-f]{8})")).Groups[1].Value } catch {}
|
|
$serial = "$ven&$dev&$sub" # 시리얼 번호로 사용 (고유성 보장 안 될 수 있음)
|
|
$vram = try { [math]::Round($vga.AdapterRAM / 1MB) } catch { 0 }
|
|
$slot = "PCI_$slotCounter"
|
|
|
|
# 가상 VGA 필터링
|
|
if (-not (Test-IsVirtualComponent -Manufacturer $manufacturer -Model $model -SerialNumber $serial -PnpDeviceID $pnp)) {
|
|
$results += Get-HWInfoObject "VGA" $manufacturer $model $serial $vram "MB" $slot
|
|
$slotCounter++
|
|
} else {
|
|
Write-Host " [필터링] 가상 VGA 건너뜀: $($model)"
|
|
}
|
|
}
|
|
} else { Write-Warning "VGA 정보 수집 실패" }
|
|
|
|
|
|
# === JSON 저장 ===
|
|
if ($results.Count -lt 5) { # 요약 정보 포함 최소 개수 확인
|
|
$errorMsg = "❌ 수집된 유효한 하드웨어 정보가 충분하지 않습니다 (개수: $($results.Count))."
|
|
Write-ErrorLog $errorMsg
|
|
Write-Error $errorMsg
|
|
exit 1
|
|
}
|
|
|
|
try {
|
|
# 속성 순서 고정 및 JSON 변환
|
|
$orderedResults = $results | Select-Object Hostname, Category, Manufacturer, Model, Serial, SpecValue, SpecUnit, Timestamp, PortOrSlot
|
|
$json = $orderedResults | ConvertTo-Json -Depth 5 -Compress -ErrorAction Stop
|
|
|
|
# 파일 저장 (UTF8 BOM 포함 - Rust에서 처리)
|
|
$json | Out-File $outPath -Encoding UTF8 -Force -ErrorAction Stop
|
|
Write-Host "✅ 수집 완료 및 저장: $outPath"
|
|
} catch {
|
|
$errorMsg = "❌ JSON 변환 또는 저장 실패: $($_.Exception.Message)"
|
|
Write-ErrorLog $errorMsg
|
|
Write-Error $errorMsg
|
|
exit 1
|
|
}
|
|
|
|
|
|
# === 무결성 검증 ===
|
|
$verifyFailed = $false
|
|
try {
|
|
if (-not (Test-Path $outPath)) {
|
|
$errorMsg = "❌ 저장된 JSON 파일($outPath)이 존재하지 않습니다."
|
|
Write-ErrorLog $errorMsg
|
|
Write-Error $errorMsg
|
|
$verifyFailed = $true
|
|
} else {
|
|
$fileInfo = Get-Item $outPath
|
|
if ($fileInfo.Length -lt 100) { # 최소 파일 크기 기준 상향 조정
|
|
$warningMsg = "⚠️ 저장된 파일 크기가 너무 작습니다 ($($fileInfo.Length) bytes). (내용 검토 필요)"
|
|
Write-ErrorLog $warningMsg
|
|
Write-Warning $warningMsg
|
|
$verifyFailed = $true
|
|
} else {
|
|
$parsed = $null # 초기화
|
|
$parsed = Get-Content $outPath -Raw | ConvertFrom-Json -ErrorAction Stop # -Raw 추가 (성능 개선)
|
|
if ($null -eq $parsed -or $parsed.Count -eq 0) {
|
|
$warningMsg = "⚠️ JSON 파싱 결과 데이터가 없습니다. (파일 내용 검토 필요)"
|
|
Write-ErrorLog $warningMsg
|
|
Write-Warning $warningMsg
|
|
$verifyFailed = $true
|
|
} else {
|
|
# 필수 카테고리 확인 강화
|
|
$hasCPU = $parsed.Category -contains "CPU"
|
|
$hasMemory = $parsed.Category -contains "Memory"
|
|
$hasDisk = $parsed.Category -contains "SSD" -or $parsed.Category -contains "HDD"
|
|
if (-not ($hasCPU -and $hasMemory -and $hasDisk)) {
|
|
$missing = @()
|
|
if (-not $hasCPU) {$missing += "CPU"}
|
|
if (-not $hasMemory) {$missing += "Memory"}
|
|
if (-not $hasDisk) {$missing += "Disk(SSD/HDD)"}
|
|
$warningMsg = "⚠️ 필수 하드웨어 정보가 누락되었습니다: $($missing -join ', ')"
|
|
Write-ErrorLog $warningMsg
|
|
Write-Warning $warningMsg
|
|
$verifyFailed = $true
|
|
}
|
|
# 추가: 결과 배열에 최소 항목 수 확인 (예: 요약 4개 + 필수 3개 = 7개 이상)
|
|
if ($parsed.Count -lt 7) {
|
|
$warningMsg = "⚠️ 수집된 항목 수가 너무 적습니다 ($($parsed.Count)개). (상세 검토 필요)"
|
|
Write-ErrorLog $warningMsg
|
|
Write-Warning $warningMsg
|
|
$verifyFailed = $true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
$errorMsg = "❌ 무결성 검증 중 오류 발생: $($_.Exception.Message)"
|
|
Write-ErrorLog $errorMsg
|
|
Write-Error $errorMsg
|
|
$verifyFailed = $true
|
|
}
|
|
|
|
# 최종 결과 처리
|
|
if ($verifyFailed) {
|
|
$finalMsg = "❌ 무결성 검증 실패. 시스템 관리자에게 문의하세요. 로그 파일: $logFile"
|
|
Write-ErrorLog $finalMsg
|
|
Write-Error $finalMsg
|
|
exit 1 # 검증 실패 시 종료 코드 1
|
|
} else {
|
|
$finalMsg = "✅ 무결성 검증 완료. 데이터가 정상입니다."
|
|
Write-Host $finalMsg
|
|
}
|
|
|
|
Write-Host "스크립트 실행 완료."
|
|
exit 0 # 성공 시 종료 코드 0 |