如何在 Windows 的 1 条语句中压缩文件夹内容?

How To Compress Folder-Contents in 1 Statement on Windows?

我正在尝试压缩包含子文件夹和项目的文件夹,使用 Windows shell 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 工具,因此捆绑第 3 方文件不是理想的。而且,需要同步压缩。

我可以轻松地将文件夹 其内容添加到 zip:

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

所以我们知道 CopyHere 可以在 1 条语句中处理文件夹内的多个对象。

问题是,上面的命令将 containing-folder 放在 zip 的根目录下,里面的内容。但是,我不想要包含文件夹——只想要它的内容。

文档提到了通配符(选项 128),但是当我使用通配符时,出现错误:

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

The file name you specified is not valid or too long.

也许有一种方法可以使用我上面的第一个命令,然后将 zip 中的项目移动到 zip 的根目录?

循环遍历源文件夹中的每个项目,一次添加一个到 zip 是可以接受的。但是,因为 CopyHere 是异步的,如果前一个 CopyHere 没有完成,每个后续的 CopyHere 都会失败。 None 个修复程序适用于此问题:


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 上工作,那可能是解决方案。正在调查...

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

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

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

这就是 Zipper & ZipWriter, Zipping from VB programs 所做的。考虑到您的要求(出于某种原因包括对 Timer 控件的恐惧),您可以使用同步 ZipperSync Class。请参阅 post #4。该代码包含一个简单的 AddFolderToZipperSync 捆绑逻辑以添加文件夹而不是仅添加一个文件。

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

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

解法:

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 开始同步执行。解决方法是Windows Script Host Object Model中的.Run方法。在 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,并将整个压缩脚本传递给它。

我认为此过程唯一可行的方法是 CreateFromDirectoryAdd-Type 在同一会话中执行。

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

分隔它们

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

此外,您需要使用单引号而不是双引号,否则当菊花链命令传递给 powershell.exe

时,字符串周围的双引号会被删除

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 是您 OS 上的活动环境,则 System.IO.Compression 不存在 - Add-Type 命令将失败。但是如果你的机器有可用的 .NET 4 程序集,你仍然可以这样做:

  • 创建一个使用 .Net 4 运行 PowerShell 的批处理文件。参见

  • 在上面的 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 shell,您还必须担心字符限制,但在 8k+ 时,它有点宽敞: https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553

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

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

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

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

-- 取决于答案 here


复制到这里:

回复问题:CopyHere 命令可以在命令行上执行吗?

CopyHere 可以直接在PowerShell提示符下执行(下面的代码)。然而,即使在 powershell 中它也是异步的——控制 returns 到 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())