Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Powershell Zip Folder Contents

Tags:

powershell

zip

I know a lot has been covered on zipping files using Powershell however I cannot find a method which does exactly what I need.

I want to be able to zip folders AND files into a .zip folder without having the parent folder inside the zip. So for example I have a folder named STUFF which contains files/folders, I want to zip this into a folder called STUFF.zip. This folders structure would then be STUFF.zip>files/folders NOT STUFF.zip>STUFF>files/folders as I currently get using this code...

function CountZipItems(
[__ComObject] $zipFile)
{
If ($zipFile -eq $null)
{
    Throw "Value cannot be null: zipFile"
}

Write-Host ("Counting items in zip file (" + $zipFile.Self.Path + ")...")

[int] $count = CountZipItemsRecursive($zipFile)

Write-Host ($count.ToString() + " items in zip file (" `
    + $zipFile.Self.Path + ").")

return $count
}

function CountZipItemsRecursive(
[__ComObject] $parent)
{
If ($parent -eq $null)
{
    Throw "Value cannot be null: parent"
}

[int] $count = 0

$parent.Items() |
    ForEach-Object {
        $count += 1

        If ($_.IsFolder -eq $true)
        {
            $count += CountZipItemsRecursive($_.GetFolder)
        }
    }

return $count
}

function IsFileLocked(
[string] $path)
{
If ([string]::IsNullOrEmpty($path) -eq $true)
{
    Throw "The path must be specified."
}

[bool] $fileExists = Test-Path $path

If ($fileExists -eq $false)
{
    Throw "File does not exist (" + $path + ")"
}

[bool] $isFileLocked = $true

$file = $null

Try
{
    $file = [IO.File]::Open(
        $path,
        [IO.FileMode]::Open,
        [IO.FileAccess]::Read,
        [IO.FileShare]::None)

    $isFileLocked = $false
}
Catch [IO.IOException]
{
    If ($_.Exception.Message.EndsWith(
        "it is being used by another process.") -eq $false)
    {
        Throw $_.Exception
    }
}
Finally
{
    If ($file -ne $null)
    {
        $file.Close()
    }
}

return $isFileLocked
}

function GetWaitInterval(
[int] $waitTime)
{
If ($waitTime -lt 1000)
{
    return 100
}
ElseIf ($waitTime -lt 5000)
{
    return 1000
}
Else
{
    return 5000
}
}

function WaitForZipOperationToFinish(
[__ComObject] $zipFile,
[int] $expectedNumberOfItemsInZipFile)
{
If ($zipFile -eq $null)
{
    Throw "Value cannot be null: zipFile"
}
ElseIf ($expectedNumberOfItemsInZipFile -lt 1)
{
    Throw "The expected number of items in the zip file must be specified."
}

Write-Host -NoNewLine "Waiting for zip operation to finish..."
Start-Sleep -Milliseconds 1000 # ensure zip operation had time to start

[int] $waitTime = 0
[int] $maxWaitTime = 60 * 1000 # [milliseconds]
while($waitTime -lt $maxWaitTime)
{
    [int] $waitInterval = GetWaitInterval($waitTime)

    Write-Host -NoNewLine "."
    Start-Sleep -Milliseconds $waitInterval
    $waitTime += $waitInterval

    Write-Debug ("Wait time: " + $waitTime / 1000 + " seconds")

    [bool] $isFileLocked = IsFileLocked($zipFile.Self.Path)

    If ($isFileLocked -eq $true)
    {
        Write-Debug "Zip file is locked by another process."
        Continue
    }
    Else
    {
        Break
    }
}

Write-Host                           

If ($waitTime -ge $maxWaitTime)
{
    Throw "Timeout exceeded waiting for zip operation"
}

[int] $count = CountZipItems($zipFile)

If ($count -eq $expectedNumberOfItemsInZipFile)
{
    Write-Debug "The zip operation completed succesfully."
}
ElseIf ($count -eq 0)
{
    Throw ("Zip file is empty. This can occur if the operation is" `
        + " cancelled by the user.")
}
ElseIf ($count -gt $expectedCount)
{
    Throw "Zip file contains more than the expected number of items."
}
}

function ZipFolder(
[IO.DirectoryInfo] $directory)
{
If ($directory -eq $null)
{
    Throw "Value cannot be null: directory"
}

Write-Host ("Creating zip file for folder (" + $directory.FullName + ")...")

[IO.DirectoryInfo] $parentDir = $directory.Parent

[string] $zipFileName

If ($parentDir.FullName.EndsWith("\") -eq $true)
{
    # e.g. $parentDir = "C:\"
    $zipFileName = $parentDir.FullName + $directory.Name + ".zip"
}
Else
{
    $zipFileName = $parentDir.FullName + "\" + $directory.Name + ".zip"
    #$zipFileName = $directory.Name + ".zip"
    #$zipFileName = $parentDir.FullName + ".zip"
}

If (Test-Path $zipFileName)
{
    Throw "Zip file already exists ($zipFileName)."
}

Set-Content $zipFileName ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))

$shellApp = New-Object -ComObject Shell.Application
$zipFile = $shellApp.NameSpace($zipFileName)

If ($zipFile -eq $null)
{
    Throw "Failed to get zip file object."
}

[int] $expectedCount = (Get-ChildItem $directory -Force -Recurse).Count
$expectedCount += 1 # account for the top-level folder

$zipFile.CopyHere($directory.FullName)
#Get-ChildItem $directory | foreach {$zipFile.CopyHere($_.fullname)}

# wait for CopyHere operation to complete
WaitForZipOperationToFinish $zipFile $expectedCount

Write-Host -Fore Green ("Successfully created zip file for folder (" `
    + $directory.FullName + ").")
}

Usage

Remove-Item "H:\STUFF.zip"

[IO.DirectoryInfo] $directory = Get-Item "H:\STUFF"
ZipFolder $directory

Complete credit goes here for this code. I really appreciate any help I get, this capability is crucial to my project! Unfortunately I cannot use the Community Extension module as other PC's this will be run on does not have this module installed.

Thanks!

like image 715
user3402227 Avatar asked Apr 02 '14 10:04

user3402227


2 Answers

As per @mjolinor "H:\Stuff\", if you have .NET 4.5 installed you can run the following:

$src = "H:\Stuff\"
$dst = "H:\Stuff.zip"
[Reflection.Assembly]::LoadWithPartialName( "System.IO.Compression.FileSystem" )
[System.IO.Compression.ZipFile]::CreateFromDirectory($src, $dst)
like image 145
Raf Avatar answered Oct 06 '22 01:10

Raf


If you use PSCX Module you will use the line code below to obtain what you need:

write-zip h:\Stuff\* myzipfile.zip 
like image 22
CB. Avatar answered Oct 06 '22 00:10

CB.