Plik zip PowerShell synchronicznie

10

W skrypcie PowerShell chcę spakować folder przed jego usunięciem. Uruchamiam następujące (nie pamiętam, gdzie znalazłem fragment):

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)

    }
}

Ten fragment kodu kompresuje folder, ale w sposób asynchroniczny. W rzeczywistości metoda CopyHere obiektów Shell.Application rozpoczyna kompresję i nie czeka na jej zakończenie. Następne instrukcje moich skryptów psują się (ponieważ plik zip nie został zakończony).

Jakieś sugestie? Jeśli to możliwe, chciałbym uniknąć dodawania plików wykonywalnych i pozostać przy czystych funkcjach Windows.

[edytuj] pełna zawartość mojego pliku PS1 minus rzeczywista nazwa bazy danych. Celem skryptu jest wykonanie kopii zapasowej zestawu bazy danych SQL, a następnie skompresowanie kopii zapasowych w jednym pakiecie w folderze o bieżącej dacie:

$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.
źródło
Żeby sprawdzić, czy zadziała, jeśli był zsynchronizowany: jeśli dodasz następujący kod po swoim foreach, czy będzie działał zgodnie z oczekiwaniami? Write-Host "Press any key to continue ..." Linia 1 : Linia 2:$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
Kerry
I podczas testu oczywiście zakładam, że nie będziesz „naciskał żadnego klawisza, aby kontynuować”, dopóki ręcznie nie potwierdzisz, że proces zip został zakończony.
Kerry
@Kerry: tak naprawdę działa zgodnie z oczekiwaniami, gdy dodam test ręczny
Steve B

Odpowiedzi:

5

W końcu znalazłem czysty sposób, bawiąc się właściwościami obiektów com. W szczególności następujący fragment kodu może przetestować, czy plik znajduje się w pliku 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
    }
}

Pełny skrypt jest następujący:

$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.
źródło
jak możesz go używać na VB?
MacGyver,
@Leandro: masz na myśli skrypt VB? Ponieważ zarówno PowerShell, jak i VBScript są językiem skryptowym, zdolnym do pracy z obiektami COM, powinieneś być w stanie odtworzyć tutaj zachowanie bez większych trudności
Steve B
1

Ponieważ działało dobrze po ręcznym wstrzymaniu go, oto tymczasowy hack, którego możesz użyć, dopóki nie zostanie znalezione „właściwe” rozwiązanie. Ogólnie rzecz biorąc, używanie „opóźnień” i „liczników czasu” w ten sposób NIE jest tym, co zrobiłbyś dla rzeczy o kluczowym znaczeniu. To powiedziawszy, dopóki nie znajdziesz lepszej odpowiedzi, możesz to zrobić i sprawdzić, czy to działa:

  • Wykonaj proces ręcznie kilka razy i CZAS, ile czasu zajmuje zwykle skompresowanie. Jeśli rozmiar bazy danych jest generalnie taki sam każdego dnia, czas potrzebny do ukończenia prawdopodobnie będzie średnio mniej więcej w tym samym czasie.

  • Załóżmy, że w testach ręcznych masz średnio 60 sekund. Bądź konserwatywny i pomnóż go przez około 4, ponieważ prawdopodobnie nie zajmie to 4 razy dłużej niż zwykle w „normalne” dni. Masz teraz 240 sekund (60 sekund średnio 4).

  • Na razie więc zamiast kodu „naciśnij dowolny klawisz, aby kontynuować”, zamień go na OPÓŹNIENIE w kodzie, aby skrypt zawiesił się na chwilę, aby poczekać, aż zip się skończy. Wymaga to drobnych poprawek i zgadywania terminów i nie jest dobrym podejściem. Ale w mgnieniu oka ...

  • W każdym razie, jeśli chcesz spróbować, zmień kod na:

Jeśli używasz PowerShell V1:

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

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

Jeśli używasz programu PowerShell V2, użyj zamiast tego polecenia cmdlet Sleep:

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

Start-Sleep -Second 240

Aby zadzierać z czasami w wersji 1, używa milisekund. (Więc 10 sekund = 10000)

Aby zadzierać z czasami w V2, używa sekund. (240 = 240 sekund)

Nigdy nie użyłbym tego w produkcji, ale jeśli nie jest to tak wielka sprawa, a okaże się, że działa dobrze w 99% przypadków, może być wystarczająco dobry.

Kerry
źródło
W końcu znalazłem rozwiązanie. W każdym razie doceniam twoją pomoc. Dziękuję
Steve B