Archivo zip de PowerShell sincrónicamente

10

En un script de PowerShell, quiero comprimir una carpeta antes de eliminarla. Ejecuto lo siguiente (no recuerdo dónde encontré el fragmento):

function Compress-ToZip
{
    param([string]$zipfilename)

    if(-not (test-path($zipfilename)))
    {
        set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
        (Get-ChildItem $zipfilename).IsReadOnly = $false   
    }

    $shellApplication = new-object -com shell.application
    $zipPackage = $shellApplication.NameSpace($zipfilename)

    foreach($file in $input)
    {
         $zipPackage.CopyHere($file.FullName)

    }
}

Este fragmento realmente comprime la carpeta, pero de forma asincrónica. De hecho, el método CopyHere de los objetos Shell.Application inicia la compresión y no espera su finalización. Las siguientes declaraciones de mis scripts luego se confunden (ya que el proceso del archivo zip no se completa).

¿Alguna sugerencia? Si es posible, me gustaría evitar agregar archivos ejecutables y mantener las características puras de Windows.

[editar] contenido completo de mi archivo PS1 menos el nombre real de la base de datos. El objetivo del script es hacer una copia de seguridad de un conjunto de SQL db, luego comprimir las copias de seguridad en un solo paquete en una carpeta con la fecha actual:

$VerbosePreferenceBak = $VerbosePreference
$VerbosePreference = "Continue"

add-PSSnapin SqlServerCmdletSnapin100

function BackupDB([string] $dbName, [string] $outDir)
{
    Write-Host "Backup de la base :  $dbName"
    $script = "BACKUP DATABASE $dbName TO DISK = '$outDir\$dbName.bak' WITH FORMAT, COPY_ONLY;"

    Invoke-Sqlcmd -Query "$script" -ServerInstance "." -QueryTimeOut 600
    Write-Host "Ok !"
}

function Compress-ToZip
{
    param([string]$zipfilename)

Write-Host "Compression du dossier"

    if(-not (test-path($zipfilename)))
    {
        set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
        (Get-ChildItem $zipfilename).IsReadOnly = $false   
    }

    $shellApplication = new-object -com shell.application
    $zipPackage = $shellApplication.NameSpace($zipfilename)

    foreach($file in $input)
    {
         $zipPackage.CopyHere($file.FullName)       
    }
Write-Host "Press any key to continue ..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

}


$targetDir = "E:\Backup SQL"
$date = Get-Date -format "yyyy-MM-dd"
$newDir = New-Item -ItemType Directory "$targetDir\$date\sql" -Force

BackupDB  "database 1" "$newDir"
BackupDB  "database 2" "$newDir"
BackupDB  "database 3" "$newDir"

Get-Item $newDir | Compress-ToZip "$targetDir\$date\sql_$date.zip"


Write-Host "."
remove-item $newDir -Force -Confirm:$false -Recurse

$VerbosePreference = $VerbosePreferenceBak
Steve B
fuente
Solo para ver si funcionaría si fuera síncrono: si agrega el siguiente código después de su foreach, ¿funciona como se esperaba? Línea1: Write-Host "Press any key to continue ..." Línea2:$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
Kerry
Y durante la prueba, obviamente, supongo que no "presionará ninguna tecla para continuar" hasta que confirme manualmente que el proceso zip se ha completado.
Kerry
@ Kerry: en realidad funciona como se esperaba cuando agrego su prueba manual
Steve B

Respuestas:

5

Finalmente encontré una manera limpia, jugando con las propiedades de los objetos com. Especialmente, el siguiente fragmento puede probar si el archivo está presente en el archivo zip:

foreach($file in $input)
{
    $zipPackage.CopyHere($file.FullName)    
    $size = $zipPackage.Items().Item($file.Name).Size
    while($zipPackage.Items().Item($file.Name) -Eq $null)
    {
        start-sleep -seconds 1
        write-host "." -nonewline
    }
}

El guión completo es el siguiente:

$VerbosePreferenceBak = $VerbosePreference
$VerbosePreference = "Continue"

add-PSSnapin SqlServerCmdletSnapin100

