stringpowershell7zipquotation-marks

PowerShell, getting string syntax correct in 7-zip script


I can't seem to solve this, been struggling with it for a while (maybe it's simple; I just can't see it as I've been looking at it for so long). I can get the 7z.exe syntax to work, but when I try and put it together into a simple script, it fails.

e.g., if I run .\zip.ps1 "C:\test\test.zip" "C:\test\test1.txt" "C:\test\test2.txt*

Instead of zipping up the 2 files required, it zips up everything in the C:\test folder, completely ignoring my arguments.

How can I adjust the below string syntax so that 7z.exe will correctly respect the input arguments and compress them from within a PowerShell script?

"Received $($args.Count) files:"
$parent = Split-Path (Split-Path $args[0]) -Leaf
$sevenzip = "C:\Program Files\7-Zip\7z.exe"
$zipname = "$($parent) $(Get-Date -Format 'yyyy_MM_dd HH_mm_ss').zip"
$args_line = $args | foreach-object { "`"$_`"" }
# $args_line = '"' + $($args -join """ """) + '"'   # Want to use """ here so that it can capture not only files with spaces, but files with ' in the filename
''
"Zip Name  : $zipname"
''
"Arguments : $args_line"
''
if (Test-Path $sevenzip) {
    if (Test-Path "C:\0\$zipname") { rm "C:\0\$zipname" -Force }
    ''
    'String output of the line to run:'
    "& ""$sevenzip"" a -r -tzip ""C:\0\$zipname"" $args_line"   # Taking this output and pasting onto console works.
''
    & "$sevenzip" a -r -tzip "C:\0\$zipname" "$args_line"   # This does not work
} else {
    "7z.exe was not found at '$sevenzip', please check and try again"
}

The error that I get is:

Files read from disk: 0
Archive size: 22 bytes (1 KiB)

Scan WARNINGS for files and folders:

1 : The system cannot find the file specified.
----------------

Solution

  • Pass $args directly to your & "$sevenzip" call:

    & "$sevenzip" a -r -tzip "C:\0\$zipname" $args
    

    This makes PowerShell pass the array elements as individual arguments, automatically enclosing them in "..." if needed (based on whether they contain spaces).

    Using arrays as arguments for external programs is in effect an implicit form of array-based splatting; thus, you could alternatively pass @args.


    Generally, note that in direct invocation[1] you cannot pass multiple arguments to an external program via a single string; that is, something like "$args_line" cannot be expected to work, because it is passed as a single argument to the target program.


    If you want to emulate the resulting part of the command line, for display purposes:

    ($argListForDisplay = $args.ForEach({ ($_, "`"$_`"")[$_ -match ' '] })) -join ' '
    

    Note:


    A simplified example:

    & {  # an ad-hoc script block that functions like a script or function
    
      # Construct the argument list *for display*
      $argListForDisplay = $args.ForEach({ ($_, "`"$_`"")[$_ -match ' '] }) -join ' '
    
      @"
    The following is the equivalent of:
    
      Write-Output $argListForDisplay
    
    "@
    
     # Pass $args *directly*
     # Simply prints the argument received one by one, each on its own line.
     # Note: With PowerShell-native commands, generally use @args instead (see below). 
     Write-Output $args
    
    } firstArg 'another Arg' lastArg  # sample pass-through arguments
    

    Output:

    The following is the equivalent of:
    
      Write-Output firstArg "another Arg" lastArg
    
    firstArg
    another Arg
    lastArg
    

    Note: Write-Output is used in lieu of an external program for convenience. Technically, you'd have to use @args instead of $args in order to pass the array elements as individual, positional arguments, but, as stated, this is the default behavior with externals programs. Write-Output, as a PowerShell-native command, receives the array as a whole, as a single argument when $args is used; it just so happens to process that array the same way as if its elements had been passed as individual arguments.


    [1] You can use a single string as an -ArgumentList value for Start-Process. However, Start-Process is usually the wrong tool for invoking console applications such as 7z.exe - see this answer.