Windows, PowerShell и длинные пути

zihggini2_o9_0eqdiwrnorr54k.jpeg

Думаю, вам, как и мне, не раз приходилось видеть пути вида \!!! Важное\____Новое____\!!! Не удалять!!!\Приказ №98819–649-Б от 30 февраля 1985 г. о назначении Козлова Ивана Александровича временно исполняющим обязанности руководителя направления по сопровождению корпоративных VIP-клиентов и организации деловых встреч в кулуарах.doc. И зачастую открыть такой документ в Windows сходу не получится. Кто-то практикует workaround в виде мапирования дисков, кто-то использует файловые менеджеры, умеющие работать с длинными путями: Far Manager, Total Commander и им подобные. А еще многие с грустью наблюдали, как созданный ими PS-скрипт, в который было вложено немало труда и который в тестовом окружении работал на ура, в боевой среде беспомощно жаловался на непосильную задачу: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
Как оказалось, 260 символов хватит «не только лишь всем». Если вам интересно выйти за границы дозволенного — прошу под кат.
Вот лишь некоторые из печальных последствий ограничения длины файлового пути:

Немного отклоняясь от темы, замечу, что для DFS Replication рассматриваемая в статье проблема не страшна и файлы с длинными именами успешно путешествуют с сервера на сервер (если, конечно, в остальном вы всё сделали правильно).
Еще хотелось бы обратить внимание на очень полезную и не раз меня выручавшую утилиту robocopy. Ей тоже не страшны длинные пути, да и умеет она многое. Поэтому если задача сводится к копированию/переносу файловых данных, можно остановиться на ней. Если нужно пошаманить со списками контроля доступа в файловой системе (DACL), посмотрите в сторону subinacl. Несмотря на солидный возраст, отлично себя показала на Windows 2012 R2. Тут рассмотрены способы применения.
Мне же было интересно научить работать с длинными путями PowerShell. С ним почти как в бородатом анекдоте про Ивана-Царевича и Василису Прекрасную.

Быстрый способ


Перейти на Linux и не париться Windows 10/2016/2019 и включить соответствующий параметр групповой политики/твикнуть реестр. Подробно на этом способе останавливаться не буду, т.к. в сети уже много статей на эту тему, например, эта.
Учитывая, что в большинстве компаний много, мягко говоря, не свежих версий операционных систем, способ этот быстрый только для написания на бумаге, если, конечно, вы не из тех счастливчиков, у которых мало legacy-систем и царят Windows 10/2016/2019.

Долгий способ


Тут сразу оговоримся, что изменения не затронут поведение проводника Windows, а дадут возможность использовать длинные пути в командлетах PowerShell, таких как Get-Item, Get-ChildItem, Remove-Item и др.
Для начала обновим PowerShell. Делается на раз-два-три.

  1. Обновляем .NET Framework до версии не ниже 4.5. Операционная система должна быть не ниже Windows 7 SP1/2008 R2. Актуальную версию загрузить можно здесь, дополнительную информацию почитать тут.
  2. Скачиваем и устанавливаем Windows Management Framework 5.1
  3. Перезагружаем машину.


Трудолюбивые могут сделать описанные выше шаги вручную, ленивые — с помощью SCCM, политик, скриптов и эникеев других средств автоматизации.

Текущую версию PowerShell можно узнать из переменной $PSVersionTable. После обновления должно быть примерно так:
q_cdwg2qzweozvyt2lz5jes4o2o.png
Теперь при использовании командлетов Get-ChildItem и ему подобных вместо привычного Path будем использовать LiteralPath.
Формат путей при этом будет немного другим:
Get-ChildItem -LiteralPath "\\?\C:\Folder"
Get-ChildItem -LiteralPath "\\?\UNC\ServerName\Share"
Get-ChildItem -LiteralPath "\\?\UNC\192.168.0.10\Share"

Для удобства преобразования путей из привычного формата в формат LiteralPath можно использовать вот такую функцию:
Function ConvertTo-LiteralPath {
Param([parameter(Mandatory=$true, Position=0)][String]$Path)
If ($Path.Substring(0,2) -eq "\\") {Return ("\\?\UNC" + $Path.Remove(0,1))}
Else {Return "\\?\$Path"}
}

Обратите внимание, что при задании параметра LiteralPath нельзя использовать подстановочные символы (*, ? и т.д.)
Помимо параметра LiteralPath, в обновленной версии PowerShell командлет Get-ChildItem получил параметр Depth, с помощью которого можно задавать глубину вложенности для рекурсивного поиска, я пару раз его использовал и остался доволен.

Теперь можно не бояться, что ваш PS-скрипт собьется с долгого тернистого пути и не разглядит далекие файлы. Меня, например, очень выручил этот подход при написании скрипта для сбрасывания атрибута «временный» у файлов в DFSR-папках. Но это уже другая история, о которой я постараюсь рассказать в другой статье. От вас же жду интересных комментариев и предлагаю пройти опрос.

Полезные ссылки:
docs.microsoft.com/ru-ru/dotnet/api/microsoft.powershell.commands.contentcommandbase.literalpath? view=powershellsdk-1.1.0
docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem? view=powershell-5.1
stackoverflow.com/questions/46308030/handling-path-too-long-exception-with-new-psdrive/46309524
luisabreu.wordpress.com/2013/02/15/theliteralpath-parameter

© Habrahabr.ru