PowerShell Compress-Archive: Исправление усеченных имен папок
Узнайте, почему командлет PowerShell Compress-Archive усекает имена папок и как сохранить полные имена в архивах. Найдите решения для проблемы ограничения длины пути в 255 символов.
Почему имена папок обрезаются при использовании командлета PowerShell Compress-Archive?
Я столкнулся с проблемой с командлетом PowerShell Compress-Archive, при котором имена папок обрезаются в результирующем архиве. Вот моя ситуация:
Структура папок
D:\
+ Logs
+ Prod
+ API1.domain.com
+ Server1.domain.com
+ Server2.domain.com
+ API2.domain.com
+ Server1.domain.com
+ Server2.domain.com
+ Staging
...
Скрипт PowerShell
Я создаю архивы для логов за определенный месяц с помощью PowerShell v5. Каждый архив следует шаблону именования “API1.domain.com_YYYYMM_{00}.zip”.
$BaseLogFilePath = "D:\Logs\Prod";
$LogArchivePath = $BaseLogFilePath+"\ArchiveLogs";
$TempPath = $BaseLogFilePath+"\Temp";
$list_MainFolders = Get-ChildItem -Path $BaseLogFilePath -Directory
foreach($mainFld in $list_MainFolders){
$logDir = $mainFld.FullName.Substring($BaseLogFilePath.Length);
$currentLogDir_SourcePath = Join-Path -Path $BaseLogFilePath -ChildPath $logDir;
#create subfolder in Temp for main directory
$currentLogDir_TempPath = Join-Path -Path $TempPath -ChildPath $logDir;
$currentLogDir_ArchiveFileName= $LogArchivePath+$logDir+"_"+$mnthName+".zip";
$currentLogDir_TempPathString = $currentLogDir_TempPath+"\*";
Compress-Archive -Path $currentLogDir_TempPathString -DestinationPath $currentLogDir_ArchiveFileName
}
Проблема
Когда я проверяю созданные архивы, структура папок выглядит так:
\Server1.domain
\Server2.domain
Полные имена папок (например, “Server1.domain.com”) обрезаются до “Server1.domain”.
Вопрос
Почему имена папок обрезаются при сжатии, и как можно сохранить полные имена папок в архиве?
Имена папок усекаются из-за ограничений командлета PowerShell Compress-Archive, который использует класс .NET ZipArchive, имеющий ограничение в 255 символов для путей к файлам и папкам внутри архива. Это усечение происходит, когда полный путь превышает это ограничение, что заставляет PowerShell сокращать имена для совместимости со спецификациями формата zip.
Содержание
- Понимание ограничения Compress-Archive
- Почему имена папок усекаются
- Решения для сохранения полных имен папок
- Альтернативные подходы
- Лучшие практики создания архивов
Понимание ограничения Compress-Archive
Командлет PowerShell Compress-Archive, представленный в PowerShell 5.0, relies on .NET System.IO.Compression.ZipArchive class для создания zip-архивов. Эта базовая реализация имеет inherent ограничения, которые могут вызывать усечение имен папок в определенных сценариях.
Ограничение в 255 символов для путей к файлам и папкам внутри zip-архива является хорошо задокументированным ограничением. Это ограничение применяется ко всему пути от корня архива до любого отдельного файла или папки. Когда ваша структура папок содержит длинные доменные имена, такие как “Server1.domain.com”, вложенные в несколько уровней, суммарная длина пути может легко превысить этот порог, вызывая автоматическое усечение механизмом сжатия.
# Пример того, как длина пути может быстро накапливаться
$rootPath = "D:\Logs\Prod\API1.domain.com\Server1.domain.com\"
$additionalPath = "Logs\2024\01\"
$fullPath = Join-Path -Path $rootPath -ChildPath $additionalPath
$fullPath.Length # Это может легко превысить 255 символов
Почему имена папок усекаются
Несколько факторов способствуют проблеме усечения, которую вы испытываете:
1. Длина пути превышает пределы формата zip
Спецификация формата zip-файлов устанавливает максимальную длину пути в 255 символов для любого файла или папки внутри архива. Когда ваша вложенная структура папок создает пути длиннее этого предела, PowerShell автоматически сокращает имена для соблюдения требований.
2. Алгоритм усечения PowerShell
При превышении предела пути PowerShell использует алгоритм усечения, который приоритизирует сохранение структуры папок, сокращая при этом отдельные имена папок. Это объясняет, почему вы видите “Server1.domain” вместо “Server1.domain.com” - расширение .com удаляется для сокращения длины пути.
3. Проблемы с Unicode и кодировкой символов
Длинные доменные имена со специальными символами или символами Unicode могут занимать больше “слотов символов” в подсчете пути, усугубляя проблему усечения.
4. Влияние вложенной структуры папок
Ваша вложенная структура с доменными именами в качестве имен папок создает сценарий, при котором даже умеренная глубина папок может вызвать превышение предела:
D:\Logs\Prod\API1.domain.com\Server1.domain.com\
# Путь: "API1.domain.com/Server1.domain.com/" = ~40 символов
# Добавьте еще несколько уровней, и вы приближаетесь к пределу
Решения для сохранения полных имен папок
1. Сокращение путей архива
Измените скрипт для создания архивов с более короткими путями, используя более прямой подход:
$BaseLogFilePath = "D:\Logs\Prod";
$LogArchivePath = $BaseLogFilePath+"\ArchiveLogs";
$TempPath = $BaseLogFilePath+"\Temp";
$list_MainFolders = Get-ChildItem -Path $BaseLogFilePath -Directory
foreach($mainFld in $list_MainFolders){
# Создайте плоскую структуру во временной папке, чтобы избежать вложенных путей
$shortName = $mainFld.Name # Используйте только имя папки, а не полный путь
$tempArchivePath = Join-Path -Path $TempPath -ChildPath $shortName
# Копируйте файлы во временную папку со сокращенными путями
Copy-Item -Path "$($mainFld.FullName)\*" -Destination $tempArchivePath -Recurse -Force
# Создайте архив из сокращенной структуры
$archiveName = "$shortName_$($mnthName).zip"
$archivePath = Join-Path -Path $LogArchivePath -ChildPath $archiveName
Compress-Archive -Path $tempArchivePath -DestinationPath $archivePath
# Очистите временные файлы
Remove-Item -Path $tempArchivePath -Recurse -Force
}
2. Использование сторонних инструментов сжатия
Рассмотрите возможность использования более надежных утилит сжатия, которые лучше обрабатывают длинные пути:
# Использование 7-Zip (7z.exe)
$7zPath = "C:\Program Files\7-Zip\7z.exe"
$archiveName = "$($mainFld.Name)_$($mnthName).zip"
$archivePath = Join-Path -Path $LogArchivePath -ChildPath $archiveName
& $7zPath a -tzip $archivePath "$($mainFld.FullName)\*"
3. Расширения сообщества PowerShell
Установите и используйте модуль Pscx, который предоставляет улучшенное управление архивами:
# Установите Pscx, если он еще не установлен
Install-Module -Name Pscx -Scope CurrentUser -Force
Import-Module Pscx
# Используйте Archive-Cmdlet из Pscx, который лучше обрабатывает длинные пути
Archive-Cmdlet -Path "$($mainFld.FullName)\*" -Destination $archivePath -Format Zip
4. Стратегия сокращения пути
Реализуйте пользовательскую стратегию сокращения пути перед сжатием:
function Shorten-PathForArchive {
param(
[string]$Path,
[int]$MaxLength = 240 # Оставьте некоторый буфер для накладных расходов zip
)
if ($Path.Length -le $MaxLength) {
return $Path
}
# Разделите путь и сократите компоненты с конца
$pathParts = $Path.Split([IO.Path]::DirectorySeparatorChar)
$shortened = @()
foreach ($part in $pathParts) {
if ($Path.Length -le $MaxLength) {
$shortened += $part
break
}
# Сократите текущий компонент
$shortPart = if ($part.Length -gt 8) { $part.Substring(0, 8) } else { $part }
$shortened += $shortPart
$Path = $Path.Substring(0, $Path.Length - $part.Length) + $shortPart
}
return [string]::Join([IO.Path]::DirectorySeparatorChar, $shortened)
}
Альтернативные подходы
1. Разделение больших архивов
Вместо создания одного большого архива разделите его на более мелкие части:
# Разделите по диапазонам дат или подпапкам
$startDate = Get-Date -Year 2024 -Month 1 -Day 1
$endDate = Get-Date -Year 2024 -Month 1 -Day 7
$logFiles = Get-ChildItem -Path $mainFld.FullName -Recurse -File |
Where-Object { $_.LastWriteTime -ge $startDate -and $_.LastWriteTime -lt $endDate }
# Создайте отдельный архив для каждого диапазона дат
foreach ($date in $startDate..$endDate) {
$dayFiles = $logFiles | Where-Object { $_.LastWriteTime.Date -eq $date }
if ($dayFiles) {
$dayArchive = "$($mainFld.Name)_$($date.ToString('yyyyMMdd')).zip"
Compress-Archive -Path $dayFiles.FullName -DestinationPath (Join-Path $LogArchivePath $dayArchive)
}
}
2. Использование относительных путей
Измените скрипт для использования относительных путей вместо абсолютных:
# Сначала измените на базовый каталог
Push-Location -Path $BaseLogFilePath
foreach($mainFld in $list_MainFolders){
$relativePath = $mainFld.Name # Только имя папки, а не полный путь
$archiveName = "$relativePath_$($mnthName).zip"
$archivePath = Join-Path -Path $LogArchivePath -ChildPath $archiveName
# Используйте относительный путь в сжатии
Compress-Archive -Path $relativePath -DestinationPath $archivePath
}
Pop-Location
3. Улучшения в PowerShell 7.x
Если возможно, обновитесь до PowerShell 7.x, который имеет улучшенное управление архивами:
# PowerShell 7.x имеет лучшую поддержку длинных путей
if ($PSVersionTable.PSVersion.Major -ge 7) {
# Включите длинные пути при необходимости
[Environment]::SetEnvironmentVariable("LONG_PATHS_ENABLED", "1", "Process")
Compress-Archive -Path $Path -DestinationPath $ArchivePath
}
Лучшие практики создания архивов
1. Мониторинг длины пути
Всегда контролируйте длину путей перед сжатием:
function Test-PathLength {
param([string]$Path)
$maxLength = 240 # Консервативный предел для zip-архивов
return ($Path.Length -le $maxLength)
}
# Используйте в вашем скрипте
if (-not (Test-PathLength -Path $currentLogDir_TempPathString)) {
Write-Warning "Путь превышает рекомендованную длину: $currentLogDir_TempPathString"
}
2. Конвенция именования архивов
Используйте более короткие, но описательные соглашения об именовании:
# Вместо: API1.domain.com_202401_00.zip
# Используйте: api1-2024-01.zip
$shortName = $mainFld.Name.Replace('.', '-').ToLower()
$archiveName = "${shortName}_${mnthName}.zip"
3. Регулярное обслуживание архивов
Реализуйте регулярную очистку и обслуживание архивов:
# Удалите старые архивы для предотвращения накопления путей
$cutoffDate = (Get-Date).AddMonths(-6)
$oldArchives = Get-ChildItem -Path $LogArchivePath -Filter "*.zip" |
Where-Object { $_.CreationTime -lt $cutoffDate }
foreach ($archive in $oldArchives) {
Remove-Item -Path $archive.FullName -Force
}
4. Тестирование и валидация
Всегда проверяйте содержимое архива после создания:
function Test-ArchiveContents {
param([string]$ArchivePath)
try {
$shell = New-Object -ComObject Shell.Application
$zip = $shell.NameSpace((Resolve-Path $ArchivePath).Path)
foreach ($item in $zip.Items()) {
if ($item.Path.Length -gt 240) {
Write-Warning "Обнаружен длинный путь в архиве: $($item.Path)"
}
}
return $true
}
catch {
Write-Error "Не удалось проверить архив: $_"
return $false
}
}
# Использование
Test-ArchiveContents -ArchivePath $currentLogDir_ArchiveFileName
Заключение
Усечение имен папок в командлете PowerShell Compress-Archive в основном вызвано ограничением в 255 символов для пути, наложенным спецификацией формата zip-файла. Это ограничение становится проблематичным при работе с вложенными структурами папок, содержащими длинные доменные имена, как в вашем сценарии ведения журналов.
Ключевые решения включают сокращение путей перед сжатием, использование сторонних инструментов с лучшей поддержкой длинных путей и реализацию более стратегической организации архивов. Приняв эти подходы, вы можете сохранять полные имена папок при эффективном управлении архивами.
Для немедленного решения я рекомендую реализовать стратегию сокращения пути или переключиться на 7-Zip для ваших задач сжатия. Кроме того, рассмотрите возможность обновления до PowerShell 7.x, если это возможно, так как он предлагает улучшенную обработку длинных путей и операций с файлами.