Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

In a PowerShell script, I want to zip a folder before deleting the folder. I run the following (I don't remember where I found the snippet):

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)

    }
}

This snippet actually compress the folder, but in an asynchronous way. In fact, the CopyHere method of the Shell.Application objects starts the compression and does not wait for its completion. The next statements of my scripts then mess up (as the zip file process is not completed).

Any suggestions? If possible I'd like to avoid adding any executable files and stay on pure Windows features.

[edit] full content of my PS1 file minus the actual name of the DB. The goal of the script is to backup a set of SQL db, then zip the backups in a single package in a folder named with the current date:

$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
share|improve this question
    
Just to see if it woudl work if it was synchronous: If you add the following code after your foreach does it work as expected? Line1: Write-Host "Press any key to continue ..." Line2: $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") –  Kerry May 30 '11 at 23:23
    
And during the test I am obviously assuming that you will not "press any key to continue" until you manually confirm that the zip process has completed. –  Kerry May 31 '11 at 3:53
    
@Kerry: it actually works as expected when I add your manual test –  Steve B May 31 '11 at 8:28
add comment

2 Answers

up vote 3 down vote accepted

I finally found a clean way, playing with the properties of the com objects. Especially, the following snippet can test if the file is present in the zip file :

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
    }
}

The full script is the following :

$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
share|improve this answer
    
how can you use it on VB? –  Leandro Nov 6 '12 at 21:31
    
@Leandro: do you mean VB script? As both PowerShell and VBScript are scripting language, able to work with COM objects, you should be able to replicate the behavior here without much difficulties –  Steve B Nov 7 '12 at 8:28
add comment

Because it worked fine when you manually paused it, here is a temporary hack that you may be able to use until the "right" solution is found. Generally using "delays" and "timers" like this is NOT what you would do for mission critical things. That said, until a better answer is found, you can do this and see if it works:

  • Do the process manually a few times and TIME how long in seconds it usually takes the zip process to complete. If the database size is generally the same every day then the time it takes to finish will probably average around the same time.

  • Let's say you get an average of 60 seconds in your manual tests. Be conservative and multiply it by 4 or so as it will likely not take 4 times longer than usual on "normal" days. So now you have 240 seconds (60 second average times 4).

  • So for now, instead of having the "press any key to continue" code in there, replace that with a DELAY in the code to just have the script hang out for a bit to wait for the zip to finish. This requires some tweaking and guessing on timings and is not a good approach. But in a pinch...

  • Anyway, if you want to try it, change code to:

If using PowerShell V1:

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

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

If using PowerShell V2, use the Sleep cmdlet instead:

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

Start-Sleep -Second 240

To mess with times in V1 it uses milliseconds. (So 10 seconds = 10000)

To mess with times in V2 it uses seconds. (240 = 240 seconds)

I would never use this in production but if it is not that big of a deal, and it proves to work just fine 99% of the time, it might be good enough.

share|improve this answer
    
I finally found the solution. I appreciate your help anyways. Thx –  Steve B May 31 '11 at 12:14
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.