Skip to content

Instantly share code, notes, and snippets.

@mobzystems
Last active September 21, 2024 03:31
Show Gist options
  • Save mobzystems/793007db28e3ffcc20e2 to your computer and use it in GitHub Desktop.
Save mobzystems/793007db28e3ffcc20e2 to your computer and use it in GitHub Desktop.
7-Zip commands for PowerShell
<#
Powershell Module 7-Zip - 7-Zip commands for PowerShell
The functions in this module call 7za.exe, the standAlone version of 7-zip to perform various tasks on 7-zip archives.
Place anywhere, together with 7za.exe and 7zsd.sfx. 7za.exe is required for all operations; 7zsd.sfx for creating self
extracting archives.
http://www.7-zip.org
Import-Module [Your path\]7-Zip
Brought to you by MOBZystems, Home of Tools - http://www.mobzystems.com/
License: use at will!
#>
[CmdletBinding()]
Param()
# Be strict
Set-StrictMode -Version Latest
# Module variables
[string]$SCRIPT_DIRECTORY = Split-Path ($MyInvocation.MyCommand.Path) -Parent
[string]$7Z_SFX = Join-Path $SCRIPT_DIRECTORY "7zsd.sfx"
[string]$7Z_EXE = Join-Path $SCRIPT_DIRECTORY "7za.exe"
# Sanity checks
if (!(Test-Path -PathType Leaf $7Z_EXE)) {
Write-Warning "Cannot find 7za.exe in `"$SCRIPT_DIRECTORY`". This file is required for all operations in this module"
}
if (!(Test-Path -PathType Leaf $7Z_SFX)) {
Write-Warning "Cannot find 7zsd.sfx in `"$SCRIPT_DIRECTORY`". This file is required for New-7zSfx"
}
<#
This (internal) function does the hard work: it calls 7za with the appropriate arguments
#>
Function Perform7zOperation {
[CmdletBinding()]
Param(
# The operation to perform
[Parameter(Mandatory=$true)]
[ValidateSet("Add", "Update", "List", "Extract", "Test")]
[string]$Operation,
# The path of the archive
[Parameter(Mandatory=$true)]
[string]$Path,
# A list of file names or patterns to include
[Parameter(Mandatory=$true)]
[AllowEmptyCollection()]
[string[]]$Include,
# A list of file names or patterns to exclude
[Parameter(Mandatory=$true)]
[AllowEmptyCollection()]
[string[]]$Exclude,
# Apply include patterns recursively
[Parameter(Mandatory=$true)]
[switch]$Recurse,
# Additional switches for 7za
[Parameter(Mandatory=$true)]
[AllowEmptyString()]
[string]$Switches,
# Throw if the output does not contain "Everything is OK"
[Parameter(Mandatory=$false)]
[switch]$CheckOK = $true
)
switch ($Operation) {
"Add" {
$7zcmd = "a"
$verb = "Adding to"
}
"Update" {
$7zcmd = "u"
$verb = "Updating"
}
"Extract" {
$7zcmd = "e"
$verb = "Extracting"
}
"List" {
$7zcmd = "l"
$verb = "Listing"
}
"Test" {
$7zcmd = "t"
$verb = "Testing"
}
}
# Create a list of quoted file names from the $Include argument
[string]$files = ""
$Include | ForEach-Object { $files += " `"$_`"" }
$files = $files.TrimStart()
# Set up switches to use
$Switches += " -bd -y" # -bd: no percentage indicator, -y: Yes to all prompts
if ($Recurse) {
$Switches += " -r" # -r: recurse
}
# Add excludes to the switches
$Exclude | ForEach-Object { $Switches += " `"-x!$_`"" }
$Switches = $Switches.TrimStart()
Write-Verbose "$verb archive `"$Path`""
[string]$cmd = "`"$7Z_EXE`" $7zcmd $Switches `"$Path`" $files"
Write-Debug $cmd
Invoke-Expression "&$cmd" -OutVariable output | Write-Verbose
# Check result
if ($CheckOK) {
if (-not ([string]$output).Contains("Everything is Ok")) {
throw "$verb archive `"$Path`" failed: $output"
}
}
# No error: return the 7-Zip output
Write-Output $output
}
<#
.SYNOPSIS
Create a new 7-Zip archive
.DESCRIPTION
Use this cmdlet to create 7-Zip archives. Possible types are 7z (default) and zip.
The archive file is overwritten if it exists!
.EXAMPLE
New-7zArchive new-archive *.txt
Creates a new 7-zip-archive named 'new-archive.7z' containing all files with a .txt extension
in the current directory
.EXAMPLE
New-7zArchive new-archive *.txt -Type zip
Creates a new zip-archive named 'new-archive.zip' containing all files with a .txt extension
in the current directory
.EXAMPLE
New-7zArchive new-archive *.jpg,*.gif,*.png,*.bmp -Recurse -Exclude tmp/
Creates a new 7-zip archive named 'new-archive.7z' containing all files with an extension
of jpg, gif, png or bmp in the current directory and all directories below it
All files in the folder tmp are excluded, i.e. not included in the archive.
#>
Function New-7zArchive {
[CmdletBinding()]
Param(
# The path of the archive to create
[Parameter(Mandatory=$true, Position=0)]
[string]$Path,
# A list of file names or patterns to include
[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)]
[string[]]$Include,
# A list of file names or patterns to exclude
[Parameter(Mandatory=$false)]
[string[]]$Exclude = @(),
# The type of archive to create
[ValidateSet("7z", "zip")]
[string]$Type = "7z",
# Apply include patterns recursively
[switch]$Recurse,
# Additional switches for 7za
[string]$Switches = ""
)
Begin {
# Make sure the archive is deleted before it is created
if (Test-Path -PathType Leaf $Path) {
Remove-Item $Path | Out-Null
}
$filesToProcess = @()
}
Process {
$filesToProcess += $Include
}
End {
$Switches = "$Switches -t$Type"
[string[]]$result = Perform7zOperation -Operation Add -Path $Path -Include $filesToProcess -Exclude $Exclude -Recurse:$Recurse -Switches $Switches
}
}
<#
.SYNOPSIS
Add files to a 7-Zip archive
.DESCRIPTION
Use this cmdlet to add files to an existing 7-Zip archive. If the archive does not
exists, it is created
#>
Function Add-7zArchive {
[CmdletBinding()]
Param(
# The path of the archive to add to
[Parameter(Mandatory=$true, Position=0)]
[string]$Path,
# A list of file names or patterns to include
[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)]
[string[]]$Include,
# A list of file names or patterns to exclude
[Parameter(Mandatory=$false)]
[string[]]$Exclude = @(),
# The type of archive to create
# Apply include patterns recursively
[switch]$Recurse,
# Additional switches for 7za
[string]$Switches = ""
)
Begin {
$filesToProcess = @()
}
Process {
$filesToProcess += $Include
}
End {
[string[]]$result = Perform7zOperation -Operation Add -Path $Path -Include $filesToProcess -Exclude $Exclude -Recurse:$Recurse -Switches $Switches
}
}
<#
.SYNOPSIS
Update files in a 7-Zip archive
.DESCRIPTION
Use this cmdlet to update files to an existing 7-Zip archive. If the archive does not
exists, it is created
#>
Function Update-7zArchive {
[CmdletBinding()]
Param(
# The path of the archive to update
[Parameter(Mandatory=$true, Position=0)]
[string]$Path,
# A list of file names or patterns to include
[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)]
[string[]]$Include,
# A list of file names or patterns to exclude
[Parameter(Mandatory=$false)]
[string[]]$Exclude = @(),
# Apply include patterns recursively
[switch]$Recurse,
# Additional switches for 7za
[string]$Switches = ""
)
Begin {
$filesToProcess = @()
}
Process {
$filesToProcess += $Include
}
End {
[string[]]$result = Perform7zOperation -Operation Update -Path $Path -Include $filesToProcess -Exclude $Exclude -Recurse:$Recurse -Switches $Switches
}
}
<#
.SYNOPSIS
Extract files fom a 7-Zip archive
.DESCRIPTION
Use this cmdlet to extract files from an existing 7-Zip archive
.EXAMPLE
Expand-7zArchive backups.7z
#>
Function Expand-7zArchive {
[CmdletBinding()]
Param(
# The path of the archive to update
[Parameter(Mandatory=$true, Position=0)]
[string]$Path,
# The path to extract files to
[Parameter(Mandatory=$false, Position=1)]
[string]$Destination = ".",
# A list of file names or patterns to include
[Parameter(Mandatory=$false, ValueFromPipeLine=$true, Position=2)]
[string[]]$Include = @("*"),
# A list of file names or patterns to exclude
[Parameter(Mandatory=$false)]
[string[]]$Exclude = @(),
# Apply include patterns recursively
[switch]$Recurse,
# Additional switches for 7za
[string]$Switches = "",
# Force overwriting existing files
[switch]$Force
)
Begin {
$Switches = $Switches + " `"-o$Destination`""
if ($Force) {
$Switches = $Switches + " -aoa" # Overwrite ALL
} else {
$Switches = $Switches + " -aos" # SKIP extracting existing files
}
$filesToProcess = @()
}
Process {
$filesToProcess += $Include
}
End {
[string[]]$result = Perform7zOperation -Operation Extract -Path $Path -Include $filesToProcess -Exclude $Exclude -Recurse:$Recurse -Switches $Switches
$result | ForEach-Object {
if ($_.StartsWith("Skipping ")) {
Write-Warning $_
}
}
}
}
<#
.SYNOPSIS
List the files in a 7-Zip archive.
.DESCRIPTION
Use this cmdlet to examine the contents of 7-Zip archives.
Output is a list of PSCustomObjects with properties [string]Mode, [DateTime]DateTime, [int]Length, [int]Compressed and [string]Name
.EXAMPLE
Get-7zArchive c:\temp\test.7z
List the contents of the archive "c:\temp\test.7z"
#>
Function Get-7zArchive {
[CmdletBinding()]
[OutputType([PSCustomObject[]])]
Param(
# The name of the archive to list
[Parameter(Mandatory=$true, Position=0)]
[string]$Path,
# Additional switches
[Parameter(Mandatory=$false)]
[string]$Switches = ""
)
[string[]]$result = Perform7zOperation -Operation List -Path $Path -Include @() -Exclude @() -Recurse:$false -Switches $Switches -CheckOK:$false
[bool]$separatorFound = $false
[int]$filecount = 0
$result | ForEach-Object {
if ($_.StartsWith("------------------- ----- ------------ ------------")) {
if ($separatorFound) {
# Second separator! We're done
break
}
$separatorFound = -not $separatorFound
} else {
if ($separatorFound) {
# 012345678901234567890123456789012345678901234567890123456789012345678901234567890
# x-----------------x x---x x----------x x----------x x--------------------
# 2015-12-20 14:25:18 ....A 18144 2107 XMLClassGenerator.ini
[string]$mode = $_.Substring(20, 5)
[DateTime]$datetime = [DateTime]::ParseExact($_.Substring(0, 19), "yyyy'-'MM'-'dd HH':'mm':'ss", [CultureInfo]::InvariantCulture)
[int]$length = [int]"0$($_.Substring(26, 12).Trim())"
[int]$compressedlength = [int]"0$($_.Substring(39, 12).Trim())"
[string]$name = $_.Substring(53).TrimEnd()
# Write a PSCustomObject with properties to output
Write-Output ([PSCustomObject] @{
Mode = $mode
DateTime = $datetime
Length = $length
Compressed = $compressedlength
Name = $name
})
$filecount++
}
}
}
}
<#
.SYNOPSIS
Test a new 7-Zip archive.
.DESCRIPTION
Use this cmdlet to test 7-Zip archives for errors
.EXAMPLE
Test-7zArchive c:\temp\test.7z
Test the archive "c:\temp\test.7z". Throw an error if any errors are found
#>
Function Test-7zArchive {
[CmdletBinding()]
Param(
# The name of the archive to test
[Parameter(Mandatory=$true, Position=0)]
[string]$Path,
# Additional switches
[Parameter(Mandatory=$false, Position=1)]
[String]$Switches = ""
)
[string[]]$result = Perform7zOperation -Operation Test -Path $Path -Include @() -Exclude @() -Recurse:$false -Switches $Switches -CheckOK:$false
# Check result
if ($result.Contains("No files to process")) {
Write-Verbose "Archive is empty"
return
}
if ($result.Contains("cannot find archive")) {
throw "Archive `"$Path`" not found"
}
if ($result.Contains("Everything is Ok")) {
Write-Verbose "Archive is OK"
return
}
# In all other cases, we have an error. Write out the results Verbose
$result | Write-Verbose
# ... and throw an error
throw "Testing archive `"$Path`" failed: $result"
}
<#
.SYNOPSIS
Create a new 7-Zip self extracting archive
.DESCRIPTION
Create self-extracting archives using 7-Zip
.EXAMPLE
New-7zsfx app-sfx app.exe,app.exe.config app.exe
Simply create a self-extracting exe from an executable file app.exe
with its configuration file app.exe.config:
#>
Function New-7zSfx {
[CmdletBinding()]
Param(
# The name of the exe-file to produce, without extension
[Parameter(Mandatory=$true, Position=0)]
[string]$Path,
# The files to include in the archive
[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)]
[string[]]$Include,
# The command to run when the sfx archive is started
[Parameter(Mandatory=$true, Position=2)]
[string]$CommandToRun,
# Title for messages
[Parameter(Mandatory=$false)]
[string]$Title,
# Begin Prompt message
[Parameter(Mandatory=$false)]
[string]$BeginPrompt,
# Title of extraction dialog
[Parameter(Mandatory=$false)]
[string]$ExtractTitle,
# Text in dialog
[Parameter(Mandatory=$false)]
[string]$ExtractDialogText,
# Button text of cancel button
[Parameter(Mandatory=$false)]
[string]$ExtractCancelText,
# A list of additional options, of the form "key=value"
[Parameter(Mandatory=$false)]
[string[]]$ConfigOptions,
# Include subdirectories
[switch]$Recurse,
# Additional switches to pass to 7za when creating the archive
[string]$Switches = ''
)
Begin {
# Get the base name of the specified path in Name
if (-not [IO.Path]::IsPathRooted($Path)) {
$Path = Join-Path "." $Path
}
# The join the directory name with the file name exluding the extension
[string]$Name = Join-Path ([IO.Path]::GetDirectoryName($Path)) ([IO.Path]::GetFileNameWithoutExtension($Path))
[string]$tmpfile = "$Name.sfx.tmp"
[string]$exefile = "$Name.exe"
if (Test-Path -PathType Leaf "$exefile") {
Remove-Item "$exefile" -Force
}
$filesToInclude = @()
}
Process {
$filesToInclude += $Include
}
End {
# Escape a variable for the config file
Function Escape([string]$t) {
# Prefix \ and " with \, replace CRLF with \n and TAB with \t
Return $t.Replace('\', '\\').Replace('"', '\"').Replace("`r`n", '\n').Replace("`t", '\t')
}
[string[]]$output = New-7zArchive -Path $tmpfile -Include $filesToInclude -Exclude @() -Type 7z -Recurse:$Recurse -Switches $Switches
# Copy sfx + archive + config to exe
<#
http://www.7zsfx.info/en/
Title - title for messages
BeginPrompt - Begin Prompt message
Progress - Value can be "yes" or "no". Default value is "yes".
RunProgram - Command for executing. Default value is "setup.exe". Substring %%T will be replaced with path to temporary folder, where files were extracted
Directory - Directory prefix for "RunProgram". Default value is ".\\"
ExecuteFile - Name of file for executing
ExecuteParameters - Parameters for "ExecuteFile"
ExtractTitle - title of extraction dialog
ExtractDialogText - text in dialog
ExtractCancelText - button text of cancel button
#>
[string]$cfg = @"
;!@Install@!UTF-8!
Title="$Title"
RunProgram="$(Escape($CommandToRun))"
"@
if ($BeginPrompt -ne "") {
$cfg += "BeginPrompt=`"$(Escape($BeginPrompt))`"`r`n"
}
if ($ExtractTitle -ne "") {
$cfg += "ExtractTitle=`"$(Escape($ExtractTitle))`"`r`n"
}
if ($ExtractDialogText -ne "") {
$cfg += "ExtractDialogText=`"$(Escape($ExtractDialogText))`"`r`n"
}
if ($ExtractCancelText -ne "") {
$cfg += "ExtractCancelText=`"$(Escape($ExtractCancelText))`"`r`n"
}
if ($ConfigOptions -ne $null) {
$ConfigOptions | ForEach-Object {
[string[]]$parts = $_.Split('=')
if ($parts.Length -lt 2) {
throw "Invalid configuration option '$($_)': missing '='"
} else {
$cfg += "$($parts[0])=`"$(Escape($parts[1]))`"`r`n"
}
}
}
$cfg += ";!@InstallEnd@!`r`n"
Write-Verbose "Creating sfx `"$exefile`"..."
Write-Debug $cfg
[string]$cfgfile = "$Name.sfx.cfg"
Set-Content "$cfgfile" -Value $cfg
Get-Content "$7Z_SFX","$cfgfile","$tmpfile" -Encoding Byte -Raw | Set-Content "$exefile" -Encoding Byte
Remove-Item "$tmpfile"
Remove-Item "$cfgfile"
}
}
Export-ModuleMember -Function *-7z*
@mortentanz
Copy link

This is one very useful module and thanks a lot for sharing. We had one minor issue, since we are dealing with (very) large files. We had to change the int specified for file length at line 382 to long to make Get-7zipArchive stop barking at us.

@proviste
Copy link

for some reason the break keyword at line 372 also break outside loops. So when I loop on multiple archives, only the first one is processed. As as very dirty fix I replace the foreach-object with a foreach($_ in $result) .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment