Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ImageResizer to MSIX installer and make the code MSIX-compatible #1219

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions installer/MSIX/PackagingLayout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
<File DestinationPath="modules\PowerRenameExt.dll" SourcePath="..\..\x64\Release\modules\PowerRenameExt.dll"/>
<File DestinationPath="modules\shortcut_guide.dll" SourcePath="..\..\x64\Release\modules\shortcut_guide.dll"/>
<File DestinationPath="modules\PowerRenameUWPUI.exe" SourcePath="..\..\x64\Release\PowerRenameUWPUI.exe"/>
<File DestinationPath="modules\ImageResizer.exe" SourcePath="..\..\x64\Release\ImageResizer.exe"/>
<File DestinationPath="modules\ImageResizerExt.dll" SourcePath="..\..\x64\Release\modules\ImageResizerExt.dll"/>
<File DestinationPath="modules\GalaSoft.MvvmLight.dll" SourcePath="..\..\x64\Release\GalaSoft.MvvmLight.dll"/>
<File DestinationPath="modules\GalaSoft.MvvmLight.Platform.dll" SourcePath="..\..\x64\Release\GalaSoft.MvvmLight.Platform.dll"/>
<File DestinationPath="modules\GalaSoft.MvvmLight.Extras.dll" SourcePath="..\..\x64\Release\GalaSoft.MvvmLight.Extras.dll"/>
<File DestinationPath="modules\System.Windows.Interactivity.dll" SourcePath="..\..\x64\Release\System.Windows.Interactivity.dll"/>

<File DestinationPath="svgs\*" SourcePath="..\..\x64\Release\svgs\*"/>
<File DestinationPath="settings-html\**" SourcePath="..\..\x64\Release\settings-html\**"/>
Expand Down
6 changes: 6 additions & 0 deletions installer/MSIX/appxmanifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
<com:ExeServer Executable="modules\PowerRenameUWPUI.exe" DisplayName="PowerRenameUWPUI">
<com:Class Id="0440049F-D1DC-4E46-B27B-98393D79486B"/>
</com:ExeServer>
<com:SurrogateServer DisplayName="ImageResizerExt">
<com:Class Id="51B4D7E5-7568-4234-B4BB-47FB3C016A69" Path="modules\ImageResizerExt.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
Expand All @@ -46,6 +49,9 @@
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="DirectoryPowerRename" Clsid="0440049F-D1DC-4E46-B27B-98393D79486B" />
</desktop5:ItemType>
<desktop4:ItemType Type="*">
<desktop4:Verb Id="ImageResizer" Clsid="51B4D7E5-7568-4234-B4BB-47FB3C016A69" />
</desktop4:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
</Extensions>
Expand Down
1 change: 0 additions & 1 deletion installer/PowerToysSetup/Product.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,6 @@
<File Source="$(var.BinX64Dir)\modules\ImageResizer.exe">
<netfx:NativeImage Id="ImageResizer.exe" Platform="all" Priority="0" />
</File>
<RegistryValue Root="HKLM" Key="Software\Microsoft\ImageResizer" Value="[ModulesInstallFolder]ImageResizer.exe" Type="string"/>
<File Source="$(var.BinX64Dir)\modules\GalaSoft.MvvmLight.dll" />
<File Source="$(var.BinX64Dir)\modules\GalaSoft.MvvmLight.Platform.dll" />
<File Source="$(var.BinX64Dir)\modules\GalaSoft.MvvmLight.Extras.dll" />
Expand Down
2 changes: 1 addition & 1 deletion installer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ For the first-time installation, you'll need to generate a self-signed certifica
4. Run `.\msix_reinstall.ps1` from the devenv powershell

#### What msix_reinstall.ps1 does
`msix_reinstall.ps1` removes the current PowerToys installation, restarts explorer.exe (to update PowerRename shell extension), builds `PowerToys-x64.msix` package, signs it with a PowerToys_TemporaryKey.pfx, and finally installs it.
`msix_reinstall.ps1` removes the current PowerToys installation, restarts explorer.exe (to update PowerRename and ImageResizer shell extension), builds `PowerToys-x64.msix` package, signs it with a PowerToys_TemporaryKey.pfx, and finally installs it.

