如何在 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 个修复程序适用于此问题:
比较 source-folder 和 destination-zip 中的项目数失败,因为如果 zip 包含一个文件夹,那只算作 1 个项目(它包含的项目不计算在内。
在每个项目之间等待一段时间是可行的,但计时器是不可接受的:它是任意的。我无法提前猜测每个对象的大小或压缩时间。
我检查 zip 是否已锁定访问失败。如果我在文件未锁定之前阻塞我的循环,我仍然会收到文件访问错误。
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 提示符下。
这需要 .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
,并将整个压缩脚本传递给它。
我认为此过程唯一可行的方法是 CreateFromDirectory
与 Add-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
由于 .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())
我正在尝试压缩包含子文件夹和项目的文件夹,使用 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 个修复程序适用于此问题:
比较 source-folder 和 destination-zip 中的项目数失败,因为如果 zip 包含一个文件夹,那只算作 1 个项目(它包含的项目不计算在内。
在每个项目之间等待一段时间是可行的,但计时器是不可接受的:它是任意的。我无法提前猜测每个对象的大小或压缩时间。
我检查 zip 是否已锁定访问失败。如果我在文件未锁定之前阻塞我的循环,我仍然会收到文件访问错误。
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 提示符下。
这需要 .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
,并将整个压缩脚本传递给它。
我认为此过程唯一可行的方法是 CreateFromDirectory
与 Add-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
由于 .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())