2

I have the following that is working but I need to also have the ability to read the contents of compressed file (zip)

function Search-Files {
param ([string[]]$Servers, [string]$SearchPath, [string]$SearchItem, [string[]]$LogName)

ForEach ($Server in $Servers) {
    if ($LogName -eq $null) {
        dir -Path \\$server\$SearchPath -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Select-String -pattern $SearchItem -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Select-Object Filename, Path, Matches, LineNumber
    }
    Else {
        dir -Path \\$server\$SearchPath -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | ? {$_.Name -match $LogName} | Select-String -pattern $SearchItem -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Select-Object Filename, Path, Matches, LineNumber
    }
  }
}

Currently I am getting the following out put displayed which is what I would like to do for zip files as well

ip.ininlog \CO200197L\C$\Temp\Test\Test\ip\ip.ininlog {3030872954} 136594

I have found the following just not sure how to proceed to get them implemented

Grep File in Zip

List File in Zip

I need the ability to transverse all zip files that are store in a directory

Sample of Directory Structure

 2014-07-01 - root
     zip.zip
     zip_1.zip
     zip_2.zip
     etc
Community
  • 1
  • 1
ondrovic
  • 1,105
  • 2
  • 23
  • 40
  • Just to be sure I understand your question, you are meaning to run the `dir -Path \\...` operation inside ZIP files (i.e. traverse into ZIPs as if they were file system folders)? – PeterK Jul 10 '14 at 05:43
  • yes don't have to use dir can use gci – ondrovic Jul 10 '14 at 05:48
  • Thanks for the clarification. In case .NET 4.5 is available, you can use `System.IO.Compression.ZipFile` to extract the files to a temporary location and continue using the current approach. These classes also provide stream support and you may be able to extract ZIP contents in-memory and pass down the pipeline to Select-String. I realize that doesn't answer your question, hence the comment. – PeterK Jul 10 '14 at 06:15

2 Answers2

1

In case you have NET 4.5 framework installed, you can use 4.5's built-in ZIP support to extract files to a temporary path and run the selection on the temporary file. If no 4.5 is available, I recommend using SharpCompress (https://sharpcompress.codeplex.com/) which works in a similar way.

The following code snippet demonstrates extracting a ZIP archive into a temporary file, running the selection process from your script and the cleanup after the extraction. You can significantly simplify the code by extracting the entire ZIP file at once (just use ExtractToDirectory() on the archive) if it contains only the files you are seeking.

# import .NET 4.5 compression utilities
Add-Type -As System.IO.Compression.FileSystem;

# the input archive
$archivePath = "C:\sample.zip";

