vba - que - macro para renombrar archivos
Cómo comprimir contenido de carpeta en 1 instrucción en Windows? (2)
Automatizar los objetos de Shell realmente no es un enfoque viable como ya has descubierto. Sin embargo, el Explorador Shell realmente no expone esta capacidad de ninguna otra manera, al menos no antes de Windows Vista y luego, de ninguna manera, se usa fácilmente desde los programas VB6 o las macros VBA.
Su mejor opción es una biblioteca ActiveX de un tercero, pero tenga cuidado con los hosts de VBA de 64 bits donde necesitará una versión de 64 bits de dicha biblioteca.
Otra opción es adquirir una copia posterior de zlibwapi.dll
y usar algún código de envoltura VB6 con ella. Esta es también una solución de 32 bits.
Eso es lo que Zipper y ZipWriter, Zipping de los programas de VB . Teniendo en cuenta sus requisitos (que por alguna razón incluye el miedo al control del temporizador) puede utilizar la clase ZipperSync síncrona. Ver la publicación # 4 allí. Ese código incluye un simple AddFolderToZipperSync
la lógica para agregar una carpeta en lugar de solo un archivo.
La desventaja de la clase sincrónica es que una gran operación de archivo congela la IU de su programa hasta que se complete. Si no lo desea, use Zipper UserControl en su lugar.
También puede tomar las ideas de eso para escribir su propia clase contenedora.
Estoy intentando comprimir una carpeta que contiene subcarpetas y elementos, usando el comando CopyHere
shell de Windows:
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
Actualización: tenga en cuenta que prefiere una solución nativa, esto es para una herramienta de Excel VBA distribuida, por lo que agrupar archivos de terceros no es ideal. Y, necesita compresión sincrónica .
Puedo agregar fácilmente una carpeta y su contenido al zip:
oShell.Namespace(sZipPath).CopyHere "C:/My Folder"
Entonces sabemos que CopyHere puede procesar múltiples objetos dentro de una carpeta en 1 declaración.
El problema es que el comando anterior coloca la carpeta contenedora en la raíz del archivo zip y su contenido dentro de ella. Pero no quiero la carpeta que contiene, solo su contenido.
El documento menciona un comodín (opción 128), pero cuando uso un comodín, recibo un error:
oShell.Namespace(sZipPath).CopyHere "C:/My Folder/*"
El nombre de archivo que especificó no es válido o demasiado largo.
Tal vez haya una forma de usar mi 1er comando arriba, y luego mover los elementos en el zip a la raíz del zip.
Sería aceptable recorrer cada elemento en la carpeta de origen, agregando uno a la vez al zip. Pero, debido a que CopyHere es asíncrono, cada CopyHere subsiguiente falla si el CopyHere anterior no está terminado. Ninguna de las correcciones funciona para este problema:
No se puede comparar el número de elementos en la carpeta de origen y el zip de destino, ya que si el archivo zip contiene una carpeta, eso solo cuenta como 1 artículo (los artículos que contiene no se cuentan. Https://stackoverflow.com/a/16603850/209942
Esperar un momento entre cada elemento funciona, pero un temporizador es inaceptable: es arbitrario. No puedo adivinar de antemano el tamaño o el tiempo de compresión de cada objeto.
Verifico si el zip está bloqueado para el acceso. Si bloqueo mi ciclo hasta que el archivo no esté bloqueado, sigo teniendo un error de acceso al archivo. 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
Actualización: VBA puede llamar comandos de shell de forma síncrona (en lugar de crear un objeto shell32.shell
en VBA), por lo que si CopyHere funciona en línea de comandos o PowerShell, esa podría ser la solución. Investigando ...
Solución:
Windows contiene otra utilidad de compresión nativa: CreateFromDirectory
en un indicador de PowerShell.
Esto requiere .Net 4.0 o posterior:
> 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)
Tenga en cuenta que es posible que deba proporcionar los nombres completos de las rutas-- el directorio activo no estaba implícito en mi máquina.
La compresión anterior es síncrona en el indicador de PowerShell, como lo solicita el OP.
El siguiente paso es ejecutar sincrónicamente desde VBA. La solución es el método .Run
en Windows Script Host Object Model . En VBA, establezca una referencia a eso, y haga lo siguiente, estableciendo el 3er parámetro del comando bWaitOnReturn
, bWaitOnReturn
en 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
Ahora llame a SynchronousShell
y pase todo el script de compresión.
Creo que la única forma de que este proceso funcione es si CreateFromDirectory
se ejecuta en la misma sesión que Add-Type
.
Entonces, debemos pasar todo el asunto como 1 cadena. Es decir, cargue los 4 comandos en una sola variable sCmd
, de modo que Add-Type
permanezca asociado con el CreateFromDirectory
posterior. En la sintaxis de PowerShell, puede separarlos con ;
https://thomas.vanhoutte.be/miniblog/execute-multiple-powershell-commands-on-one-line/
Además, querrá usar comillas simples en lugar de comillas dobles, de lo contrario las comillas dobles alrededor de las cadenas se eliminarán cuando los comandos en cadena margarita se pasen a powershell.exe
https://.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)"
Resuelto Lo anterior constituye la solución completa.
Información adicional : los comentarios adicionales a continuación son para circunstancias especiales:
Entornos .Net de varias versiones
Si un .NET <4.0 es el entorno activo en su SO, entonces System.IO.Compression
no existe-- Add-Type
comando Add-Type
fallará. Pero si su máquina tiene los ensamblados .NET 4 disponibles, aún puede hacer esto:
Cree un archivo por lotes que ejecute PowerShell con .Net 4. Consulte https://.com/a/31279372.
En su comando
Add-Type
anterior, use la ruta exacta al conjunto de compresión .Net 4. En mi 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"
Portabilidad
Resulta que, en mi máquina, puedo copiar el dll de compresión a cualquier carpeta y hacer llamadas a la copia y funciona:
Add-Type -Path "C:/MyFunnyFolder/System.IO.Compression.FileSystem.dll"
No sé lo que se requiere para garantizar que esto funcione; podría requerir que los archivos .Net 4.0 o 2.0 completos se ubiquen en sus directorios esperados. Supongo que el dll hace llamadas a otros ensamblados .Net. Quizás tuvimos suerte con esto :)
Límite de caracteres
Dependiendo de la profundidad de nuestros caminos y nombres de archivo, el conteo de caracteres puede ser una preocupación. PowerShell puede tener un límite de 260 caracteres (no estoy seguro).
https://support.microsoft.com/en-us/kb/830473
Como .Run
pasa por el shell de Windows, también debe preocuparse por el límite de caracteres, pero a 8k +, es un poco más amplio: https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553 https://.com/a/3205048/209942
El siguiente sitio ofrece un método de 24k + caracteres, pero aún no lo he estudiado: http://itproctology.blogspot.com/2013/06/handling-freakishly-long-strings-from.html
Como mínimo, ya que podemos poner el dll donde queramos, podemos ponerlo en una carpeta cerca de C: root - manteniendo nuestra cuenta de caracteres hacia abajo.
Actualización: esta publicación muestra cómo podemos poner todo en un script-file y llamarlo con ps4.cmd. Esta puede ser mi respuesta preferida:
./ps4.cmd GC ./zipper.ps1 | IEX
- dependiendo de la respuesta aquí .
Copia aquí:
Re la pregunta: ¿Puede el comando CopyHere
ejecutarse en la línea de comandos?
CopyHere
se puede ejecutar directamente en el indicador de PowerShell (código a continuación). Sin embargo, incluso en powershell, es asincrónico: el control vuelve al indicador de PowerShell antes de que el proceso finalice. Por lo tanto, no hay solución para el OP. Así es como se hace:
> $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())