Search code examples
multithreadingpowershelljobscompress-archive

Using Compress-Archive to compress multiple folders multithreadded with Powershell


Everyday I need create archives of the following

C:.
└───1.Jan
    ├───20000
    │       img1.bmp
    │       img2.bmp
    │       img3.bmp
    │
    ├───20001
    │       img1.bmp
    │       img2.bmp
    │       img3.bmp
    │
    ├───20002
    │       img1.bmp
    │       img2.bmp
    │       img3.bmp
    │
    ├───20003
    │       img1.bmp
    │       img2.bmp
    │       img3.bmp
    │
    ├───20004
    │       img1.bmp
    │       img2.bmp
    │       img3.bmp
    │
    ├───20005
    │        img1.bmp
    │        img2.bmp
    │        img3.bmp
    │
    └───Entered

I currently have the script working with creating the zip files one at a time, However I can sometimes have 200+ folders to zip and they are different sizes so I would like to get this working multithreadded.

function Zip-Folders([String] $FolderPath) {
if ($FolderPath -eq '') {
    return
} 
else {
    $FolderNames = Get-ChildItem -Path $FolderPath -Name -Directory -Exclude Enter*
        foreach ($i in $FolderNames) {
            $TempPath = "$FolderPath\$i"
            $TempFileName = "$i Photos"
            if (-Not(Get-ChildItem -Path $TempPath | Where-Object {$_.Name -like '*.zip'})) {
                Write-Host "[$TempPath] has been compressed to [$TempFileName]."
                Compress-Archive -Path $tempPath\* -DestinationPath $tempPath\$TempFileName
            }
            Else {
                Write-Host "[$i] has already been compressed."
            }
        }
}
}

The code asks for the folder via a Folderbrowser dialog.

If someone could either help with the code or point me in the direction where I can find the information for this, I am a beginner with PowerShell but have done some programming.

If there is any other information needed let me know.


Solution

  • This is how you can do it using Runspace, use the -Threshold parameter depending on how many folders you want to compress at the same time.

    As commented, it is highly recommended to use the ThreadJob Module if you have the ability to install Modules in your environment.

    function Zip-Folders {
        [cmdletbinding()]
        param(
            [ValidateScript({ Test-Path $_ -PathType Container })]
            [Parameter(Mandatory)]
            [String] $FolderPath,
    
            [int] $Threshold = 10
        )
    
        begin {
            $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $Threshold)
            $RunspacePool.Open()
            $subFolders = Get-ChildItem -Path $FolderPath -Directory -Exclude Enter*
        }
        
        process {
            try {
                $runspaces = foreach ($folder in $subFolders) {
                    $instance = [powershell]::Create().AddScript({
                        param($thisFolder)
    
                        $fileName = "{0} Photos.zip" -f $thisFolder.Name
                        $absolutePath = $thisFolder.FullName
                        $zipPath = Join-Path $absolutePath -ChildPath $fileName
    
                        if(-not (Get-ChildItem -Path $absolutePath -Filter *.zip)) {
                            Compress-Archive -Path $absolutePath\* -DestinationPath $zipPath
                            return "[$absolutePath] has been compressed to [$zipPath]."
                        }
    
                        "[$absolutePath] has already been compressed."
                    }).AddParameter('thisFolder', $folder)
    
                    $instance.RunspacePool = $RunspacePool
    
                    [pscustomobject]@{
                        Instance = $instance
                        Handle   = $instance.BeginInvoke()
                    }
                }
    
                foreach($r in $runspaces) {
                    $r.Instance.EndInvoke($r.Handle)
                    $r.Instance.ForEach('Dispose')
                }
            }
            catch {
                $PSCmdlet.WriteError($_)
            }
            finally {
                $runspaces.ForEach('Clear')
                $RunspacePool.ForEach('Dispose')
            }
        }
    }