Как сжать содержимое папки в 1 заявлении в Windows?

Я пытаюсь закрепить папку, содержащую вложенные папки и элементы, используя команду оболочки Windows CopyHere:

https://msdn.microsoft.com/en-us/library/windows/desktop/bb787866(v=vs.85).aspx https://msdn.microsoft.com/en-us/library/windows/desktop/ms723207(v=vs.85).aspx

Обновление: Примечание. Предпочитайте собственное решение - это для распределенного инструмента Excel VBA, поэтому объединение сторонних файлов не является идеальным. И, требуется синхронное сжатие.

Я могу легко добавить папку и ее содержимое в zip:

oShell.Namespace(sZipPath).CopyHere "C:\My Folder"

Итак, мы знаем, что CopyHere может обрабатывать несколько объектов внутри папки в 1 инструкции.

Проблема заключается в том, что вышеприведенная команда помещает содержащую папку в корень zip, а содержимое внутри нее. Но я не хочу содержать папку - просто ее содержимое.

В документе упоминается шаблон (опция 128), но когда я использую подстановочный знак, я получаю сообщение об ошибке:

oShell.Namespace(sZipPath).CopyHere "C:\My Folder\*"

Указанное имя файла недействительно или слишком длинное.

Возможно, есть способ использовать мою первую команду выше, а затем переместить элементы в zip в корень zip?

Было бы приемлемо прокручивать каждый элемент в исходной папке, добавляя по одному к zip файлу. Но, поскольку CopyHere является асинхронным, каждый последующий экземпляр CopyHere завершается с ошибкой, если предыдущее CopyHere не завершено. Ни одна из исправлений не работает для этой проблемы:

  • Сравнение количества элементов в папке source-folder и destination-zip не выполняется, потому что если zip содержит папку, то считается, что только один элемент (элементы, которые он содержит, не учитываются. qaru.site/info/30054/...

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

  • Проверка того, что zip заблокирован для доступа, не удалось мне. Если я заблокирую цикл до тех пор, пока файл не будет заблокирован, я все равно получаю ошибку доступа к файлу. qaru.site/info/30055/...


Function FileIsOpen(sPathname As String) As Boolean ' true if file is open
    Dim lFileNum As Long
    lFileNum = FreeFile
    Dim lErr As Long
    On Error Resume Next
    Open sPathname For Binary Access Read Write Lock Read Write As #lFileNum
    lErr = Err
    Close #lFileNum
    On Error GoTo 0
    FileIsOpen = (lErr <> 0)
End Function

Обновление: VBA может синхронно вызывать команды оболочки (вместо создания объекта shell32.shell в VBA), поэтому, если CopyHere работает в командной строке или PowerShell, это может быть решением. Исследуя...

Ответ 1

Решение:

Windows содержит еще одну встроенную утилиту сжатия: CreateFromDirectory в командной строке PowerShell.

https://msdn.microsoft.com/en-us/library/system.io.compression.zipfile.createfromdirectory(v=vs.110).aspx

https://blogs.technet.microsoft.com/heyscriptingguy/2015/03/09/use-powershell-to-create-zip-archive-of-folder/

Для этого требуется .Net 4.0 или новее:

> Add-Type -AssemblyName System.IO.Compression
> $src = "C:\Users\v1453957\documents\Experiment\rezip\aFolder"
> $zip="C:\Users\v1453957\Documents\Experiment\rezip\my.zip"
> [io.compression.zipfile]::CreateFromDirectory($src, $zip)

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

Вышеуказанное сжатие синхронно в командной строке PowerShell, как и запросы OP.


Следующий шаг выполняется синхронно с VBA. Решение есть метод .Run в Windows Script Модель объекта хоста. В VBA установите ссылку на это и выполните следующие действия, установив третий параметр команды .Run, bWaitOnReturn - True:

Function SynchronousShell(sCmd As String)As Long Dim oWSH As New IWshRuntimeLibrary.WshShell ShellSynch = oWSH.Run(sCmd, 3, True) Set oWSH = Nothing End Function

Теперь вызовите SynchronousShell и передайте ему все сжатие script.

Я считаю, что единственный способ для этого процесса - работать, если CreateFromDirectory выполняется в том же сеансе, что и Add-Type.

Итак, мы должны передать все это как одну строку. То есть загрузите все 4 команды в одну переменную sCmd, так что Add-Type останется связанной с последующим CreateFromDirectory. В синтаксисе PowerShell вы можете разделить их на ;

https://thomas.vanhoutte.be/miniblog/execute-multiple-powershell-commands-on-one-line/

Кроме того, вам нужно использовать одиночные кавычки вместо двойных кавычек, иначе двойные кавычки вокруг строк удаляются, когда команды с последовательным соединением передаются в файл powershell.exe

fooobar.com/questions/30032/...

sCmd = "ps4 Add-Type -AssemblyName System.IO.Compression; $src = 'C:\Users\v1453957\documents\Experiment\rezip\aFolder'; $zip='C:\Users\v1453957\Documents\Experiment\rezip\my.zip'; [io.compression.zipfile]::CreateFromDirectory($src, $zip)"

Решено. Вышеупомянутое представляет собой полное решение.


Дополнительная информация: Дополнительные комментарии ниже приведены для особых обстоятельств:

Многопользовательские среды .Net

Если .NET < 4.0 - это активная среда вашей ОС, тогда System.IO.Compression не существует - команда Add-Type завершится с ошибкой. Но если на вашей машине есть сборки .NET 4, вы все равно можете сделать это:

  • Создайте пакетный файл, который запускает PowerShell с .Net 4. См. fooobar.com/questions/30024/...

  • В приведенной выше команде Add-Type используйте точный путь к сборке .Net 4 Compression. На моем Win Server 2008:

Add-Type -Path "C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.IO.Compression.FileSystem\v4.0_4.0.0.0__b77a5c561934e089\System.IO.Compression.FileSystem.dll"

Портативность

Оказывается, на моей машине я могу скопировать dll компрессии в любую папку и сделать вызовы к копии, и она работает:

Add-Type -Path "C:\MyFunnyFolder\System.IO.Compression.FileSystem.dll"

Я не знаю, что необходимо для обеспечения этого - может потребоваться, чтобы полные .Net 4.0 или 2.0 файлы были расположены в ожидаемых каталогах. Я предполагаю, что dll вызывает вызовы на другие сборки .Net. Может быть, нам просто повезло с этим:)

