3

I am having trouble trying to call a function for a script that I'm using to build a list of zip files in a folder on my PC. The final CSV I need is to create is a list of the zip files with their uncompressed sizes. Here is what I have so far (compiled from several posts):

Function to get the uncompressed size:

function Get-UncompressedZipFileSize {

param (
    $Path
)

$shell = New-Object -ComObject shell.application
$zip = $shell.NameSpace($Path)
$size = 0
foreach ($item in $zip.items()) {
    if ($item.IsFolder) {
        $size += Get-UncompressedZipFileSize -Path $item.Path
    } else {
        $size += $item.size
    }
}


[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$shell) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

return $size
}

Here is my Array Creation:

$arr = @()
gci C:\zips -recurse | ? {$_.PSIsContainer -eq $False} | % {
$obj = New-Object PSObject
$obj | Add-Member NoteProperty Name $_.Name
$obj | Add-Member NoteProperty FullPath $_.FullName
$arr += $obj
}
$arr | Export-CSV -notypeinformation c:\zips

I'm stuck at creating a new member object into my array that will call the get-uncompressedzipfilesize function to pass that size back into the array as a new column in my zip. Is something like this even possible.?

Harry123
  • 47
  • 5

2 Answers2

3

To make this simpler, since you're only calling your function to get the size of the current folder (zip), you can use a Calculated Property for this:

$Path = "C:\Zips"
Get-ChildItem -Path $Path -Directory -Recurse | 
    Select-Object -Property Name, FullName,
    @{
        Name = "Size"
        Expression = {
            Get-UncompressedZipFileSize -Path $_.FullName
        }
    } | Export-Csv -Path "$Path\zip.csv" -Force -NoTypeInformation -Append 

On another note, if you ever find yourself explicitly adding to an array, take advantage of PowerShell's pipeline streaming.

$Path = "C:\Zips"
Get-ChildItem -Path $Path -Directory -Recurse | 
    ForEach-Object -Process {
        [PSCustomObject]@{
            Name = $_.Name
            FullPath = $_.FullName
            Size = Get-UncompressedZipFileSize -Path $_.FullName
        } | Export-Csv -Path "$Path\zip.csv" -Force -NoTypeInformation -Append 
    }

Not only is adding to a fixed array (+=) computationally expensive (if you have a large directory), it is slow. Fixed arrays mean just that, they are a fixed size and in order for you to add to it, it needs to be broken down and recreated. An alternate solution would by an arraylist but, in this case - and in most cases - it's not needed.

  • Get-ChildItem also includes a -Directory switch to search for just folders. Presented in V3.
  • I would recommend searching for the file extension of the compressed folders as well so you don't run into any issues using -Filter.
mklement0
  • 312,089
  • 56
  • 508
  • 622
Abraham Zinala
  • 2,812
  • 2
  • 6
  • 22
  • 2
    Nice, though I didn't know those poor arrays get _broken down_ :) – mklement0 Feb 12 '22 at 19:32
  • I think I read that in the book "PowerShell in Practice". Lol could be wrong – Abraham Zinala Feb 12 '22 at 19:34
  • 1
    :) Well, I guess eventually _garbage-collecting_ the old array can be though of as breaking it down... – mklement0 Feb 12 '22 at 19:35
  • Would love to see these concepts in actual code; to get a better understanding. It's just so difficult lol – Abraham Zinala Feb 12 '22 at 19:38
  • 3
    Garbage collection is built into .NET and performed on demand, so there's rarely a need to control it explicitly in user code, though it is possible via the [`[GC]`](https://docs.microsoft.com/en-us/dotnet/api/system.gc) class; the linked topic also leads to background information. – mklement0 Feb 12 '22 at 19:47
  • 1
    Thank you thank you thank you!! I ended up going with the Calculated Property solution and created my own array and calling in my custom function!! Hooray! – Harry123 Feb 13 '22 at 16:18
3

Here is an alternative using ZipFile Class. The SizeConvert function is inspired from this answer. The output of the Get-ZipFileSize would be the absolute path of the Zip File, it's compressed and expanded size and it's formatted sizes (i.e.: 7.88 MB instead of 8262942).

using namespace System.IO
using namespace System.IO.Compression
using namespace System.Linq

function SizeConvert {
[cmdletbinding()]
param(
    [object]$Length,
    [int]$DecimalPoints = 2
)

    $suffix = "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"
    $index = 0
    while ($Length -ge 1kb) {
        $Length /= 1kb
        $index++
    }
    return [string]::Format(
        '{0} {1}',
        [math]::Round($Length, $DecimalPoints), $suffix[$index]
    )
}

function Get-ZipFileSize {
[cmdletbinding()]
param(
    [parameter(ValueFromPipeline)]
    [FileInfo]$Path
)
    process {
        try
        {
            $zip = [ZipFile]::OpenRead($Path.FullName)
            $compressedSize = [Enumerable]::Sum([int64[]]$zip.Entries.CompressedLength)
            $expandedSize = [Enumerable]::Sum([Int64[]]$zip.Entries.Length)

            [pscustomobject]@{
                FilePath            = $Path.FullName
                RawExpanded         = $expandedSize
                RawCompressed       = $compressedSize
                FormattedExpanded   = SizeConvert $expandedSize -DecimalPoints 3
                FormattedCompressed = SizeConvert $compressedSize -DecimalPoints 3
            }
        }
        catch
        {
            $PSCmdlet.WriteError($_)
        }
        finally
        {
            if($zip -is [System.IDisposable]) {
                $zip.Dispose()
            }
        }
    }
}

Get-ChildItem -Filter *.zip -Recurse | Get-ZipFileSize | Export-Csv ....
Santiago Squarzon
  • 20,988
  • 4
  • 10
  • 27
  • 1
    I guess `SizeConvert` is future-proof, supporting yottabytes. :) – zett42 Feb 12 '22 at 21:53
  • @zett42 LOL I guess yes! Didn't want to modify the function too much as I think the author did a great job but yeah YB seem a bit of an overkill :P – Santiago Squarzon Feb 12 '22 at 21:56
  • 1
    You might also comsider using the Windows Shell [shlwapi.h `StrFormatByteSize`](https://docs.microsoft.com/windows/win32/api/shlwapi/nf-shlwapi-strformatbytesizew) function for convenient conversion, see [How to convert value to KB, MB, or GB depending on digit placeholders?](https://stackoverflow.com/a/57535324/1701026) – iRon Feb 13 '22 at 12:06
  • @iRon that looks very interesting never seen that before, unfortunately it wouldn't be compatible with Linux – Santiago Squarzon Feb 13 '22 at 14:12