How to extract files from an archive file (zip, 7z, rar, etc.) on recent versions of Windows 11 without any 3rd party library
See the question and my original answer on StackOverflowHere is some sample code (using ATL from Visual Studio for simplicity) that can extract files from an archive file (zip, 7z, rar, etc.) on recent versions of Windows 11.
However some remarks:
- It uses an undocumented interface
ITransferSource2
(which IMHO should be documented). It should supportIStream
orIStorage
orITransferSource
'sMoveItem
but it doesn't seem to work. - It can be very slow on some formats, for example .7z and .rar. Actually, it should run more or less at the same speed as the Windows Shell UI itself (my experience shows that copy-pasting a .7z file takes very long time vs 7Zip itself). This reduces considerably the interest for this feature IMHO.
#include <windows.h>
#include <atlbase.h>
#include <shobjidl_core.h>
#include <shlguid.h>
// this is an undocumented interface
DECLARE_INTERFACE_IID_(ITransferSource2, ITransferSource, "D6B78E20-B98A-49FA-AE7E-2BA7FCE522F5")
{
public:
virtual HRESULT WINAPI IsCopySupported(IShellItem* psiSource, IShellItem* psiDest, BOOL* fSupported) = 0;
virtual HRESULT WINAPI CopyItem(IShellItem* psiSource, IShellItem* psiDest, PCWSTR pszNameDst, TRANSFER_SOURCE_FLAGS flags, int, IShellItem**) = 0;
virtual HRESULT WINAPI LastCopyError(int*) = 0;
};
void ExtractItemToTarget(IShellItem* item, IShellItem* target, bool recursive)
{
// enumerate item's children
CComPtr<IEnumShellItems> items;
ATLASSERT(SUCCEEDED(item->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&items))));
do
{
CComPtr<IShellItem> child;
items->Next(1, &child, 0);
if (!child)
break;
// get relative name
CComHeapPtr<wchar_t> name;
ATLASSERT(SUCCEEDED(child->GetDisplayName(SIGDN_PARENTRELATIVEPARSING, &name)));
// get full path for display
CComHeapPtr<wchar_t> path;
ATLASSERT(SUCCEEDED(child->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path)));
wprintf(L"Processing '%s'...\n", path.m_pData);
// folder or item?
SFGAOF folder = 0;
if (SUCCEEDED(child->GetAttributes(SFGAO_FOLDER, &folder)) && folder)
{
if (recursive)
{
CComPtr<ITransferDestination> destination;
ATLASSERT(SUCCEEDED(target->BindToHandler(nullptr, BHID_Transfer, IID_PPV_ARGS(&destination))));
// ensure directory exists. we create a directory even for an empty folder
CComPtr<IShellItem> childTarget;
CComPtr<IShellItemResources> resources;
ATLASSERT(SUCCEEDED(destination->CreateItem(name, FILE_ATTRIBUTE_DIRECTORY, 0, TSF_OVERWRITE_EXIST, IID_PPV_ARGS(&childTarget), IID_PPV_ARGS(&resources))));
// go deep down
ExtractItemToTarget(child, childTarget, recursive);
}
}
else
{
// extract file (here we overwrite)
CComPtr<ITransferSource> source;
ATLASSERT(SUCCEEDED(item->BindToHandler(nullptr, BHID_Transfer, IID_PPV_ARGS(&source))));
CComPtr<ITransferSource2> source2;
ATLASSERT(SUCCEEDED(source.QueryInterface(&source2)));
CComPtr<IShellItem> newItem;
ATLASSERT(SUCCEEDED(source2->CopyItem(child, target, name, TSF_OVERWRITE_EXIST, 0, &newItem)));
}
child.Release();
} while (true);
}
int main()
{
ATLASSERT(SUCCEEDED(CoInitialize(nullptr)));
{
// should support zip 7z (slow!) gz bz2 tar rar (slow!) tgz tbz2 tzst txz zst xz
CComPtr<IShellItem> file;
ATLASSERT(SUCCEEDED(SHCreateItemFromParsingName(L"c:\\mypath\\my.zip", nullptr, IID_PPV_ARGS(&file))));
// target path, create directory
auto targetPath = L"c:\\target";
CreateDirectory(targetPath, nullptr);
CComPtr<IShellItem> target;
ATLASSERT(SUCCEEDED(SHCreateItemFromParsingName(targetPath, nullptr, IID_PPV_ARGS(&target))));
// extract file into target folder
ExtractItemToTarget(file, target, true);
}
CoUninitialize();
return 0;
}