Лимит символов

В зависимости от глубины наших путей и имен файлов, количество символов может быть проблемой. PowerShell может иметь ограничение в 260 символов (не уверен).

https://support.microsoft.com/en-us/kb/830473

https://social.technet.microsoft.com/Forums/windowsserver/en-US/f895d766-5ffb-483f-97bc-19ac446da9f8/powershell-command-size-limit?forum=winserverpowershell

Так как .Run проходит через оболочку Windows, вам также нужно беспокоиться об этом лимите, но в 8k + это немного просто: https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553 fooobar.com/questions/30045/...

Сайт ниже предлагает метод 24k + character, но я еще не изучил его: http://itproctology.blogspot.com/2013/06/handling-freakishly-long-strings-from.html

Как минимум, поскольку мы можем поместить dll везде, где захотим, мы можем поместить его в папку рядом с C: root - сохраняя наш счетчик символов.

Обновление: Этот пост показывает, как мы можем поместить все это в файл script и вызвать его с помощью ps4. CMD. Это может стать моим предпочтительным ответом:

.\ps4.cmd GC .\zipper.ps1 | IEX

- в зависимости от ответа здесь.


CopyHere:

Возьмем вопрос: может ли команда CopyHere выполнить команду в командной строке?

CopyHere может выполняться непосредственно в приглашении PowerShell (код ниже). Однако даже в powershell он асинхронный - управление возвращается к запросу PowerShell до завершения процесса. Поэтому никакого решения для ОП нет. Вот как это делается:

> $shellapp=new-object -com shell.application
> $zippath="test.zip"
> $zipobj=$shellapp.namespace((Get-Location).Path + "\$zippath")
> $srcpath="src"
> $srcobj=$shellapp.namespace((Get-Location).Path + "\$srcpath")
> $zipobj.Copyhere($srcobj.items())

Ответ 2

Автоматизация объектов Shell действительно не является жизнеспособным подходом, как вы уже обнаружили. Обозреватель Explorer действительно не раскрывает эту возможность каким-либо другим способом, хотя, по крайней мере, не до Windows Vista, а затем не в любой форме, легко используемой из программ VB6 или макросов VBA.

Лучше всего использовать стороннюю библиотеку ActiveX, но будьте осторожны с 64-битными узлами VBA, где вам понадобится 64-разрядная версия такой библиотеки.

Другой вариант - получить более позднюю копию zlibwapi.dll и использовать с ней некоторый код оболочки VB6. Это также 32-разрядное решение.

Что то Zipper и ZipWriter, Zipping из VB-программ. Учитывая ваши требования (которые почему-то включают в себя страх управления таймером), вы можете использовать синхронный класс ZipperSync. См. Сообщение №4. Этот код включает в себя простой AddFolderToZipperSync набор логики для добавления папки вместо одного файла.

Недостатком синхронного класса является то, что большая архивная операция зависает с вашим программным интерфейсом до его завершения. Если вы не хотите, чтобы вместо этого использовался Zipper UserControl.

Вы также можете взять идеи из этого, чтобы написать свой собственный класс-оболочку.