1

CopyHere我正在尝试使用 Windows shell命令压缩包含子文件夹和项目的文件夹:

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 工具的,因此捆绑第 3 方文件并不理想。并且,需要同步压缩。

我可以轻松地将文件夹及其内容添加到 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 包含一个文件夹,则仅计为 1 个项目(其中包含的项目不计算在内。https://stackoverflow.com/a/16603850/209942

  • 在每个项目之间等待一段时间是可行的,但计时器是不可接受的:它是任意的。我无法提前猜测每个对象的大小或压缩时间。

  • 检查 zip 是否被锁定以供我访问失败。如果我阻塞循环直到文件未被锁定,我仍然会收到文件访问错误。https://stackoverflow.com/a/6666663/209942


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 可以同步调用 shell 命令(而不是在 VBA 中创建shell32.shell对象),所以如果 CopyHere 在命令行或 PowerShell 上工作,那可能是解决方案。正在调查...

4

2 回答 2

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)

请注意,您可能必须提供完整的路径名——我的机器上没有隐含活动目录。

如 OP 请求,上述压缩在 PowerShell 提示符下是同步的。


下一步是从 VBA 同步执行。解决方案是Windows Script Host Object Model.Run中的方法。在 VBA 中,设置对它的引用,然后执行以下操作,将命令的第三个参数设置为:.RunbWaitOnReturnTrue

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,并将整个压缩脚本传递给它。

我相信这个过程起作用的唯一方法是 ifCreateFromDirectory在与Add-Type.

因此,我们必须将整个内容作为 1 个字符串传递。也就是说,将所有 4 个命令加载到一个sCmd变量中,以便Add-Type与后续的CreateFromDirectory. 在 PowerShell 语法中,您可以将它们分开;

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

此外,您需要使用单引号而不是双引号,否则当菊花链命令传递给 powershell.exe 时,字符串周围的双引号将被删除

https://stackoverflow.com/a/39801732/209942

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 程序集,您仍然可以这样做:

  • 创建一个使用 .Net 4 运行 PowerShell 的批处理文件。请参阅https://stackoverflow.com/a/31279372

  • 在上面的Add-Type命令中,使用 .Net 4 压缩程序集的确切路径。在我的 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 shell,您还必须担心字符限制,但在 8k+ 时,它有点宽敞: https ://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553 https: //stackoverflow.com/a/3205048/209942

下面的网站提供了 24k+ 字符的方法,但我还没有研究过:http: //itproctology.blogspot.com/2013/06/handling-freakishly-long-strings-from.html

至少,因为我们可以将 dll 放在我们喜欢的任何地方,我们可以将它放在 C:root 附近的文件夹中——保持我们的字符数减少。

更新: 这篇文章展示了我们如何将整个内容放在一个脚本文件中,并使用 ps4.cmd 调用它。这可能会成为我的首选答案:

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

- 取决于这里的答案。


复制这里:

关于问题:命令可以CopyHere在命令行上执行吗?

CopyHere 可以直接在 PowerShell 提示符下执行(代码如下)。然而,即使在 powershell 中它也是异步的——控制在进程完成之前返回到 PowerShell 提示符。因此,OP没有解决方案。这是如何完成的:

> $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())
于 2016-09-30T22:11:06.670 回答
1

正如您已经发现的那样,自动化 Shell 对象确实不是一种可行的方法。不过,Explorer Shell 并没有真正以任何其他方式公开此功能,至少在 Windows Vista 之前没有,而且在 VB6 程序或 VBA 宏中也没有以任何容易使用的方式。

最好的选择是第 3 方 ActiveX 库,但要小心 64 位 VBA 主机,您需要此类库的 64 位版本。

另一种选择是获取更高版本的副本zlibwapi.dll并使用一些 VB6 包装器代码。这也是一个 32 位的解决方案。

这就是Zipper 和 ZipWriter,来自 VB 程序的 Zipping所做的。考虑到您的要求(由于某种原因包括对 Timer 控件的恐惧),您可以使用同步 ZipperSync 类。见那里的帖子#4。该代码包括一个简单的AddFolderToZipperSync捆绑逻辑来添加一个文件夹,而不仅仅是一个文件。

同步类的缺点是大型归档操作会冻结您的程序 UI,直到它完成。如果您不希望这样,请改用 Zipper UserControl。

您也可以从中汲取灵感来编写自己的包装类。

于 2016-09-30T07:58:21.180 回答