Search code examples
windowspowershellzipaccess-deniedfile-search

Unable to bundle searched files in Windows into a ZIP with their absolute path


I wish to search for files or folders provided to $itemsToInclude under directory C:\bea Once found it should add the found item [file or folder] to $zipFileName while persisting the folder structure.

Thus, if rt.jar is found in C:\bea\dev\log\rt.jar as well as C:\bea\QA\log\rt.jar my C:\bea\package-dev.zip should contain both like below:

- `C:\bea\dev\log\rt.jar`
- `C:\bea\QA\log\rt.jar`

Below is my attempt which fails with error Access to the path 'C:\bea\package-dev.zip' is denied." but my powershell can very well write to the C:\bea\ folder. I dont expect to see any ACCESS DENIED error.

  $zipFileName = "C:\bea\package-dev.zip"
  cd "C:\bea"
  $itemsToInclude = "font.properties,rt.jar"
  Write-Host "itemsToInclude is- $itemsToInclude"
  $workspace = "C:\bea"
  if ($itemsToInclude -eq '*') {
  # Include all files, including files from subdirectories
  Write-Host "Include all files, including files from subdirectories"
  Get-ChildItem -Path $workspace -Recurse -File |
    Compress-Archive -DestinationPath $zipFileName  -Update -Force
  } else {
  # Include specific files as per the comma-separated list
  Write-Host "Include specific files as per the comma-separated list"
  $pattern = $itemsToInclude.Split(',').ForEach({ [regex]::Escape($_) }) -join '|'
  $filesToInclude = Get-ChildItem $workspace -Recurse -File |
  Where-Object FullName -Match $pattern
  
  $filesToInclude | ForEach-Object {
    $relativePath = $_.FullName.Substring($workspace.Length)
    $destinationPath = Join-Path $zipFileName $relativePath
    $destinationFolder = Split-Path $destinationPath -Parent
    if (-not (Test-Path $destinationFolder)) {
      New-Item -ItemType Directory -Path $destinationFolder | Out-Null
      }
      Copy-Item $_.FullName -Destination $destinationPath -Force
    }
    Compress-Archive -Path $zipFileName -DestinationPath $zipFileName -Update
  }

Output:

itemsToInclude is- font.properties,rt.jar
Include specific files as per the comma-separated list
New-Object : Exception calling ".ctor" with "2" argument(s): "Access to the path 'C:\bea\package-dev.zip' is denied."
At C:\windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Archive\Microsoft.PowerShell.Archive.psm1:729 
char:30
+ ... ileStream = New-Object -TypeName System.IO.FileStream -ArgumentList $ ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [New-Object], MethodInvocationException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
 

Note: I m able to successfully create ZIP without the absolute path for files in the same directory using the below code:

  $zipFileName = "C:\bea\package-dev.zip"
  cd "C:\bea"
  $itemsToInclude = "font.properties,rt.jar"
  Write-Host "itemsToInclude is- $itemsToInclude"
  $workspace = "C:\bea"
  if ($itemsToInclude -eq '*') {
  # Include all files, including files from subdirectories
    Write-Host "Include all files, including files from subdirectories"
    Get-ChildItem -Path $workspace -Recurse -File |
      Compress-Archive -DestinationPath $zipFileName  -Update -Force
     } else {
     # Include specific files as per the comma-separated list
     Write-Host "Include specific files as per the comma-separated list" 
       $pattern = $itemsToInclude.Split(',').ForEach({ [regex]::Escape($_) }) -join '|'
       Get-ChildItem $workspace -Recurse -File |
       Where-Object FullName -Match $pattern |
       Compress-Archive -DestinationPath $zipFileName -Update
       }

Can you please suggest?


Solution

  • Hard to determine why your code is failing, it might possible that the Compress-Archive is not correctly closing the zip stream or you have the zip opened while trying to run your code.

    If you're able to install modules then using PSCompression can simplify the task of adding zip entries to an existing zip archive for you. The module can be installed through the PowerShell Gallery using:

    Install-Module PSCompression -Scope CurrentUser
    

    Then the else peace of your code can be replaced using a similar logic as in the example 4 of the docs:

    if (-not (Test-Path $zipFileName)) {
        $null = New-Item $zipFileName -ItemType File
    }
    
    Write-Host 'Include specific files as per the comma-separated list'
    $pattern = $itemsToInclude.Split(',').ForEach({ [regex]::Escape($_) }) -join '|'
    $filesToInclude = Get-ChildItem $workspace -Recurse -File |
        Where-Object FullName -Match $pattern
    
    $filesToInclude | ForEach-Object {
        $newZipEntrySplat = @{
            EntryPath   = $_.FullName.Substring($workspace.Length)
            SourcePath  = $_.FullName
            Destination = $zipFileName
        }
    
        New-ZipEntry @newZipEntrySplat
    }