7-Zip: conditional extract here/extract to subfolder based on count of items in root of archive

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

7-Zip: conditional extract here/extract to subfolder based on count of items in root of archive

07 Nov 2017, 18:13

A function to extract an archive to a folder or to a subfolder based on its contents.

7-Zip has two very useful right-click menu options for archives:
- Extract Here
- Extract to "MyZip\" (i.e. extract to subfolder)

- If an archive contains one file/folder only (in the root), I would like it to do Extract Here.
- If an archive contains multiple files and/or folders (in the root), I would like it to do Extract to subfolder.

- If you always do Extract Here, you could end up with a mess if the zip contains lots of loose files not within a folder.
- If you always do Extract to subfolder, but everything is already neatly contained within one folder, you end up with an unnecessary extra folder level, so you have to move everything up one level, delete the unnecessary folder, and possibly rename the top-level folder.

This same problem is mentioned here:
7-Zip / Discussion / Open Discussion:Feature Req: Conditional extract-to-folder
https://sourceforge.net/p/sevenzip/disc ... /efcad42a/

7-Zip is one of the best utilities available, but this problem really needs to be fixed.

This may be the only time in human history that 'smart' behaviour in an IT context would be a good idea.

One solution would be to count the number of files and folders in the root, but I was unable to do this reliably via the 7-Zip command line because there appear to be some limitations in 7-Zip's syntax for listing files and folders, but also I found some inconsistencies in what 7-Zip would list for different archives, with no clear pattern.

Here's an attempt:

Code: Select all

;l (List contents of archive) command
;https://sevenzip.osdn.jp/chm/cmdline/commands/list.htm
;7-Zip / Discussion / Open Discussion:Listing only the top level of a ZIP file?
;https://sourceforge.net/p/sevenzip/discussion/45797/thread/843b2e18/
;7-Zip / Discussion / Open Discussion:Feature Req: Conditional extract-to-folder
;https://sourceforge.net/p/sevenzip/discussion/45797/thread/efcad42a/

;Index of /download/1.1
;https://autohotkey.com/download/1.1/
;AutoHotkey_1.1.26.01.zip
;https://autohotkey.com/download/1.1/AutoHotkey_1.1.26.01.zip

q:: ;7-Zip - attempt to list files and folders in the root
vPathZip = %A_Desktop%\AutoHotkey_1.1.26.01.zip

vPathExe = C:\Program Files\7-Zip\7z.exe
vTarget = "%vPathExe%" l "%vPathZip%"
vSfx := " -x!*\*"
;vSfx := " -x!*\"
vText := JEE_RunGetStdOut(vTarget vSfx)
MsgBox, % vText
vCount := vCountDir := vCountFile := 0
Loop, Parse, vText, `n
{
	if (SubStr(A_LoopField, 1, 19) = "-------------------")
		vCount++
	else if (vCount = 1)
		if (SubStr(A_LoopField, 21, 1) = "D")
			vCountDir++
		else
			vCountFile++
}
MsgBox, % vCountDir " " vCountFile
return

;==================================================

JEE_RunGetStdOut(vTarget, vSize:="")
{
	DetectHiddenWindows, On
	vComSpec := A_ComSpec ? A_ComSpec : ComSpec
	Run, % vComSpec,, Hide, vPID
	WinWait, % "ahk_pid " vPID
	DllCall("kernel32\AttachConsole", UInt,vPID)
	oShell := ComObjCreate("WScript.Shell")
	oExec := oShell.Exec(vTarget)
	vStdOut := ""
	if !(vSize = "")
		VarSetCapacity(vStdOut, vSize)
	while !oExec.StdOut.AtEndOfStream
		vStdOut := oExec.StdOut.ReadAll()
	DllCall("kernel32\FreeConsole")
	Process, Close, % vPID
	return vStdOut
}
;==================================================
It was a long time since I checked this, but it may be that even if you tell 7-Zip to list every file in the archive, this doesn't always succeed, plus there may be issues if there are too many files/folders in the archive. But I am uncertain of the facts on both of these points.

So, an alternative solution is to always extract to a temporary folder, then count the files/folders, then move to the folder, or to a subfolder, as appropriate.

A function with an example.

Code: Select all

q:: ;7-Zip - extract to folder/subfolder based on the number of items inside the archive
vPathZip = %A_Desktop%\MyZip.zip
vDirDest = %A_Desktop%\%A_Now%

vPathExe := "C:\Program Files\7-Zip\7z.exe"
vDirTemp = %A_Desktop%\z 7-zip temp
vRet := JEE_7ZipExtractEx(vPathZip, vDirDest, vPathExe, vDirTemp, vPathDest)
MsgBox, % vRet " " vPathDest

vComSpec := A_ComSpec ? A_ComSpec : ComSpec
vAttrib := FileExist(vPathDest)
;if the zip contained one file at its root, extract the file and perform Open Containing Folder
;if the zip contained one folder at its root, extract the folder and open that folder
;if the zip contained multiple files/folders at its root, extract them to a folder, and open that folder
if !vAttrib
	return
else if InStr(vAttrib, "D")
	Run, % vPathDest
else
	Run, %vComSpec% /c explorer.exe /select`, "%vPathDest%",, Hide
return
*/

;if zip contains 1 file/folder only in its root, extract directly
;if zip contains multiple files/folders in its root, extract to subfolder
;extract file/folder based on number of items inside archive
JEE_7ZipExtractEx(vPathZip, vDirDest, vPathExe, vDirTemp, ByRef vPathDest)
{
	vPathDest := ""
	if !FileExist(vPathZip) || !FileExist(vPathExe)
		return 0
	if (vDirDest = "")
		SplitPath, vPathZip,, vDirDest
	vDirTemp .= "\X"
	if FileExist(vDirTemp)
		FileRecycle, % vDirTemp
		;FileDelete, % vDirTemp
	if FileExist(vDirTemp)
		return 0
	FileCreateDir, % vDirTemp
	if !InStr(FileExist(vDirTemp), "D")
		return 0
	if !FileExist(vDirDest)
		FileCreateDir, % vDirDest
	if !InStr(FileExist(vDirDest), "D")
		return 0

	;-y (assume Yes on all queries)
	vTarget = "%vPathExe%" x "%vPathZip%" -o"%vDirTemp%" -y
	RunWait, % vTarget,, Hide

	vCount := 0, vIsDir := 1
	Loop, Files, % vDirTemp "\*", FD
	{
		vPath := A_LoopFileFullPath
		vName := A_LoopFileName
		vCount := A_Index
		if (A_Index = 2)
			break
	}
	if (vCount = 0)
		return 0
	else if (vCount = 1) ;move the 1 item contained in the zip
	{
		vPathDest := vDirDest "\" vName
		if FileExist(vPathDest)
			return 0
		else if InStr(FileExist(vPath), "D")
			FileMoveDir, % vPath, % vPathDest
		else
			FileMove, % vPath, % vPathDest
	}
	else ;create a folder for the multiple items contained in the zip
	{
		SplitPath, vPathZip,,,, vNameNoExtZip
		vPathDest := vDirDest "\" vNameNoExtZip
		if FileExist(vPathDest)
			return 0
		FileMoveDir, % vDirTemp, % vPathDest
	}
	return !!FileExist(vPathDest)
}
A stand-alone script which adds itself to the SendTo menu, by creating a shortcut in the SendTo folder.

Code: Select all

;'7-Zip extract.ahk' by jeeswg
;extract to folder/subfolder based on the number of items inside the archive

#NoTrayIcon

vPathZip = %1%
SplitPath, vPathZip,, vDirDest

;if no path specified, add a shortcut to the SendTo folder
if (vPathZip = "")
{

	vName := "7-Zip extract.lnk"
	vPathLnk = %A_AppData%\Microsoft\Windows\SendTo\%vName%
	MsgBox, % "creating shortcut: " vPathLnk
	FileCreateShortcut, % A_AhkPath, % vPathLnk,, "%A_ScriptFullPath%"
	return
}

vPathExe := "C:\Program Files\7-Zip\7z.exe"
vDirTemp = %A_Desktop%\z 7-zip temp
vRet := JEE_7ZipExtractEx(vPathZip, vDirDest, vPathExe, vDirTemp, vPathDest)
MsgBox, % vRet " " vPathDest
vComSpec := A_ComSpec ? A_ComSpec : ComSpec

vAttrib := FileExist(vPathDest)
;if the zip contained one file, extract the file and perform Open Containing Folder
;if the zip contained one folder at its root, extract the folder and open that folder
;if the zip contained multiple files/folders at its root, extract them to a folder, and open that folder
if !vAttrib
	return
else if InStr(vAttrib, "D")
	Run, % vPathDest
else
	Run, %vComSpec% /c explorer.exe /select`, "%vPathDest%",, Hide
return

;==================================================

;if zip contains 1 file/folder only in its root, extract directly
;if zip contains multiple files/folders in its root, extract to subfolder
;extract file/folder based on number of items inside archive
JEE_7ZipExtractEx(vPathZip, vDirDest, vPathExe, vDirTemp, ByRef vPathDest)
{
	vPathDest := ""
	if !FileExist(vPathZip) || !FileExist(vPathExe)
		return 0
	if (vDirDest = "")
		SplitPath, vPathZip,, vDirDest
	vDirTemp .= "\X"
	if FileExist(vDirTemp)
		FileRecycle, % vDirTemp
		;FileDelete, % vDirTemp
	if FileExist(vDirTemp)
		return 0
	FileCreateDir, % vDirTemp
	if !InStr(FileExist(vDirTemp), "D")
		return 0
	if !FileExist(vDirDest)
		FileCreateDir, % vDirDest
	if !InStr(FileExist(vDirDest), "D")
		return 0

	;-y (assume Yes on all queries)
	vTarget = "%vPathExe%" x "%vPathZip%" -o"%vDirTemp%" -y
	RunWait, % vTarget,, Hide

	vCount := 0, vIsDir := 1
	Loop, Files, % vDirTemp "\*", FD
	{
		vPath := A_LoopFileFullPath
		vName := A_LoopFileName
		vCount := A_Index
		if (A_Index = 2)
			break
	}
	if (vCount = 0)
		return 0
	else if (vCount = 1) ;move the 1 item contained in the zip
	{
		vPathDest := vDirDest "\" vName
		if FileExist(vPathDest)
			return 0
		else if InStr(FileExist(vPath), "D")
			FileMoveDir, % vPath, % vPathDest
		else
			FileMove, % vPath, % vPathDest
	}
	else ;create a folder for the multiple items contained in the zip
	{
		SplitPath, vPathZip,,,, vNameNoExtZip
		vPathDest := vDirDest "\" vNameNoExtZip
		if FileExist(vPathDest)
			return 0
		FileMoveDir, % vDirTemp, % vPathDest
	}
	return !!FileExist(vPathDest)
}

;==================================================
Thanks for reading. Any comments are welcome.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 42 guests