function BackupDB([string] $dbName, [string] $outDir) {
    Write-Host "Backup de la base :  $dbName"
    $script = "BACKUP DATABASE $dbName TO DISK = '$outDir\$dbName.bak' WITH FORMAT, COPY_ONLY;"

    Invoke-Sqlcmd -Query "$script" -ServerInstance "." -QueryTimeOut 600
    Write-Host "Ok !"
}

function Compress-ToZip {
    param([string]$zipfilename)

    Write-Host "Compression du dossier"

    if(-not (test-path($zipfilename)))  {
        set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
        (Get-ChildItem $zipfilename).IsReadOnly = $false   
    }

    $shellApplication = new-object -com shell.application
    $zipPackage = $shellApplication.NameSpace($zipfilename)

    foreach($file in $input) {
        $zipPackage.CopyHere($file.FullName)    
        $size = $zipPackage.Items().Item($file.Name).Size
        while($zipPackage.Items().Item($file.Name) -Eq $null)
        {
            start-sleep -seconds 1
            write-host "." -nonewline
        }
        write-host "."
    }      
}


$targetDir = "E:\Backup SQL"
$date = Get-Date -format "yyyy-MM-dd"
$newDir = New-Item -ItemType Directory "$targetDir\$date\sql" -Force

BackupDB  "DB1" "$newDir"
BackupDB  "DB2" "$newDir"
BackupDB  "DB3" "$newDir"
BackupDB  "DB4" "$newDir"

Get-ChildItem "$newDir" | Compress-ToZip "$targetDir\$date\sql_$date.zip"

remove-item $newDir -Force -Confirm:$false -Recurse

$VerbosePreference = $VerbosePreferenceBak
Steve B
fuente
¿Cómo puedes usarlo en VB?
MacGyver
@Leandro: ¿te refieres al script VB? Como PowerShell y VBScript son lenguaje de script, capaz de trabajar con objetos COM, debería poder replicar el comportamiento aquí sin muchas dificultades
Steve B
1

Debido a que funcionó bien cuando lo detuvo manualmente, aquí hay un truco temporal que puede usar hasta que encuentre la solución "correcta". En general, el uso de "retrasos" y "temporizadores" como este NO es lo que haría para las cosas de misión crítica. Dicho esto, hasta que se encuentre una mejor respuesta, puede hacer esto y ver si funciona:

  • Realice el proceso manualmente varias veces y TIEMPO cuánto tiempo en segundos generalmente tarda en completarse el proceso zip. Si el tamaño de la base de datos es generalmente el mismo todos los días, entonces el tiempo que demora en terminar probablemente sea el promedio de la misma hora.

  • Digamos que obtiene un promedio de 60 segundos en sus pruebas manuales. Sea conservador y multiplíquelo por 4 más o menos, ya que es probable que no tome 4 veces más de lo habitual en días "normales". Entonces ahora tienes 240 segundos (60 segundos promedio 4).

  • Entonces, por ahora, en lugar de tener el código "presione cualquier tecla para continuar" allí, reemplácelo con un RETARDO en el código para que el script se cuelgue un poco y espere a que termine el zip. Esto requiere algunos ajustes y conjeturas sobre los tiempos y no es un buen enfoque. Pero en un apuro ...

  • De todos modos, si quieres probarlo, cambia el código a:

Si usa PowerShell V1:

foreach($file in $input)
{
  $zipPackage.CopyHere($file.FullName)       
}

[System.Threading.Thread]::Sleep(240000)

Si usa PowerShell V2, use el cmdlet Sleep en su lugar:

foreach($file in $input)
{
   $zipPackage.CopyHere($file.FullName)       
}

Start-Sleep -Second 240

Para meterse con los tiempos en V1, usa milisegundos. (Entonces 10 segundos = 10000)

Para meterse con los tiempos en V2, usa segundos. (240 = 240 segundos)

Nunca usaría esto en producción, pero si no es un gran problema y demuestra que funciona bien el 99% del tiempo, podría ser lo suficientemente bueno.

Kerry
fuente
Finalmente encontré la solución. Agradezco tu ayuda de todos modos. Thx
Steve B