#### Removing all .msi/.msix PowerToys installations
```ps
Expand Down
150 changes: 129 additions & 21 deletions src/modules/imageresizer/dll/ContextMenuHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ CContextMenuHandler::CContextMenuHandler()
{
m_pidlFolder = NULL;
m_pdtobj = NULL;
app_name = GET_RESOURCE_STRING(IDS_RESIZE_PICTURES);
}

CContextMenuHandler::~CContextMenuHandler()
Expand All @@ -35,6 +36,11 @@ HRESULT CContextMenuHandler::Initialize(_In_opt_ PCIDLIST_ABSOLUTE pidlFolder, _
{
Uninitialize();

if (!CSettings::GetEnabled())
{
return E_FAIL;
}

if (pidlFolder)
{
m_pidlFolder = ILClone(pidlFolder);
Expand Down Expand Up @@ -184,29 +190,24 @@ HRESULT CContextMenuHandler::InvokeCommand(_In_ CMINVOKECOMMANDINFO* pici)
{
if (wcscmp(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, RESIZE_PICTURES_VERBW) == 0)
{
return ResizePictures(pici);
return ResizePictures(pici, nullptr);
}
}
else if (LOWORD(pici->lpVerb) == ID_RESIZE_PICTURES)
{
return ResizePictures(pici);
return ResizePictures(pici, nullptr);
}

return E_FAIL;
}

// TODO: Error handling and memory management
HRESULT CContextMenuHandler::ResizePictures(CMINVOKECOMMANDINFO* pici)
// This function is used for both MSI and MSIX. If pici is null and psiItemArray is not null then this is called by Invoke(MSIX). If pici is not null and psiItemArray is null then this is called by InvokeCommand(MSI).
HRESULT CContextMenuHandler::ResizePictures(CMINVOKECOMMANDINFO* pici, IShellItemArray* psiItemArray)
{
// Set the application path from the registry
LPTSTR lpApplicationName = new TCHAR[MAX_PATH];
ULONG nChars = MAX_PATH;
CRegKey regKey;
// Open registry key saved by installer under HKLM
regKey.Open(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\ImageResizer"), KEY_READ | KEY_WOW64_64KEY);
regKey.QueryStringValue(NULL, lpApplicationName, &nChars);
regKey.Close();

// Set the application path based on the location of the dll
std::wstring path = get_module_folderpath(g_hInst_imageResizer);
path = path + L"\\ImageResizer.exe";
LPTSTR lpApplicationName = (LPTSTR)path.c_str();
// Create an anonymous pipe to stream filenames
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe;
Expand All @@ -220,7 +221,6 @@ HRESULT CContextMenuHandler::ResizePictures(CMINVOKECOMMANDINFO* pici)

CString commandLine;
commandLine.Format(_T("\"%s\""), lpApplicationName);
delete[] lpApplicationName;

// Set the output directory
if (m_pidlFolder)
Expand All @@ -240,7 +240,14 @@ HRESULT CContextMenuHandler::ResizePictures(CMINVOKECOMMANDINFO* pici)
startupInfo.cb = sizeof(STARTUPINFO);
startupInfo.hStdInput = hReadPipe;
startupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
startupInfo.wShowWindow = pici->nShow;
if (pici)
{
startupInfo.wShowWindow = pici->nShow;
}
else
{
startupInfo.wShowWindow = SW_SHOWNORMAL;
}

PROCESS_INFORMATION processInformation;

Expand All @@ -260,17 +267,118 @@ HRESULT CContextMenuHandler::ResizePictures(CMINVOKECOMMANDINFO* pici)
CloseHandle(processInformation.hProcess);
CloseHandle(processInformation.hThread);

// Stream the input files
HDropIterator i(m_pdtobj);
for (i.First(); !i.IsDone(); i.Next())
// psiItemArray is NULL if called from InvokeCommand. This part is used for the MSI installer. It is not NULL if it is called from Invoke (MSIX).
if (!psiItemArray)
{
CString fileName(i.CurrentItem());
fileName.Append(_T("\r\n"));
// Stream the input files
HDropIterator i(m_pdtobj);
for (i.First(); !i.IsDone(); i.Next())
{
CString fileName(i.CurrentItem());
fileName.Append(_T("\r\n"));

writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
}
}
else
{
//m_pdtobj will be NULL when invoked from the MSIX build as Initialize is never called (IShellExtInit functions aren't called in case of MSIX).
DWORD fileCount = 0;
// Gets the list of files currently selected using the IShellItemArray
psiItemArray->GetCount(&fileCount);
// Iterate over the list of files
for (DWORD i = 0; i < fileCount; i++)
{
IShellItem* shellItem;
psiItemArray->GetItemAt(i, &shellItem);
LPWSTR itemName;
// Retrieves the entire file system path of the file from its shell item
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &itemName);
CString fileName(itemName);
fileName.Append(_T("\r\n"));
// Write the file path into the input stream for image resizer
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
}
}

writePipe.Close();

return S_OK;
}

HRESULT __stdcall CContextMenuHandler::GetTitle(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszName)
{
return SHStrDup(app_name.c_str(), ppszName);
}

HRESULT __stdcall CContextMenuHandler::GetIcon(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszIcon)
{
// Since ImageResizer is registered as a COM SurrogateServer the current module filename would be dllhost.exe. To get the icon we need the path of ImageResizerExt.dll, which can be obtained by passing the HINSTANCE of the dll
std::wstring iconResourcePath = get_module_filename(g_hInst_imageResizer);
iconResourcePath += L",-";
iconResourcePath += std::to_wstring(IDI_RESIZE_PICTURES);
return SHStrDup(iconResourcePath.c_str(), ppszIcon);
}

HRESULT __stdcall CContextMenuHandler::GetToolTip(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszInfotip)
{
*ppszInfotip = nullptr;
return E_NOTIMPL;
}

HRESULT __stdcall CContextMenuHandler::GetCanonicalName(GUID* pguidCommandName)
{
*pguidCommandName = __uuidof(this);
return S_OK;
}

HRESULT __stdcall CContextMenuHandler::GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState)
{
if (!CSettings::GetEnabled())
{
*pCmdState = ECS_HIDDEN;
return S_OK;
}
// Hide if the file is not an image
*pCmdState = ECS_HIDDEN;
// Suppressing C26812 warning as the issue is in the shtypes.h library
#pragma warning(suppress : 26812)
PERCEIVED type;
PERCEIVEDFLAG flag;
IShellItem* shellItem;
//Check extension of first item in the list (the item which is right-clicked on)
psiItemArray->GetItemAt(0, &shellItem);
LPTSTR pszPath;
// Retrieves the entire file system path of the file from its shell item
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
LPTSTR pszExt = PathFindExtension(pszPath);

// TODO: Instead, detect whether there's a WIC codec installed that can handle this file
AssocGetPerceivedType(pszExt, &type, &flag, NULL);

free(pszPath);
// If selected file is an image...
if (type == PERCEIVED_TYPE_IMAGE)
{
*pCmdState = ECS_ENABLED;
}
return S_OK;
}

HRESULT __stdcall CContextMenuHandler::GetFlags(EXPCMDFLAGS* pFlags)
{
*pFlags = ECF_DEFAULT;
return S_OK;
}

HRESULT __stdcall CContextMenuHandler::EnumSubCommands(IEnumExplorerCommand** ppEnum)
{
*ppEnum = nullptr;
return E_NOTIMPL;
}

// psiItemArray contains the list of files that have been selected when the context menu entry is invoked
HRESULT __stdcall CContextMenuHandler::Invoke(IShellItemArray* psiItemArray, IBindCtx* /*pbc*/)
{
return ResizePictures(nullptr, psiItemArray);
}
19 changes: 16 additions & 3 deletions src/modules/imageresizer/dll/ContextMenuHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@

using namespace ATL;

class ATL_NO_VTABLE CContextMenuHandler :
class ATL_NO_VTABLE __declspec(uuid("51B4D7E5-7568-4234-B4BB-47FB3C016A69")) CContextMenuHandler :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CContextMenuHandler, &CLSID_ContextMenuHandler>,
public IShellExtInit,
public IContextMenu
public IContextMenu,
public IExplorerCommand
{
BEGIN_COM_MAP(CContextMenuHandler)
COM_INTERFACE_ENTRY(IShellExtInit)
COM_INTERFACE_ENTRY(IContextMenu)
COM_INTERFACE_ENTRY(IExplorerCommand)
END_COM_MAP()
DECLARE_REGISTRY_RESOURCEID(IDR_CONTEXTMENUHANDLER)
DECLARE_NOT_AGGREGATABLE(CContextMenuHandler)
Expand All @@ -33,12 +35,23 @@ class ATL_NO_VTABLE CContextMenuHandler :
HRESULT STDMETHODCALLTYPE GetCommandString(UINT_PTR idCmd, UINT uType, _In_ UINT* pReserved, LPSTR pszName, UINT cchMax);
HRESULT STDMETHODCALLTYPE InvokeCommand(_In_ CMINVOKECOMMANDINFO* pici);

// Inherited via IExplorerCommand
virtual HRESULT __stdcall GetTitle(IShellItemArray* psiItemArray, LPWSTR* ppszName) override;
virtual HRESULT __stdcall GetIcon(IShellItemArray* psiItemArray, LPWSTR* ppszIcon) override;
virtual HRESULT __stdcall GetToolTip(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip) override;
virtual HRESULT __stdcall GetCanonicalName(GUID* pguidCommandName) override;
virtual HRESULT __stdcall GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) override;
virtual HRESULT __stdcall Invoke(IShellItemArray* psiItemArray, IBindCtx* pbc) override;
virtual HRESULT __stdcall GetFlags(EXPCMDFLAGS* pFlags) override;
virtual HRESULT __stdcall EnumSubCommands(IEnumExplorerCommand** ppEnum) override;

private:
void Uninitialize();
HRESULT ResizePictures(CMINVOKECOMMANDINFO* pici);
HRESULT ResizePictures(CMINVOKECOMMANDINFO* pici, IShellItemArray* psiItemArray);
PCIDLIST_ABSOLUTE m_pidlFolder;
IDataObject* m_pdtobj;
HBITMAP m_hbmpIcon = nullptr;
std::wstring app_name;
};

OBJECT_ENTRY_AUTO(__uuidof(ContextMenuHandler), CContextMenuHandler)
2 changes: 2 additions & 0 deletions src/modules/imageresizer/dll/stdafx.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
#include "targetver.h"
#include "resource.h"

#include <winrt/base.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlfile.h>
#include <atlstr.h>
#include <windows.h>

#include <ShlObj.h>