locked
Events Not Firing Until Task Completed RRS feed

  • Question

  • Hi,

    I'm writing a script that compress some files and am using SevenZipSharp to provide the compression functionality. I've added the library, configured everything and written a script that will compress files into a target archive, which is great, but I've stumbled a little at the next step.

    I want to display the progress during archiving the files. I've found the events/callbacks built into SevenZipSharp and done what I think is required to hook into them. The code is included below.

    cls
    
    Add-Type -Path "<path>\SevenZipSharp.dll"
    [SevenZip.SevenZipCompressor]::SetLibraryPath("<path>\7za.dll")
    $sevenZipCompressor = New-Object -TypeName SevenZip.SevenZipCompressor
    $sevenZipCompressor.ArchiveFormat = [SevenZip.OutArchiveFormat]::SevenZip
    $sevenZipCompressor.CompressionLevel = [SevenZip.CompressionLevel]::None
    $sevenZipCompressor.CompressionMethod = [SevenZip.CompressionMethod]::Lzma2
    $sevenZipCompressor.CompressionMode = [SevenZip.CompressionMode]::Create
    $sevenZipCompressor.DirectoryStructure = $false # needed to compress files from local and networked drives together
    $sevenZipCompressor.EncryptHeaders = $true
    $sevenZipCompressor.FastCompression = $false # Gets or sets the value indicating whether to compress as fast as possible, without calling events.
    $sevenZipCompressor.VolumeSize = 10 * 1024 * 1024 # bytes, 10mb = 10 * 1024 * 1024
    
    $fileNames = New-Object Collections.Generic.List[String]
    $fileNames.Add("<path>")
    $fileNames.Add("<path>")
    $fileNames.Add("<path>")
    
    # added the following line to make sure a progress bar is displayed
    Write-Progress -Activity “Compressing” -Status “Status” -PercentComplete 0
    
    $compressingAction = {
        Write-Host ( "Event: Compressing: {0}%" -f $Event.SourceEventArgs.PercentDone )
        Write-Progress -Activity “Compressing” -Status “Status” -PercentComplete $Event.SourceEventArgs.PercentDone
    }
    $compressingEvent = Register-ObjectEvent -InputObject $sevenZipCompressor -EventName "Compressing" -Action $compressingAction
    
    $compressionFinishedAction = { Write-Host "Event: CompressionFinished" }
    $compressionFinishedEvent = Register-ObjectEvent -InputObject $sevenZipCompressor -EventName "CompressionFinished" -Action $compressionFinishedAction
    
    $fileCompressionFinishedAction = { Write-Host "Event: FileCompressionFinished" }
    $fileCompressionFinishedEvent = Register-ObjectEvent -InputObject $sevenZipCompressor -EventName "FileCompressionFinished" -Action $fileCompressionFinishedAction
    
    $fileCompressionStartedAction = { Write-Host "Event: FileCompressionStarted" }
    $fileCompressionStartedEvent = Register-ObjectEvent -InputObject $sevenZipCompressor -EventName "FileCompressionStarted" -Action $fileCompressionStartedAction
    
    $filesFoundAction = { Write-Host "Event: FilesFound" }
    $filesFoundEvent = Register-ObjectEvent -InputObject $sevenZipCompressor -EventName "FilesFound" -Action $filesFoundAction
    
    $sevenZipCompressor.CompressFilesEncrypted("<path>\test.7z", "password", $fileNames)
    
    Unregister-Event -SubscriptionId $compressingEvent.Id
    Unregister-Event -SubscriptionId $compressionFinishedEvent.Id
    Unregister-Event -SubscriptionId $fileCompressionFinishedEvent.Id
    Unregister-Event -SubscriptionId $fileCompressionStartedEvent.Id
    Unregister-Event -SubscriptionId $filesFoundEvent.Id

    Upon executing the script, I see the progress bar displayed at the top with the content "Compressing.", "Status." and no progress. After a minute or so of that being displayed, the progress bar disappears and the script completes with the following output:

    Event: FilesFound
    Event: FileCompressionStarted
    Event: Compressing: 29%
    Event: Compressing: 50%
    Event: Compressing: 59%
    Event: Compressing: 89%
    Event: Compressing: 100%
    Event: FileCompressionFinished
    Event: FileCompressionStarted
    Event: CompressionFinished
    Event: FileCompressionFinished

    The events are obviously firing, but it seems that PowerShell only picks them up once the compression task has been completed. Or maybe the SevenZipSharp code is written in a way that delays the events firing until it's completed, or maybe it only displays that behaviour when initiated via PowerShell... I have no idea what to look for to confirm whether I've dome something wrong/unexpected or if it would actually be expected in the script I've written above.

    With SevenZipSharp being wtitten in .Net, I would have thought the events would work hand-in hand with PowerShell, but either I'm missing something or it's harder than it initially seemed to get them to work together :-)

    I have PowerShell 5.1 build 18362 revision 145, I'm using the latest 7zip libraries from the 7zip homepage and SevenZipSharp from github (the squid-box version rather than tomap's)

    Anyone have any ideas how I can get this working as expected?

    Thanks!

    P.S. I did originally include images and links to explain things a little better, but am not allowed those until my account's been verified.

    Sunday, February 2, 2020 1:32 PM

All replies

  • This is normal for PowerShell. PS is a single thread and you cannot dun a process and trigger a progress bar at the same time.

    Try running PS as MTA as that can allow this with some asynch libraries.

    I would use Compress-Archive as it should work correctly with the progress bar and the progress should be automatic.

    Part of the issue is that the progress bar is created on the callback thread which exits after each event.  This makes the bar inaccessible on repeat calls but it may update after the main thread is freed after the compression is complete.


    \_(ツ)_/


    • Edited by jrv Sunday, February 2, 2020 1:55 PM
    Sunday, February 2, 2020 1:52 PM
  • Thanks for the response jrv.

    So everything runs in a single thread so although the events are raised they must be being queued to be processed after tha main task completes, that makes sense. I'll look into running PowerShell in multi-threaded mode and see what happens.

    I had looked into the Compress-Archive method but concluded that it wouldn't suit for my purpose. I think Compress-Archive is limited to working with zip files, but I don't necessarily need to compress into 7zip archives, so I've been trying to recall me reasons for avoiding it. Reading over the documentation again hasn't changed my opinion though, as it simply doesn't mention the features that I'd want to make use of, although I'm pretty sure that the zip file specification does support compressing into milti-volume archives. The other feature that I'd need is encrypting the resulting archive (both file names/paths and contents) with a password.

    The documentation does mention "The Compress-Archive cmdlet uses the Microsoft .NET API System.IO.Compression.ZipArchive to compress files. The maximum file size is 2 GB because there's a limitation of the underlying API.". This may be a reason it wouldn't be suitable for me, but only if it's referring to the size of the files to be compressed. As long as I can split the archive into volumes then I'll be using a volume size less than 2GB.

    Considering that I need to use encryption and compress into multi-volume archives, would Compress-Archive actually be suitable for me?

    Sunday, February 2, 2020 6:41 PM
  • Skip PowerShell and build it in C# as a library that can be called from PowerShell.

    You can also plug it into a WinForms form and that might get the events to cooperate if you set the action to bind to the progress control.


    \_(ツ)_/

    Sunday, February 2, 2020 7:22 PM
  • Where did you get 7za.dll?  It is not part of teh current 7-Zip product.

    Are you using the latest SevenZipSharp package?  


    \_(ツ)_/

    Sunday, February 2, 2020 7:52 PM
  • Skip PowerShell and build it in C# as a library that can be called from PowerShell.

    You can also plug it into a WinForms form and that might get the events to cooperate if you set the action to bind to the progress control.


    \_(ツ)_/

    It's a possibility, although I wouldn't know where to start with it, lol :)
    Sunday, February 2, 2020 7:57 PM
  • Where did you get 7za.dll?  It is not part of teh current 7-Zip product.

    Are you using the latest SevenZipSharp package?  


    \_(ツ)_/

    From the 7zip Downloads page @ 7-zip.org

    The containing archive is labelled 7-Zip Extra: standalone console version, 7z DLL, Plugin for Far Manager

    Sunday, February 2, 2020 7:59 PM
  • I recommend using the regular &Zip.DLL and also changing the event notification.

    Add-Type -Path 'c:\scripts\SevenZipSharp.dll'
    
    [SevenZip.SevenZipCompressor]::SetLibraryPath('c:\Program Files\7-Zip\7za.dll')
    $7Zip = [SevenZip.SevenZipCompressor]::New()
    $7Zip.ArchiveFormat = 'SevenZip'
    $7Zip.CompressionLevel = 'None'
    $7Zip.CompressionMethod = 'Lzma2'
    $7Zip.CompressionMode = 'Create'
    $7Zip.DirectoryStructure = $false
    $7Zip.EncryptHeaders = $true
    $7Zip.FastCompression = $false
    $7Zip.VolumeSize = 10Mb
    $7zip.EventSynchronization = 'AlwaysSynchronous' # OR  'AlwaysAsynchronous' OR 'Default
    
    $fileNames = '<path>','<path>','<path>'
    
    

    If you use an editor (ISE, VSCode, Sapien) the enums will be discovered and set using strings which is easier to read.  

    I started to build a form but have noticed that the type does not have a "Site" property so It may not work with a form.


    \_(ツ)_/

    Sunday, February 2, 2020 8:08 PM
  • I recommend using the regular &Zip.DLL and also changing the event notification.

    Add-Type -Path 'c:\scripts\SevenZipSharp.dll'
    
    [SevenZip.SevenZipCompressor]::SetLibraryPath('c:\Program Files\7-Zip\7za.dll')
    $7Zip = [SevenZip.SevenZipCompressor]::New()
    $7Zip.ArchiveFormat = 'SevenZip'
    $7Zip.CompressionLevel = 'None'
    $7Zip.CompressionMethod = 'Lzma2'
    $7Zip.CompressionMode = 'Create'
    $7Zip.DirectoryStructure = $false
    $7Zip.EncryptHeaders = $true
    $7Zip.FastCompression = $false
    $7Zip.VolumeSize = 10Mb
    $7zip.EventSynchronization = 'AlwaysSynchronous' # OR  'AlwaysAsynchronous' OR 'Default
    
    $fileNames = '<path>','<path>','<path>'
    

    If you use an editor (ISE, VSCode, Sapien) the enums will be discovered and set using strings which is easier to read.  

    I started to build a form but have noticed that the type does not have a "Site" property so It may not work with a form.


    \_(ツ)_/

    I think the difference in the code is just that the following line was added:

    $7zip.EventSynchronization = 'AlwaysSynchronous' # OR  'AlwaysAsynchronous' OR 'Default

    I've added this into my code and switched it to use the 7z.dll under program files, but it didn't make a difference. Was it intended to or just a suggestion?

    I have actually found al library named 7Zip4PowerShell, which is a C# wrapper for SevenZipSharp and provides all of the required functionality. I've managed to compress a file with the same settings as above in a single line of code, which is good. The only issue so far is that there's no parameter which accepts multiple source file paths. I can create a list and pipe that into the call. This works great when all source files are on the local C: drive or all on a network share, e.g. "\\shared\*", but if I pass in a mix of the two then I get an error stating

    The UNC path should be of the form \\server\share.

    Is that something that you happen to have come accross before?

    This seems like something I should be able to get around pretty easily, but I need to leave it for now so I can go and eat.

    Thanks for all of your input so far. :-D


    Sunday, February 2, 2020 8:37 PM
  • Can't help you.  You are trying to resolve too many dissimilar issues.  

    The original DLL can be set up with a form but will not work with PS scripts because of its design and threading model.


    \_(ツ)_/

    Sunday, February 2, 2020 9:02 PM
  • Can't help you.  You are trying to resolve too many dissimilar issues.  

    You're right - things did move pretty quickly after I'd asked the original question. They also didn't move down the path that I was expecting, hence the various semi-related questions.

    I think I'm at a point where I can do what I set out to do, so thanks for the help along the way. Explaining the situation and researching things you'd mentioned helped my resolve my issues (even if I did end up doing so in a roundabout way) and get further insights into how PowerShell works.

    Thanks for your time and input :-)

    I think this last issue regarding mixing of UNC and non-UNC paths is something I've encountered before but I just can't think of the cause, lol. Should be easy enough to sort that out though!

    Monday, February 3, 2020 6:10 PM