После прочтенных комментариев, в основном благодаря @DmitryO решил что надо подумать о доработке функционала

Функциональные улучшения:

  • Расширенные места поиска: именованные диапазоны, диаграммы, проверка данных

  • ✅ Добавлена Детальная статистика с группировкой результатов по файлам

Структурные изменения:

  • 🔄 Модульная архитектура вместо монолитного кода

  • 🔄 Разделение логики поиска для XLSX и XLS форматов

  • 🔄 Улучшенная обработка ошибок с детальным логированием

  • 🔄 Валидация входных данных перед началом поиска


Основные изменения кода

1. Модульная структура функций

Было (монолитный код):

Весь код в одном большом блоке с дублированием логики

 foreach ($currentFile in $files) {  
    # Прямой анализ файла здесь же  
    $archive = [System.IO.Compression.ZipFile]::OpenRead($currentFile)  
    # ... логика поиска ...  
}  

Стало (модульные функции):

Разделение на специализированные функции

function Search-InXlsxFiles { ... }  
function Process-XlsxFiles { ... }  
function Analyze-XlsxFile { ... }  
function Find-InExternalLinks { ... }  

2. Поддержка XLS/XLSB файлов

Было:

$files = Get-ChildItem -Path $searchPath -Filter "*.xlsx" -Recurse  
$files += Get-ChildItem -Path $searchPath -Filter "*.xlsm" -Recurse  

Стало:

Отдельная обработка для разных форматов

function Search-InXlsFiles {  
    # Использование Excel COM API для старых форматов  
    $excel = New-Object -ComObject Excel.Application  
    $wb = $Excel.Workbooks.Open($FilePath, $false, $true)  
    $linkSources = $Workbook.LinkSources(1)  
}  
  
function Get-ExcelFiles {  
    param([string[]]$Extensions)  
    foreach ($ext in $Extensions) {  
        $files += Get-ChildItem -Path $SearchPath -Filter "*.$ext" -Recurse  
    }  
}  

3. Расширенные места поиска

Было (только внешние связи):

$linkFiles = $archive.Entries | Where-Object { $_.FullName -like 'xl/externalLinks/_rels/*.rels' }  
foreach ($linkFile in $linkFiles) {  
    # Поиск только в зарегистрированных связях  
}  

Стало (множественные источники):

1. Зарегистрированные внешние связи  
$externalLinks = Find-InExternalLinks $archive $FilePath $SearchFile  
  
# 2. Именованные диапазоны    
$namedRangeLinks = Find-InNamedRanges $archive $FilePath $SearchFile  
  
# 3. Диаграммы  
$chartLinks = Find-InCharts $archive $FilePath $SearchFile  
  
# 4. Проверка данных  
$dataValidationLinks = Find-InDataValidation $archive $FilePath $SearchFile  

4. Улучшенная обработка ошибок

Было:

Codetry {  
    # Простая обработка  
} catch {  
    Write-Host "Ошибка: $($_.Exception.Message)" -ForegroundColor Red  
}  

Стало:

Codefunction Test-FileAccessible {  
    try {  
        $stream = [System.IO.File]::Open($FilePath, 'Open', 'Read', 'None')  
        $stream.Close()  
        return $true  
    } catch {  
        return $false  
    }  
}  
  
# В основном коде:  
if (-not (Test-FileAccessible $FilePath)) {  
    Write-LogMessage "    Файл заблокирован" ([System.Drawing.Color]::Red)  
    return $foundLinks  
}  

5. Детальная статистика результатов

Было:

CodeWrite-Host "Найдено ссылок: $foundLinks"  

Стало:

powershellCopy Codefunction Write-SearchResults {  
    Write-LogMessage "ИТОГОВАЯ СТАТИСТИКА:" ([System.Drawing.Color]::Blue)  
    Write-LogMessage "Проверено файлов: $($Results.ProcessedFiles)"  
    Write-LogMessage "Найдено ссылок: $($Results.FoundLinks)"  
      
    # Группировка по файлам с детальной информацией  
    $groupedLinks = $Results.FoundLinksInfo | Group-Object SourceFile  
    foreach ($group in $groupedLinks) {  
        Write-LogMessage "Файл: $($group.Name)" ([System.Drawing.Color]::Green)  
        foreach ($link in $group.Group) {  
            Write-LogMessage "  Тип: $($link.LinkType)"  
            Write-LogMessage "  Местоположение: $($link.Location)"  
        }  
    }  
}  

6. Валидация входных данных

Не Было

Стало:

Codefunction Test-SearchInputs {  
    if ([string]::IsNullOrWhiteSpace($SearchFile)) {  
        Write-LogMessage "ОШИБКА: Не указан файл для поиска" ([System.Drawing.Color]::Red)  
        return $false  
    }  
      
    if (-not (Test-Path $SearchPath)) {  
        Write-LogMessage "ОШИБКА: Папка недоступна: $SearchPath" ([System.Drawing.Color]::Red)  
        return $false  
    }  
      
    return $true  
}

P.S.

Правда ранее в старой версии обработка файлов занимала секунд 10 при условии 60 файлов в папке. А теперь Эти же 60 файлов могу обрабатываться часами из‑за поиска по именам, ибо есть файлы с кучей имен (60 тыс в каждом). А Основной задаче было суметь разобраться в чужой структуре файлов и понять зависимости быстро... А в таком случае расследования будут затягиваться...