3

I am executing the following code attempting to execute the 7z.exe command to unzip files.

$dir contains the user input of the path to the zip file which can contain spaces of course! And $dir\temp2 below is a directory that I previously created.

Get-ChildItem -path $dir -Filter *.zip |
ForEach-Object {
    $zip_path = """" + $dir + "\" + $_.name + """"
    $output = " -o""$dir\temp2"""
    &7z e $zip_path $output
}

When I execute it I get the following from 7z.exe:

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18

Processing archive: C:\test dir\test.zip


No files to process

Files: 0
Size:       0
Compressed: 50219965

If I then copy the value from $zip_path and $output to form my own cmd line it works!

For example:

7z e "c:\test dir\test.zip" -o"c:\test output"

Now, I can reproduce the same message "no files to process" I get when I execute within PowerShell by using the following cmd in cli.

7z e "c:\test dir\test.zip" o"c:\test output"

So, it seems that PowerShell is removing the dash char from my -o option. And yes, it needs to be -o"C:\test output" and not -o "c:\test output" with 7z.exe there is no space between the -o parameter and its value.

I am stumped. Am I doing something wrong or should I be doing this a different way?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
john johnson
  • 699
  • 1
  • 12
  • 34

3 Answers3

3

I can never get Invoke-Expression (alias = &) to work right either, so I learned how to use a process object

    $7ZExe = (Get-Command -CommandType Application  -Name 7z )
    $7ZArgs = @(
        ('-o"{0}\{1}"' -f $dir, $_.Name), 
        ('"{0}\{1}"' -f $dir, 'temp2')
    )

    [Diagnostics.ProcessStartInfo]$7Zpsi = New-Object -TypeName:System.Diagnostics.ProcessStartInfo -Property:@{
        CreateNoWindow = $false;
        UseShellExecute = $false;
        Filename = $7ZExe.Path;
        Arguments = $7ZArgs;
        WindowStyle = 'Hidden';
        RedirectStandardOutput = $true
        RedirectStandardError = $true
        WorkingDirectory = $(Get-Location).Path
    }

    $proc = [System.Diagnostics.Process]::Start($7zpsi)
    $7ZOut = $proc.StandardOutput
    $7ZErr = $proc.StandardError
    $proc.WaitForExit()
Eris
  • 7,378
  • 1
  • 30
  • 45
  • thank you thank you. that worked perfect. i had to flip the args array, the -o comes second and the first paramter need an "e" , like this ('e "{0}\{1}"' -f $dir, 'temp2'). thank you again – john johnson Sep 11 '13 at 19:28
  • 1
    `&` is the call operator, which does not parse the command (see [`about_Operators`](http://go.microsoft.com/fwlink/?LinkID=113242)). If you are having trouble using it, then item **1.3** or item **2** in [my answer to 'call msbuild with nested quotation marks'](http://stackoverflow.com/a/8468690/2495) may be useful. – Emperor XLII Sep 15 '13 at 16:12
  • This solution has the added benefit of preventing the host from swallowing stderr and stdout. For example, git.exe throws all verbose output to stderr which causes ISE to throw a RemoteException or some such nonsense. – Eris Sep 21 '13 at 19:17
3

I was able to duplicate the exact issue and tried numerous combinations escaping the -o switch and escaping quotes " and what not.

But as one answer mentioned Sysinternals, and I used Process Monitor to find out the format it was passing to 7z.exe. Things that work on a plain commandline doesn't work inside PowerShell the same way.

For example, if I tried to construct parameters inside PowerShell just like cmdline it would fail. I.e., -o"C:\scripts\so\new folder" doesn't work. But if you include the -o switch inside quotes then PowerShell passes the string "-oC:\scripts\so\new folder" which 7z.exe is happy to accept. So I learned that 7z.exe would accept both the formats such as

"C:\Program Files\7-zip\7z.exe" e "C:\scripts\so\new folder.zip" -o"C:\scripts\so\new folder"

and

"C:\Program Files\7-zip\7z.exe" e "C:\scripts\so\new folder.zip" "-oC:\scripts\so\new folder"

And both examples contain spaces in them.

[string]$pathtoexe = "C:\Program Files\7-Zip\7z.exe"
$dir = "C:\scripts\so"
$output = "$dir\new folder"
Get-ChildItem -path $dir -Filter *.zip | % {
    [array]$marguments = "e",$_.FullName,"-o$output";
    & $pathtoexe $marguments
}

Another approach in PowerShell V3 is to escape the PowerShell parsing feature. You can use the --% command to tell PowerShell to stop parsing any more commands like this.

$zipfile = "C:\scripts\so\newfolder.zip"
$destinationfolder = "C:\scripts\so\New Folder"
[string]$pathtoexe = "C:\Program Files\7-Zip\7z.exe"
& $pathtoexe --% e "C:\scripts\so\newfolder.zip" -o"C:\scripts\so\new folder"

Using the --% syntax, you type commands just like you would type them on the command line. I tested this logic, and it extracts files to the destination folder.

To learn more about --%, check PS> help about_parsing.

The issue with this approach is after --% it is not possible to include a variable. The solution to this issue is to just include the --% as another string variable and pass it like this. And this approach is similar to the commandline approach which wasn't working originally.

[string]$pathtoexe = "C:\Program Files\7-Zip\7z.exe"
$dir = "C:\scripts\so"
$output = "$dir\new folder"

Get-ChildItem -path $dir -Filter *.zip | % {
    $zipfile = $_.FullName;
    [string]$formatted = [System.String]::Concat("e ", """$zipfile"""," -o""$output""");
    [string]$stopparser = '--%';
    & $pathtoexe $stopparser $formatted;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mitul
  • 9,734
  • 4
  • 43
  • 60
  • 1
    This doesn't solve the problem. `$Varname` isn't processed after a `--%`, so he can't use variable names for the arguments. – Eris Sep 11 '13 at 20:18
  • @Eris I realized it after posting it and refactoring according to OP's problem. – Mitul Sep 11 '13 at 20:42
  • @Eris I updated my answer after lots of tries. :) thanks for the motivation. – Mitul Sep 12 '13 at 13:53
  • @Eris, I came up with a workaround with `--%` approach. Just include `--%` it as a variable. – Mitul Sep 12 '13 at 19:48
3

Using the excellent Process Explorer from the Windows Sysinternals suite I was able to observe some very interesting behavior. I simplified your command line a little as seen below:

dir -Path $dir -Filter *.zip |
  select FullName |
  % { & 7za.exe e $_ "-o$dir\tmp" }

This was actually invoking the following command line according to Process Explorer:

C:\temp\7za.exe @{FullName="C:\temp\test.zip"} -oC:\temp\test

Telling PowerShell to expand the FullName property forces it out of the hashmap and treats it as a regular string which 7-Zip can deal with:

dir -Path $dir -Filter *.zip |
  select -ExpandProperty FullName |
  % { & 7za.exe e $_ "-o$dir\tmp" }

There may still be other issues like dealing with spaces in file names that I really didn't consider or account for, but I thought it was worth adding a note that PowerShell (v2 in this case) wasn't quite passing the parameters as you might expect.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Goyuix
  • 23,614
  • 14
  • 84
  • 128
  • +1 for mentioning Sysinternals tool. I tried different options before and was trying to include just path variable inside quotes which passing wrong strings to 7z.exe. You are awesome. – Mitul Sep 12 '13 at 13:52