# open archive for reading
$archive = [System.IO.Compression.ZipFile]::OpenRead($archivePath);
try
{
    # enumerate all entries in the archive, which includes both files and directories
    foreach($archiveEntry in $archive.Entries)
    {
        # if the entry is not a directory (which ends with /)
        if($archiveEntry.FullName -notmatch '/$')
        {
            # get temporary file -- note that this will also create the file
            $tempFile = [System.IO.Path]::GetTempFileName();
            try
            {
                # extract to file system
                [System.IO.Compression.ZipFileExtensions]::ExtractToFile($archiveEntry, $tempFile, $true);

                # create PowerShell backslash-friendly path from ZIP path with forward slashes
                $windowsStyleArchiveEntryName = $archiveEntry.FullName.Replace('/', '\');
                # run selection
                Get-ChildItem $tempFile | Select-String -pattern "yourpattern" | Select-Object @{Name="Filename";Expression={$windowsStyleArchiveEntryName}}, @{Name="Path";Expression={Join-Path $archivePath (Split-Path $windowsStyleArchiveEntryName -Parent)}}, Matches, LineNumber
            }
            finally
            {
                Remove-Item $tempFile;
            }
        }
    }
}
finally
{
    # release archive object to prevent leaking resources
    $archive.Dispose();
}

If you have multiple ZIP files in the directory, you can enumerate them as follows (using your example script):

$zipArchives = Get-ChildItem -Path \\$server\$SearchPath -Recurse "*.zip";
foreach($zipArchive in $zipArchives)
{
   $archivePath = $zipArchive.FullName;
   ...
}

You can place the demo code in ... or move it to a PowerShell function.

PeterK
  • 3,667
  • 2
  • 17
  • 24
  • Thanks for that will give it a spin and see what happens I will ask one last part to this what if I have multiple zip files that I want to search through? I will update content of the question to better explain – ondrovic Jul 10 '14 at 07:12
  • I have edited the answer to provide an example of how all ZIP files can be enumerated recursively in a folder and how that would integrate with the demo code provided. Let me know if the code needs any explanation, I relied on inline comments mostly and I'm not sure if they are sufficiently thorough. – PeterK Jul 10 '14 at 08:43
  • is there anyway to do this without .NET the .NET version is working great but I have a few servers that don't have it installed and I don't have permission to install it – ondrovic Jul 16 '14 at 08:58
  • Some version of .NET must be installed if you can run PowerShell, because PowerShell itself is .NET-based. When .NET 4.5 is not available, you can always use SharpCompress as suggested in my response, the interface it uses is fairly similar to 4.5's ZipFile. You can also rely on external ZIP tools, capture and process their output. – PeterK Jul 16 '14 at 09:04
  • I saw that after I posted thanks, I am trying to load it but getting the following Add-Type : Could not load file or assembly 'C:\\Users\\Chris.Ondrovic\\Desktop\\PwrShell\\SharpCompress.dll' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047) any way you could post a sample like you did with the .NET? – ondrovic Jul 16 '14 at 09:18
  • Try `Add-Type -Path` instead of `Add-Type -As`. The `-As` switch is short for `-AssemblyName` and it does not accept a path as input. It worked nice for me using `-Path`. However, I just have noticed that SharpCompress requires at least .NET 4.0, is that OK? – PeterK Jul 16 '14 at 09:26
  • Also, unfortunately I don't have a working example based on SharpCompress and won't have the time today to develop one. The best I can provide you right now is a couple of SharpCompress examples in .NET: https://sharpcompress.codeplex.com/wikipage?title=Composite%20API%20Examples. You may want to post your question again with the requirement of _not_ using anything specific to .NET 4.5, this way other StackOverflow members may be able to help. – PeterK Jul 16 '14 at 09:29
  • Yes 4.0 is installed just not 4.5 Add-Type -Path "C:\Users\Chris.Ondrovic\Desktop\PwrShell\SharpCompress.dll" still gives the following Add-Type : Could not load file or assembly 'file:///C:\Users\Chris.Ondrovic\Desktop\PwrShell\SharpCompress.dll' or one of its dependencies. Operation is not supported. (Exception from HRESULT: 0x80131515) – ondrovic Jul 16 '14 at 09:29
  • 1
    "Operation is not supported" suggests that the file may be blocked, please see http://stackoverflow.com/questions/18801440/powershell-load-dll-got-error-add-type-could-not-load-file-or-assembly-webdr regarding this. – PeterK Jul 16 '14 at 09:31
1

Sometimes is not desirable to extract a zip entry as a file. Instead it may be preferable to work with the file in memory. Extracting a Zip entry containing XML or JSON text so it can be parsed in memory is an example.

Here is a technique that will allow you to do this. This example assumes there is a Zip entry with a name ending in .json and it is this file which is to be retrieved. Clearly the idea can be modified to handle different cases.

This code should work with version of the .NET Framework that includes the System.IO.Compression namespace.

try
{
    # import .NET 4.5 compression utilities
    Add-Type -As System.IO.Compression.FileSystem;

    # A variable to hold the recovered JSON content
    $json = $null

    $zip = [IO.Compression.ZipFile]::OpenRead($zipFileName)
    $zip.Entries | 
        Where-Object { $_.Name.EndsWith(".json") } | 
        ForEach-Object {

        # Use a MemoryStream to hold the inflated file content
        $memoryStream = New-Object System.IO.MemoryStream

        # Read the entry
        $file = $_.Open()

        # Copying inflates the entry content
        $file.CopyTo($memoryStream)

        # Make sure the entry is closed
        $file.Dispose()

        # After copying, the cursor will be at the end of the stream
        # so set the position to the beginning or there will be no output
        $memoryStream.Position = 0

        # Use a StreamReader because it allows the content to be
        # read as a string in one go
        $reader = New-Object System.IO.StreamReader($memoryStream)

        # Read the content as a string                       
        $json = $reader.ReadToEnd()

        # Close the reader and memory stream
        $reader.Dispose()
        $memoryStream.Dispose()
    }

    # Finally close the zip file.  This is necessary 
    # because the zip file does get closed automatically
    $zip.Dispose() 

    # Do something with the JSON in memory
    if ( $json -ne $null )
    {
        $objects = $json | ConvertFrom-Json
    }
}
catch
{
    # Report errors
}
bseddon
  • 154
  • 2
  • 3