Принудительно удалять файлы и каталоги в PowerShell иногда не удается, но не всегда

33

Я пытаюсь рекурсивно удалить каталог rm -Force -Recurse somedirectory, получаю несколько ошибок «Каталог не пустой». Если я повторю ту же команду , она будет успешной.

Пример:

PS I:\Documents and Settings\m\My Documents\prg\net> rm -Force -Recurse .\FileHelpers
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests\Data\RunTime\_svn: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (_svn:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests\Data\RunTime: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (RunTime:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests\Data: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (Data:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (FileHelpers.Tests:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\Libs\nunit\_svn: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (_svn:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\Libs\nunit: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (nunit:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\Libs: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (Libs:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (I:\Documents an...net\FileHelpers:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
PS I:\Documents and Settings\m\My Documents\prg\net> rm -Force -Recurse .\FileHelpers
PS I:\Documents and Settings\m\My Documents\prg\net>

Конечно, это происходит не всегда . Кроме того, это происходит не только с _svnкаталогами, и у меня нет кеша TortoiseSVN или чего-то подобного, поэтому ничто не блокирует каталог.

Любые идеи?

Маурисио Шеффер
источник

Ответы:

31

help Remove-Item говорит:

Параметр Recurse в этом командлете не работает должным образом.

а также

Поскольку параметр Recurse в этом командлете неисправен, команда использует командлет Get-Childitem для получения файлов желаний и использует оператор конвейера для передачи их в командлет Remove-Item.

и предлагает эту альтернативу в качестве примера:

get-childitem * -include *.csv -recurse | remove-item

Таким образом , вы должны трубу get-childitem -recurseв remove-item.

Приостановлено до дальнейшего уведомления.
источник
Спасибо. Только что нашел эту ветку с 2006 года: vistax64.com/powershell/… похоже, что Microsoft на самом деле не заинтересована в исправлении этого.
Маурисио Шеффер
@mausch: см. более свежую, но все еще не разрешенную ссылку: Remove-Item -Recurse
приостановлена ​​до дальнейшего уведомления.
если вы выполняете обход и удаляете, вам сначала нужно пройти через дочерние каталоги, а затем их файлы.
fschwiet
2
По крайней мере, в документации сказано, что это не работает.
Дерекердманн
6
Мне нужно было установить оба флажка -force -recurse для Remove-Item, в противном случае я не получил подсказку «пожалуйста, подтвердите» Get-ChildItem -Path $ Destination -Recurse | Remove-Item -force -recurse
MiFreidgeim SO-перестань быть злым
17

@JamesCW: проблема все еще существует в PowerShell 4.0

Я попытался другой обходной путь, и он работал: используйте cmd.exe:

&cmd.exe /c rd /s /q $somedirectory
Мехрдад Мирреза
источник
1
Старый добрый rd / s / q!
JamesCW
Я пробовал все варианты Get-ChildItem; повторные петли; вызов iisresetперед удалением, и кажется, что ничего не работает надежно . Я попробую это, хотя, когда я впервые увидел это, я воздерживался от того, чтобы DOS был внутри моей Powershell ...
Питер МакЭвой
К сожалению, rd /sпериодически тоже происходит сбой (хотя, по-видимому, реже, чем Remove-Item): github.com/Microsoft/console/issues/309
mklement
Мне не нравится косая черта от c. Нужно ли указывать перед ним команду powershell -com и заключить в кавычки часть cmd.exe? Я получаю «Вы должны предоставить выражение значения после оператора« / ».» «Неожиданный токен« c »в выражении или утверждении. То же самое и с командой powershell перед ним. Нужно ли бежать / убегать?
Мишель
7

ETA 20181217: PSVersion 4.0 и более поздние версии по-прежнему не будут работать при некоторых обстоятельствах, см. Альтернативный ответ от Mehrdad Mirreza и отчет об ошибке, поданный mklement

mklement предоставляет решение Proof of Concept в этом ответе SO , поскольку ошибка ожидает официального исправления

Новая версия PowerShell( PSVersion 4.0) полностью решила эту проблему и Remove-Item "targetdirectory" -Recurse -Forceработает без проблем с синхронизацией.

Вы можете проверить свою версию, запустив $PSVersiontableизнутри ISE или PowerShellприглашение. 4.0 - это версия, поставляемая с Windows 8.1и Server 2012 R2, и она может быть установлена ​​и в предыдущих версиях Windows.

JamesCW
источник
5
По-прежнему происходит для меня в PowerShell 4.0
ajbeaven
10
По-прежнему происходит в PowerShell v5 !!!!! 11 !! 1! 1 !!!
Ричард Хауэр
@RichardHauer хорошо, теперь я просто запутался
JamesCW
2
@JamesCW Я перешел на rdверсию. Помимо фактической работы, это примерно в 3 раза быстрее
Ричард Хауэр
Проблема не была исправлена ​​в Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 - см. Этот отчет об ошибке . Хотя rd /sможет случиться неудача реже, он тоже не работает - см. Этот отчет об ошибке .
Мклемент
4

Обновление : По-видимому, планируется сделать API-интерфейсы удаления элементов файловой системы Windows синхронными, но они еще не синхронизированы с Windows 10 версии 1903 - см. Этот комментарий на GitHub .


Существующие ответы смягчают проблему, так что она возникает реже, но они не устраняют основную причину , поэтому сбои все еще могут возникать.

Remove-Item -Recurseнеожиданно асинхронный , в конечном счете, потому что методы Windows API для удаления файлов и каталогов по своей сути асинхронны и Remove-Itemне учитывают этого.

Это периодически, непредсказуемо проявляется одним из двух способов:

  • Ваш случай: удаление непустого каталога само по себе может закончиться неудачей, если удаление подкаталога или файла в нем еще не завершено к тому времени, когда предпринята попытка удалить родительский каталог.

  • Реже: воссоздание удаленного каталога сразу после удаления может привести к сбою, поскольку удаление может быть еще не завершено к моменту повторного создания.

Проблема затрагивает не только PowerShell - х Remove-Item, но и cmd.exe«S rd /s, а также .NET - х[System.IO.Directory]::Delete() :

Начиная с Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 / cmd.exe10.0.17134.407 / .NET Framework 4.7.03056, .NET Core 2.1, ни Remove-Item, ни rd /s, ни [System.IO.Directory]::Delete()надежно не работают , поскольку они не учитывают асинхронный поведение функций удаления файлов и каталогов Windows API :

Для пользовательской функции PowerShell, которая обеспечивает надежно синхронный обходной путь , см. Этот ответ SO .

mklement
источник
При обработке файлов, где удаление обязательно:while($true) { if ( (Remove-Item [...] *>&1) -ne $null) { Start-Sleep 0.5 } else { break } }
Farway
3

Текущий ответ на самом деле не удалит каталог, только его дочерние элементы. Кроме того, у него будут проблемы с вложенными каталогами, поскольку он снова будет пытаться удалить каталог перед его содержимым. Я написал что-то, чтобы удалить файлы в правильном порядке, все равно будет та же самая проблема, хотя иногда каталог все еще будет рядом.

Итак, теперь я использую что-то, что будет перехватывать исключение, ждать и повторять (3 раза):

Сейчас я использую это:

function EmptyDirectory($directory = $(throw "Required parameter missing")) {

    if ((test-path $directory) -and -not (gi $directory | ? { $_.PSIsContainer })) {
        throw ("EmptyDirectory called on non-directory.");
    }

    $finished = $false;
    $attemptsLeft = 3;

    do {
        if (test-path $directory) {
            rm $directory -recurse -force
        }

        try {
            $null = mkdir $directory
            $finished = $true
        } 
        catch [System.IO.IOException] {
            Start-Sleep -Milliseconds 500
        }

        $attemptsLeft = $attemptsLeft - 1;
    } 
    while (-not $finished -and $attemptsLeft -gt 0)

    if (-not $finished) {
        throw ("Unable to clean and recreate directory " + $directory)
    }
}
fschwiet
источник
1
Это хорошо, но у меня все еще были проблемы с этим. Если команда mkdir выполняется до того, как система завершит выполнение команды rm, она может вызвать исключение System.UnauthorizedAccessException с FullyQualifiedErrorId ItemExistsUnauthorizedAccessError. То есть каталог еще не был удален ОС (на моем медленном жестком диске). Так что эту ошибку тоже нужно уловить. И это не прекращающаяся ошибка, поэтому для ErrorAction необходимо установить значение Stop. Я также поместил команду rm в блок try, только в случаях, когда при удалении возникают временные ошибки ввода-вывода.
Марк Лапьер
Я не могу поверить, что это даже нужно сделать. Блин, Powershell отстой!
jcollum
3

Для удаления каталога и его содержимого требуется два шага. Сначала удалите содержимое, затем саму папку. Используя обходной путь для неисправного рекурсивного элемента удаления, решение будет выглядеть так:

Get-ChildItem -Path "$folder\\*" -Recurse | Remove-Item -Force -Recurse
Remove-Item $folder

Таким образом, вы также можете удалить родительский каталог.

Карл Бейкер
источник
1
Это именно то, что сказал принятый ответ. У вас есть что добавить?
Майкл Хэмптон
1
Они указывают на то, что принятый ответ не удаляет сам каталог, поэтому требуется два шага.
Пол Джордж
2
Команда, к которой Remove-Itemвы подключаетесь, имеет ту же проблему, что и изначально. Он может наткнуться на элемент каталога, который не является пустым таким же образом.
Деян
@Dejan Этот каталог все еще не может быть пустым, если сработает первая строка этого кода, не так ли?
Ифеди Оконкво,
1
Хотя это может снизить вероятность сбоя, он все равно может потерпеть неудачу, учитывая, что Remove-Item -Recurseэто все еще происходит. Основная проблема все еще существует в Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 - см. Этот отчет об ошибке .
августа
3

Гоша. Много ответов. Я честно предпочитаю это всем им. Это супер просто, полно, читабельно и работает на любом компьютере с Windows. Он использует .NET (надежную) функцию рекурсивного удаления, и если по какой-то причине происходит сбой, он генерирует правильное исключение, которое может быть обработано блоком try / catch.

$fullPath = (Resolve-Path "directory\to\remove").ProviderPath
[IO.Directory]::Delete($fullPath, $true)

Обратите внимание, что Resolve-Pathстрока важна, потому что .NET не знает ваш текущий каталог при разрешении относительных путей к файлам. Это единственное, что я могу придумать.

Фил
источник
2

Это то, что у меня работает:

$Target = "c:\folder_to_delete"

Get-ChildItem -Path $Target -Recurse -force |
  Where-Object { -not ($_.psiscontainer) } |
   Remove-Item Force

Remove-Item -Recurse -Force $Target

Эта первая строка удаляет все файлы в дереве. Вторая удаляет все папки, включая верхнюю.

Джеймс Коупленд
источник
Хотя это может снизить вероятность сбоя, он все равно может потерпеть неудачу, учитывая, что Remove-Item -Recurseэто все еще происходит. Основная проблема все еще существует в Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 - см. Этот отчет об ошибке .
Мклемент
0

У меня была эта проблема с каталогом, который не удалялся. Я обнаружил, что одна из подпапок была повреждена, и когда я попытался переместить или переименовать этот дочерний каталог, я получил сообщение об ошибке, в котором говорилось, что чего-то не хватает. Я попытался использовать rm -Force и получил ту же ошибку, что и вы.

Для меня работало сжатие родительского каталога с помощью 7-zip с включенной опцией «Удалить файлы после сжатия». После того, как он был сжат, я смог удалить zip-файл.

RedDawnRising
источник