diff --git a/PowerToys.sln b/PowerToys.sln
index b7817c2918..68b6454c54 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -49,6 +49,34 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests-CommonLib", "src\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZonesEditor", "src\modules\fancyzones\editor\FancyZonesEditor\FancyZonesEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "powerrename", "powerrename", "{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameExt", "src\modules\powerrename\dll\PowerRenameExt.vcxproj", "{B25AC7A5-FB9F-4789-B392-D5C85E948670}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798}
+ {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameLib", "src\modules\powerrename\lib\PowerRenameLib.vcxproj", "{51920F1F-C28C-4ADF-8660-4238766796C2}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUI", "src\modules\powerrename\ui\PowerRenameUI.vcxproj", "{0E072714-D127-460B-AFAD-B4C40B412798}"
+ ProjectSection(ProjectDependencies) = postProject
+ {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameTest", "src\modules\powerrename\testapp\PowerRenameTest.vcxproj", "{A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798}
+ {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUnitTests", "src\modules\powerrename\unittests\PowerRenameLibUnitTests.vcxproj", "{2151F984-E006-4A9F-92EF-C6DDE3DC8413}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798}
+ {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
+ {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {B25AC7A5-FB9F-4789-B392-D5C85E948670}
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -97,6 +125,26 @@ Global
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.Build.0 = Debug|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.ActiveCfg = Release|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.Build.0 = Release|x64
+ {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|x64.ActiveCfg = Debug|x64
+ {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|x64.Build.0 = Debug|x64
+ {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Release|x64.ActiveCfg = Release|x64
+ {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Release|x64.Build.0 = Release|x64
+ {51920F1F-C28C-4ADF-8660-4238766796C2}.Debug|x64.ActiveCfg = Debug|x64
+ {51920F1F-C28C-4ADF-8660-4238766796C2}.Debug|x64.Build.0 = Debug|x64
+ {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.ActiveCfg = Release|x64
+ {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.Build.0 = Release|x64
+ {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x64.ActiveCfg = Debug|x64
+ {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x64.Build.0 = Debug|x64
+ {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x64.ActiveCfg = Release|x64
+ {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x64.Build.0 = Release|x64
+ {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.ActiveCfg = Debug|x64
+ {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.Build.0 = Debug|x64
+ {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Release|x64.ActiveCfg = Release|x64
+ {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Release|x64.Build.0 = Release|x64
+ {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Debug|x64.ActiveCfg = Debug|x64
+ {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Debug|x64.Build.0 = Debug|x64
+ {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Release|x64.ActiveCfg = Release|x64
+ {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -113,6 +161,12 @@ Global
{4E577735-DFAB-41AF-8A6E-B6E8872A2928} = {1FAF749F-0D6F-4BF5-A563-31A4B5279D27}
{1A066C63-64B3-45F8-92FE-664E1CCE8077} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
+ {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
+ {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
+ {51920F1F-C28C-4ADF-8660-4238766796C2} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
+ {0E072714-D127-460B-AFAD-B4C40B412798} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
+ {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
+ {2151F984-E006-4A9F-92EF-C6DDE3DC8413} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs
index 33a5d28cf2..3d796e8303 100644
--- a/installer/PowerToysSetup/Product.wxs
+++ b/installer/PowerToysSetup/Product.wxs
@@ -232,6 +232,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -272,6 +283,7 @@
+
diff --git a/src/modules/powerrename/README.md b/src/modules/powerrename/README.md
new file mode 100644
index 0000000000..0e191fafc5
--- /dev/null
+++ b/src/modules/powerrename/README.md
@@ -0,0 +1,2 @@
+# PowerRename
+A Windows Shell Extension for more advanced renaming using search and replace or regular expressions
diff --git a/src/modules/powerrename/dll/PowerRenameExt.cpp b/src/modules/powerrename/dll/PowerRenameExt.cpp
new file mode 100644
index 0000000000..4e9c7ea4c9
--- /dev/null
+++ b/src/modules/powerrename/dll/PowerRenameExt.cpp
@@ -0,0 +1,147 @@
+#include "stdafx.h"
+#include "PowerRenameExt.h"
+#include
+#include
+#include
+#include "resource.h"
+
+extern HINSTANCE g_hInst;
+
+HWND g_hwndParent = 0;
+
+
+const wchar_t powerRenameRegPath[] = L"Software\\Microsoft\\PowerRename";
+const wchar_t powerRenameRegEnabledName[] = L"Enabled";
+
+bool CPowerRenameMenu::IsEnabled()
+{
+ DWORD type = REG_DWORD;
+ DWORD dwEnabled = 0;
+ DWORD cb = sizeof(dwEnabled);
+ SHGetValue(HKEY_CURRENT_USER, powerRenameRegPath, powerRenameRegEnabledName, &type, &dwEnabled, &cb);
+ return (dwEnabled == 0) ? false : true;
+}
+
+bool CPowerRenameMenu::SetEnabled(_In_ bool enabled)
+{
+ DWORD dwEnabled = enabled ? 1 : 0;
+ return SUCCEEDED(HRESULT_FROM_WIN32(SHSetValueW(HKEY_CURRENT_USER, powerRenameRegPath, powerRenameRegEnabledName, REG_DWORD, &dwEnabled, sizeof(dwEnabled))));
+}
+
+CPowerRenameMenu::CPowerRenameMenu()
+{
+ DllAddRef();
+}
+
+CPowerRenameMenu::~CPowerRenameMenu()
+{
+ m_spdo = nullptr;
+ DllRelease();
+}
+
+HRESULT CPowerRenameMenu::s_CreateInstance(_In_opt_ IUnknown*, _In_ REFIID riid, _Outptr_ void **ppv)
+{
+ *ppv = nullptr;
+ HRESULT hr = E_OUTOFMEMORY;
+ CPowerRenameMenu *pprm = new CPowerRenameMenu();
+ if (pprm)
+ {
+ hr = pprm->QueryInterface(riid, ppv);
+ pprm->Release();
+ }
+ return hr;
+}
+
+// IShellExtInit
+HRESULT CPowerRenameMenu::Initialize(_In_opt_ PCIDLIST_ABSOLUTE, _In_ IDataObject *pdtobj, HKEY)
+{
+ // Check if we have disabled ourselves
+ if (!IsEnabled())
+ return E_FAIL;
+
+ // Cache the data object to be used later
+ m_spdo = pdtobj;
+ return S_OK;
+}
+
+// IContextMenu
+HRESULT CPowerRenameMenu::QueryContextMenu(HMENU hMenu, UINT index, UINT uIDFirst, UINT, UINT uFlags)
+{
+ // Check if we have disabled ourselves
+ if (!IsEnabled())
+ return E_FAIL;
+
+ HRESULT hr = E_UNEXPECTED;
+ if (m_spdo)
+ {
+ if ((uFlags & ~CMF_OPTIMIZEFORINVOKE) && (uFlags & ~(CMF_DEFAULTONLY | CMF_VERBSONLY)))
+ {
+ wchar_t menuName[64] = { 0 };
+ LoadString(g_hInst, IDS_POWERRENAME, menuName, ARRAYSIZE(menuName));
+ InsertMenu(hMenu, index, MF_STRING | MF_BYPOSITION, uIDFirst++, menuName);
+ hr = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
+ }
+ }
+
+ return hr;
+}
+
+HRESULT CPowerRenameMenu::InvokeCommand(_In_ LPCMINVOKECOMMANDINFO pici)
+{
+ // Check if we have disabled ourselves
+ if (!IsEnabled())
+ return E_FAIL;
+
+ HRESULT hr = E_FAIL;
+
+ if ((IS_INTRESOURCE(pici->lpVerb)) &&
+ (LOWORD(pici->lpVerb) == 0))
+ {
+ IStream* pstrm = nullptr;
+ if (SUCCEEDED(CoMarshalInterThreadInterfaceInStream(__uuidof(m_spdo), m_spdo, &pstrm)))
+ {
+ if (!SHCreateThread(s_PowerRenameUIThreadProc, pstrm, CTF_COINIT | CTF_PROCESS_REF, nullptr))
+ {
+ pstrm->Release(); // if we failed to create the thread, then we must release the stream
+ }
+ }
+ }
+
+ return hr;
+}
+
+DWORD WINAPI CPowerRenameMenu::s_PowerRenameUIThreadProc(_In_ void* pData)
+{
+ IStream* pstrm = static_cast(pData);
+ CComPtr spdo;
+ if (SUCCEEDED(CoGetInterfaceAndReleaseStream(pstrm, IID_PPV_ARGS(&spdo))))
+ {
+ // Create the smart rename manager
+ CComPtr spsrm;
+ if (SUCCEEDED(CPowerRenameManager::s_CreateInstance(&spsrm)))
+ {
+ // Create the factory for our items
+ CComPtr spsrif;
+ if (SUCCEEDED(CPowerRenameItem::s_CreateInstance(nullptr, IID_PPV_ARGS(&spsrif))))
+ {
+ // Pass the factory to the manager
+ if (SUCCEEDED(spsrm->put_smartRenameItemFactory(spsrif)))
+ {
+ // Create the smart rename UI instance and pass the smart rename manager
+ CComPtr spsrui;
+ if (SUCCEEDED(CPowerRenameUI::s_CreateInstance(spsrm, spdo, false, &spsrui)))
+ {
+ // Call blocks until we are done
+ spsrui->Show();
+ spsrui->Close();
+ }
+ }
+ }
+
+ // Need to call shutdown to break circular dependencies
+ spsrm->Shutdown();
+ }
+ }
+
+ return 0;
+}
diff --git a/src/modules/powerrename/dll/PowerRenameExt.def b/src/modules/powerrename/dll/PowerRenameExt.def
new file mode 100644
index 0000000000..52302809ab
--- /dev/null
+++ b/src/modules/powerrename/dll/PowerRenameExt.def
@@ -0,0 +1,5 @@
+EXPORTS
+ DllGetClassObject PRIVATE
+ DllCanUnloadNow PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
\ No newline at end of file
diff --git a/src/modules/powerrename/dll/PowerRenameExt.h b/src/modules/powerrename/dll/PowerRenameExt.h
new file mode 100644
index 0000000000..cefbab5455
--- /dev/null
+++ b/src/modules/powerrename/dll/PowerRenameExt.h
@@ -0,0 +1,61 @@
+#pragma once
+#include "stdafx.h"
+
+class CPowerRenameMenu :
+ public IShellExtInit,
+ public IContextMenu
+{
+public:
+ CPowerRenameMenu();
+
+ // IUnknown
+ IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void** ppv)
+ {
+ static const QITAB qit[] =
+ {
+ QITABENT(CPowerRenameMenu, IShellExtInit),
+ QITABENT(CPowerRenameMenu, IContextMenu),
+ { 0, 0 },
+ };
+ return QISearch(this, qit, riid, ppv);
+ }
+
+ IFACEMETHODIMP_(ULONG) AddRef()
+ {
+ return InterlockedIncrement(&m_refCount);
+ }
+
+ IFACEMETHODIMP_(ULONG) Release()
+ {
+ LONG refCount = InterlockedDecrement(&m_refCount);
+ if (refCount == 0)
+ {
+ delete this;
+ }
+ return refCount;
+ }
+
+ // IShellExtInit
+ STDMETHODIMP Initialize(_In_opt_ PCIDLIST_ABSOLUTE pidlFolder, _In_ IDataObject* pdto, HKEY hkProgID);
+
+ // IContextMenu
+ STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT index, UINT uIDFirst, UINT uIDLast, UINT uFlags);
+ STDMETHODIMP InvokeCommand(_In_ LPCMINVOKECOMMANDINFO pCMI);
+ STDMETHODIMP GetCommandString(UINT_PTR, UINT, _In_opt_ UINT*, _In_ LPSTR, UINT)
+ {
+ return E_NOTIMPL;
+ }
+
+ static HRESULT s_CreateInstance(_In_opt_ IUnknown* punkOuter, _In_ REFIID riid, _Outptr_ void** ppv);
+ static DWORD WINAPI s_PowerRenameUIThreadProc(_In_ void* pData);
+
+ static bool SetEnabled(_In_ bool enabled);
+ static bool IsEnabled();
+
+private:
+ ~CPowerRenameMenu();
+
+ long m_refCount = 1;
+ CComPtr m_spdo;
+};
+
diff --git a/src/modules/powerrename/dll/PowerRenameExt.rc b/src/modules/powerrename/dll/PowerRenameExt.rc
new file mode 100644
index 0000000000..8b0eb6a61b
--- /dev/null
+++ b/src/modules/powerrename/dll/PowerRenameExt.rc
@@ -0,0 +1,111 @@
+ÿþ/ / M i c r o s o f t V i s u a l C + + g e n e r a t e d r e s o u r c e s c r i p t .
+ / /
+ # i n c l u d e " r e s o u r c e . h "
+
+ # d e f i n e A P S T U D I O _ R E A D O N L Y _ S Y M B O L S
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / /
+ / / G e n e r a t e d f r o m t h e T E X T I N C L U D E 2 r e s o u r c e .
+ / /
+ # i n c l u d e " w i n r e s . h "
+
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ # u n d e f A P S T U D I O _ R E A D O N L Y _ S Y M B O L S
+
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / / E n g l i s h ( U n i t e d S t a t e s ) r e s o u r c e s
+
+ # i f ! d e f i n e d ( A F X _ R E S O U R C E _ D L L ) | | d e f i n e d ( A F X _ T A R G _ E N U )
+ L A N G U A G E L A N G _ E N G L I S H , S U B L A N G _ E N G L I S H _ U S
+
+ # i f d e f A P S T U D I O _ I N V O K E D
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / /
+ / / T E X T I N C L U D E
+ / /
+
+ 1 T E X T I N C L U D E
+ B E G I N
+ " r e s o u r c e . h \ 0 "
+ E N D
+
+ 2 T E X T I N C L U D E
+ B E G I N
+ " # i n c l u d e " " w i n r e s . h " " \ r \ n "
+ " \ 0 "
+ E N D
+
+ 3 T E X T I N C L U D E
+ B E G I N
+ " \ r \ n "
+ " \ 0 "
+ E N D
+
+ # e n d i f / / A P S T U D I O _ I N V O K E D
+
+
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / /
+ / / V e r s i o n
+ / /
+
+ V S _ V E R S I O N _ I N F O V E R S I O N I N F O
+ F I L E V E R S I O N 1 , 0 , 0 , 1
+ P R O D U C T V E R S I O N 1 , 0 , 0 , 1
+ F I L E F L A G S M A S K 0 x 3 f L
+ # i f d e f _ D E B U G
+ F I L E F L A G S 0 x 1 L
+ # e l s e
+ F I L E F L A G S 0 x 0 L
+ # e n d i f
+ F I L E O S 0 x 4 0 0 0 4 L
+ F I L E T Y P E 0 x 2 L
+ F I L E S U B T Y P E 0 x 0 L
+ B E G I N
+ B L O C K " S t r i n g F i l e I n f o "
+ B E G I N
+ B L O C K " 0 4 0 9 0 4 b 0 "
+ B E G I N
+ V A L U E " C o m p a n y N a m e " , " M i c r o s o f t C o r p . "
+ V A L U E " F i l e D e s c r i p t i o n " , " P o w e r R e n a m e P o w e r T o y "
+ V A L U E " F i l e V e r s i o n " , " 1 . 0 . 0 . 1 "
+ V A L U E " I n t e r n a l N a m e " , " P o w e r R e n a m e E x t . d l l "
+ V A L U E " L e g a l C o p y r i g h t " , " C o p y r i g h t ( C ) 2 0 1 9 "
+ V A L U E " O r i g i n a l F i l e n a m e " , " P o w e r R e n a m e E x t . d l l "
+ V A L U E " P r o d u c t N a m e " , " P o w e r R e n a m e P o w e r T o y "
+ V A L U E " P r o d u c t V e r s i o n " , " 1 . 0 . 0 . 1 "
+ E N D
+ E N D
+ B L O C K " V a r F i l e I n f o "
+ B E G I N
+ V A L U E " T r a n s l a t i o n " , 0 x 4 0 9 , 1 2 0 0
+ E N D
+ E N D
+
+
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / /
+ / / S t r i n g T a b l e
+ / /
+
+ S T R I N G T A B L E
+ B E G I N
+ I D S _ P O W E R R E N A M E " P o w e r R e n a m e "
+ E N D
+
+ # e n d i f / / E n g l i s h ( U n i t e d S t a t e s ) r e s o u r c e s
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+
+
+
+ # i f n d e f A P S T U D I O _ I N V O K E D
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / /
+ / / G e n e r a t e d f r o m t h e T E X T I N C L U D E 3 r e s o u r c e .
+ / /
+
+
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ # e n d i f / / n o t A P S T U D I O _ I N V O K E D
+
+
\ No newline at end of file
diff --git a/src/modules/powerrename/dll/PowerRenameExt.vcxproj b/src/modules/powerrename/dll/PowerRenameExt.vcxproj
new file mode 100644
index 0000000000..70c952a12d
--- /dev/null
+++ b/src/modules/powerrename/dll/PowerRenameExt.vcxproj
@@ -0,0 +1,190 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 15.0
+ {B25AC7A5-FB9F-4789-B392-D5C85E948670}
+ PowerRenameExt
+ 10.0
+
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .dll
+ ..\lib\;..\ui\;$(IncludePath)
+ $(SolutionDir)$(Platform)\$(Configuration)\modules\
+ $(ProjectName)
+ $(SolutionDir)$(Platform)\$(Configuration)\;$(LibraryPath)
+
+
+ ..\lib\;..\ui\;$(IncludePath)
+ $(SolutionDir)$(Platform)\$(Configuration)\modules\
+ $(ProjectName)
+ $(SolutionDir)$(Platform)\$(Configuration)\;$(LibraryPath)
+
+
+ ..\lib\;..\ui\;$(IncludePath)
+ $(ProjectName)
+ $(SolutionDir)$(Platform)\$(Configuration)\modules\
+ $(SolutionDir)$(Platform)\$(Configuration)\;$(LibraryPath)
+
+
+ ..\lib\;..\ui\;$(IncludePath)
+ $(ProjectName)
+ $(SolutionDir)$(Platform)\$(Configuration)\modules\
+ $(SolutionDir)$(Platform)\$(Configuration)\;$(LibraryPath)
+
+
+
+ Level3
+ Disabled
+ true
+ true
+ stdcpp17
+ MultiThreadedDebug
+ ..\;..\..\..\common;..\..\..\common\telemetry;..\..\;%(AdditionalIncludeDirectories)
+
+
+ $(SolutionDir)$(Platform)\$(Configuration)\PowerRenameLib.lib;$(SolutionDir)$(Platform)\$(Configuration)\PowerRenameUI.lib;Pathcch.lib;comctl32.lib;$(SolutionDir)$(Platform)\$(Configuration)\..\..\src\modules\powerrename\UI\$(Platform)\$(Configuration)\PowerRenameUI.res;%(AdditionalDependencies)
+ PowerRenameExt.def
+
+
+
+
+ Level3
+ Disabled
+ true
+ true
+ stdcpplatest
+ MultiThreadedDebug
+ ..\;..\..\..\common;..\..\..\common\telemetry;..\..\;..\..\..\;..\..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)
+
+
+ $(SolutionDir)$(Platform)\$(Configuration)\PowerRenameLib.lib;$(SolutionDir)$(Platform)\$(Configuration)\PowerRenameUI.lib;Pathcch.lib;comctl32.lib;$(SolutionDir)$(Platform)\$(Configuration)\..\..\src\modules\powerrename\UI\$(Platform)\$(Configuration)\PowerRenameUI.res;%(AdditionalDependencies)
+ PowerRenameExt.def
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ true
+ stdcpp17
+ MultiThreaded
+ ..\;..\..\..\common;..\..\..\common\telemetry;..\..\;%(AdditionalIncludeDirectories)
+
+
+ true
+ true
+ $(SolutionDir)$(Platform)\$(Configuration)\PowerRenameLib.lib;$(SolutionDir)$(Platform)\$(Configuration)\PowerRenameUI.lib;Pathcch.lib;comctl32.lib;$(SolutionDir)$(Platform)\$(Configuration)\..\..\src\modules\powerrename\UI\$(Platform)\$(Configuration)\PowerRenameUI.res;%(AdditionalDependencies)
+ PowerRenameExt.def
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ true
+ stdcpplatest
+ MultiThreaded
+ ..\;..\..\..\common;..\..\..\common\telemetry;..\..\;..\..\..\;..\..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)
+
+
+ true
+ true
+ $(SolutionDir)$(Platform)\$(Configuration)\PowerRenameLib.lib;$(SolutionDir)$(Platform)\$(Configuration)\PowerRenameUI.lib;Pathcch.lib;comctl32.lib;$(SolutionDir)$(Platform)\$(Configuration)\..\..\src\modules\powerrename\UI\$(Platform)\$(Configuration)\PowerRenameUI.res;%(AdditionalDependencies)
+ PowerRenameExt.def
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {74485049-c722-400f-abe5-86ac52d929b3}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters b/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters
new file mode 100644
index 0000000000..b1a31797b4
--- /dev/null
+++ b/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters
@@ -0,0 +1,52 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Resource Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/src/modules/powerrename/dll/dllmain.cpp b/src/modules/powerrename/dll/dllmain.cpp
new file mode 100644
index 0000000000..13b35b3600
--- /dev/null
+++ b/src/modules/powerrename/dll/dllmain.cpp
@@ -0,0 +1,252 @@
+#include "stdafx.h"
+#include "PowerRenameExt.h"
+#include
+#include
+
+DWORD g_dwModuleRefCount = 0;
+HINSTANCE g_hInst = 0;
+
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+
+class CSmartRenameClassFactory : public IClassFactory
+{
+public:
+ CSmartRenameClassFactory(_In_ REFCLSID clsid) :
+ m_refCount(1),
+ m_clsid(clsid)
+ {
+ DllAddRef();
+ }
+
+ // IUnknown methods
+ IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void** ppv)
+ {
+ static const QITAB qit[] =
+ {
+ QITABENT(CSmartRenameClassFactory, IClassFactory),
+ { 0 }
+ };
+ return QISearch(this, qit, riid, ppv);
+ }
+
+ IFACEMETHODIMP_(ULONG) AddRef()
+ {
+ return InterlockedIncrement(&m_refCount);
+ }
+
+ IFACEMETHODIMP_(ULONG) Release()
+ {
+ LONG refCount = InterlockedDecrement(&m_refCount);
+ if (refCount == 0)
+ {
+ delete this;
+ }
+ return refCount;
+ }
+
+ // IClassFactory methods
+ IFACEMETHODIMP CreateInstance(_In_opt_ IUnknown* punkOuter, _In_ REFIID riid, _Outptr_ void** ppv)
+ {
+ *ppv = NULL;
+ HRESULT hr;
+ if (punkOuter)
+ {
+ hr = CLASS_E_NOAGGREGATION;
+ }
+ else
+ {
+ if (m_clsid == CLSID_PowerRenameMenu)
+ {
+ hr = CPowerRenameMenu::s_CreateInstance(punkOuter, riid, ppv);
+ }
+ else
+ {
+ hr = CLASS_E_CLASSNOTAVAILABLE;
+ }
+ }
+ return hr;
+ }
+
+ IFACEMETHODIMP LockServer(BOOL bLock)
+ {
+ if (bLock)
+ {
+ DllAddRef();
+ }
+ else
+ {
+ DllRelease();
+ }
+ return S_OK;
+ }
+
+private:
+ ~CSmartRenameClassFactory()
+ {
+ DllRelease();
+ }
+
+ long m_refCount;
+ CLSID m_clsid;
+};
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, void*)
+{
+ switch (dwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ g_hInst = hInstance;
+ break;
+
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+ return TRUE;
+}
+
+//
+// Checks if there are any external references to this module
+//
+STDAPI DllCanUnloadNow(void)
+{
+ return (g_dwModuleRefCount == 0) ? S_OK : S_FALSE;
+}
+
+//
+// DLL export for creating COM objects
+//
+STDAPI DllGetClassObject(_In_ REFCLSID clsid, _In_ REFIID riid, _Outptr_ void **ppv)
+{
+ *ppv = NULL;
+ HRESULT hr = E_OUTOFMEMORY;
+ CSmartRenameClassFactory *pClassFactory = new CSmartRenameClassFactory(clsid);
+ if (pClassFactory)
+ {
+ hr = pClassFactory->QueryInterface(riid, ppv);
+ pClassFactory->Release();
+ }
+ return hr;
+}
+
+STDAPI DllRegisterServer()
+{
+ return S_OK;
+}
+
+STDAPI DllUnregisterServer()
+{
+ return S_OK;
+}
+
+void DllAddRef()
+{
+ g_dwModuleRefCount++;
+}
+
+void DllRelease()
+{
+ g_dwModuleRefCount--;
+}
+
+
+class PowerRenameModule : public PowertoyModuleIface
+{
+private:
+ // Enabled by default
+ bool m_enabled = true;
+
+public:
+ // Return the display name of the powertoy, this will be cached
+ virtual PCWSTR get_name() override
+ {
+ return L"PowerRename";
+ }
+
+ // Enable the powertoy
+ virtual void enable()
+ {
+ m_enabled = true;
+ save_settings();
+ }
+
+ // Disable the powertoy
+ virtual void disable()
+ {
+ m_enabled = false;
+ save_settings();
+ }
+
+ // Returns if the powertoy is enabled
+ virtual bool is_enabled() override
+ {
+ return m_enabled;
+ }
+
+ // Return array of the names of all events that this powertoy listens for, with
+ // nullptr as the last element of the array. Nullptr can also be retured for empty list.
+ virtual PCWSTR* get_events() override
+ {
+ return nullptr;
+ }
+
+ // Return JSON with the configuration options.
+ // These are the settings shown on the settings page along with their current values.
+ virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override
+ {
+ HINSTANCE hinstance = reinterpret_cast(&__ImageBase);
+
+ // Create a Settings object.
+ PowerToysSettings::Settings settings(hinstance, get_name());
+ settings.set_description(L"A Windows Shell Extension for more advanced bulk renaming using search and replace or regular expressions.");
+
+ // Link to the GitHub PowerRename sub-page
+ settings.set_overview_link(L"https://github.com/microsoft/PowerToys/tree/master/src/modules/powerrename");
+
+ return settings.serialize_to_buffer(buffer, buffer_size);
+ }
+
+ // Passes JSON with the configuration settings for the powertoy.
+ // This is called when the user hits Save on the settings page.
+ virtual void set_config(PCWSTR config) override
+ {
+ }
+
+ // Signal from the Settings editor to call a custom action.
+ // This can be used to spawn more complex editors.
+ virtual void call_custom_action(const wchar_t* action) override
+ {
+ }
+
+ // Handle incoming event, data is event-specific
+ virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override
+ {
+ return 0;
+ }
+
+ // Destroy the powertoy and free memory
+ virtual void destroy() override
+ {
+ delete this;
+ }
+
+ void init_settings()
+ {
+ m_enabled = CPowerRenameMenu::IsEnabled();
+ }
+
+ void save_settings()
+ {
+ CPowerRenameMenu::SetEnabled(m_enabled);
+ }
+
+ PowerRenameModule()
+ {
+ init_settings();
+ }
+};
+
+
+extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
+{
+ return new PowerRenameModule();
+}
diff --git a/src/modules/powerrename/dll/resource.h b/src/modules/powerrename/dll/resource.h
new file mode 100644
index 0000000000..1d565af39a
--- /dev/null
+++ b/src/modules/powerrename/dll/resource.h
@@ -0,0 +1,12 @@
+#define IDS_POWERRENAME 801
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/src/modules/powerrename/dll/stdafx.cpp b/src/modules/powerrename/dll/stdafx.cpp
new file mode 100644
index 0000000000..76d63011b5
--- /dev/null
+++ b/src/modules/powerrename/dll/stdafx.cpp
@@ -0,0 +1,2 @@
+#include "stdafx.h"
+#pragma comment(lib, "windowsapp")
diff --git a/src/modules/powerrename/dll/stdafx.h b/src/modules/powerrename/dll/stdafx.h
new file mode 100644
index 0000000000..84fff93641
--- /dev/null
+++ b/src/modules/powerrename/dll/stdafx.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "targetver.h"
+
+#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
+// Windows Header Files:
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "common/common.h"
+
+void DllAddRef();
+void DllRelease();
+
+#define INITGUID
+#include
+
+// {0440049F-D1DC-4E46-B27B-98393D79486B}
+DEFINE_GUID(CLSID_PowerRenameMenu, 0x0440049F, 0xD1DC, 0x4E46, 0xB2, 0x7B, 0x98, 0x39, 0x3D, 0x79, 0x48, 0x6B);
+
+#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
+
diff --git a/src/modules/powerrename/dll/targetver.h b/src/modules/powerrename/dll/targetver.h
new file mode 100644
index 0000000000..d3495778cb
--- /dev/null
+++ b/src/modules/powerrename/dll/targetver.h
@@ -0,0 +1,8 @@
+#pragma once
+// Including SDKDDKVer.h defines the highest available Windows platform.
+
+// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
+// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
+
+#include
+
diff --git a/src/modules/powerrename/lib/Helpers.cpp b/src/modules/powerrename/lib/Helpers.cpp
new file mode 100644
index 0000000000..18d1e86a15
--- /dev/null
+++ b/src/modules/powerrename/lib/Helpers.cpp
@@ -0,0 +1,288 @@
+#include "stdafx.h"
+#include "Helpers.h"
+#include
+
+HRESULT GetIconIndexFromPath(_In_ PCWSTR path, _Out_ int* index)
+{
+ *index = 0;
+
+ HRESULT hr = E_FAIL;
+
+ SHFILEINFO shFileInfo = { 0 };
+
+ if (!PathIsRelative(path))
+ {
+ DWORD attrib = GetFileAttributes(path);
+ HIMAGELIST himl = (HIMAGELIST)SHGetFileInfo(path, attrib, &shFileInfo, sizeof(shFileInfo), (SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES));
+ if (himl)
+ {
+ *index = shFileInfo.iIcon;
+ // We shouldn't free the HIMAGELIST.
+ hr = S_OK;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT _ParseEnumItems(_In_ IEnumShellItems* pesi, _In_ IPowerRenameManager* psrm, _In_ int depth = 0)
+{
+ HRESULT hr = E_INVALIDARG;
+
+ // We shouldn't get this deep since we only enum the contents of
+ // regular folders but adding just in case
+ if ((pesi) && (depth < (MAX_PATH / 2)))
+ {
+ hr = S_OK;
+
+ ULONG celtFetched;
+ CComPtr spsi;
+ while ((S_OK == pesi->Next(1, &spsi, &celtFetched)) && (SUCCEEDED(hr)))
+ {
+ CComPtr spsrif;
+ hr = psrm->get_smartRenameItemFactory(&spsrif);
+ if (SUCCEEDED(hr))
+ {
+ CComPtr spNewItem;
+ hr = spsrif->Create(spsi, &spNewItem);
+ if (SUCCEEDED(hr))
+ {
+ spNewItem->put_depth(depth);
+ hr = psrm->AddItem(spNewItem);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ bool isFolder = false;
+ if (SUCCEEDED(spNewItem->get_isFolder(&isFolder)) && isFolder)
+ {
+ // Bind to the IShellItem for the IEnumShellItems interface
+ CComPtr spesiNext;
+ hr = spsi->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&spesiNext));
+ if (SUCCEEDED(hr))
+ {
+ // Parse the folder contents recursively
+ hr = _ParseEnumItems(spesiNext, psrm, depth + 1);
+ }
+ }
+ }
+ }
+
+ spsi = nullptr;
+ }
+ }
+
+ return hr;
+}
+
+// Iterate through the data object and add paths to the rotation manager
+HRESULT EnumerateDataObject(_In_ IDataObject* pdo, _In_ IPowerRenameManager* psrm)
+{
+ CComPtr spsia;
+ HRESULT hr = SHCreateShellItemArrayFromDataObject(pdo, IID_PPV_ARGS(&spsia));
+ if (SUCCEEDED(hr))
+ {
+ CComPtr spesi;
+ hr = spsia->EnumItems(&spesi);
+ if (SUCCEEDED(hr))
+ {
+ hr = _ParseEnumItems(spesi, psrm);
+ }
+ }
+
+ return hr;
+}
+
+HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p)
+{
+ WNDCLASS wc = { 0 };
+ PWSTR wndClassName = L"MsgWindow";
+
+ wc.lpfnWndProc = DefWindowProc;
+ wc.cbWndExtra = sizeof(void*);
+ wc.hInstance = hInst;
+ wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
+ wc.lpszClassName = wndClassName;
+
+ RegisterClass(&wc);
+
+ HWND hwnd = CreateWindowEx(
+ 0, wndClassName, nullptr, 0,
+ 0, 0, 0, 0, HWND_MESSAGE,
+ 0, hInst, nullptr);
+ if (hwnd)
+ {
+ SetWindowLongPtr(hwnd, 0, (LONG_PTR)p);
+ if (pfnWndProc)
+ {
+ SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)pfnWndProc);
+ }
+ }
+
+ return hwnd;
+}
+
+BOOL GetEnumeratedFileName(__out_ecount(cchMax) PWSTR pszUniqueName, UINT cchMax,
+ __in PCWSTR pszTemplate, __in_opt PCWSTR pszDir, unsigned long ulMinLong,
+ __inout unsigned long* pulNumUsed)
+{
+ PWSTR pszName = nullptr;
+ HRESULT hr = S_OK;
+ BOOL fRet = FALSE;
+ int cchDir = 0;
+
+ if (0 != cchMax && pszUniqueName)
+ {
+ *pszUniqueName = 0;
+ if (pszDir)
+ {
+ hr = StringCchCopy(pszUniqueName, cchMax, pszDir);
+ if (SUCCEEDED(hr))
+ {
+ hr = PathCchAddBackslashEx(pszUniqueName, cchMax, &pszName, nullptr);
+ if (SUCCEEDED(hr))
+ {
+ cchDir = lstrlen(pszDir);
+ }
+ }
+ }
+ else
+ {
+ cchDir = 0;
+ pszName = pszUniqueName;
+ }
+ }
+ else
+ {
+ hr = E_INVALIDARG;
+ }
+
+ int cchTmp = 0;
+ int cchStem = 0;
+ PCWSTR pszStem = nullptr;
+ PCWSTR pszRest = nullptr;
+ wchar_t szFormat[MAX_PATH] = { 0 };
+
+ if (SUCCEEDED(hr))
+ {
+ pszStem = pszTemplate;
+
+ pszRest = StrChr(pszTemplate, L'(');
+ while (pszRest)
+ {
+ PCWSTR pszEndUniq = CharNext(pszRest);
+ while (*pszEndUniq && *pszEndUniq >= L'0' && *pszEndUniq <= L'9')
+ {
+ pszEndUniq++;
+ }
+
+ if (*pszEndUniq == L')')
+ {
+ break;
+ }
+
+ pszRest = StrChr(CharNext(pszRest), L'(');
+ }
+
+ if (!pszRest)
+ {
+ pszRest = PathFindExtension(pszTemplate);
+ cchStem = (int)(pszRest - pszTemplate);
+
+ hr = StringCchCopy(szFormat, ARRAYSIZE(szFormat), L" (%lu)");
+ }
+ else
+ {
+ pszRest++;
+
+ cchStem = (int)(pszRest - pszTemplate);
+
+ while (*pszRest && *pszRest >= L'0' && *pszRest <= L'9')
+ {
+ pszRest++;
+ }
+
+ hr = StringCchCopy(szFormat, ARRAYSIZE(szFormat), L"%lu");
+ }
+ }
+
+ unsigned long ulMax = 0;
+ unsigned long ulMin = 0;
+ if (SUCCEEDED(hr))
+ {
+ int cchFormat = lstrlen(szFormat);
+ if (cchFormat < 3)
+ {
+ *pszUniqueName = L'\0';
+ return FALSE;
+ }
+ ulMin = ulMinLong;
+ cchTmp = cchMax - cchDir - cchStem - (cchFormat - 3);
+ switch (cchTmp)
+ {
+ case 1:
+ ulMax = 10;
+ break;
+ case 2:
+ ulMax = 100;
+ break;
+ case 3:
+ ulMax = 1000;
+ break;
+ case 4:
+ ulMax = 10000;
+ break;
+ case 5:
+ ulMax = 100000;
+ break;
+ default:
+ if (cchTmp <= 0)
+ {
+ ulMax = ulMin;
+ }
+ else
+ {
+ ulMax = 1000000;
+ }
+ break;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = StringCchCopyN(pszName, pszUniqueName + cchMax - pszName, pszStem, cchStem);
+ if (SUCCEEDED(hr))
+ {
+ PWSTR pszDigit = pszName + cchStem;
+
+ for (unsigned long ul = ulMin; ((ul < ulMax) && (!fRet)); ul++)
+ {
+ wchar_t szTemp[MAX_PATH] = { 0 };
+ hr = StringCchPrintf(szTemp, ARRAYSIZE(szTemp), szFormat, ul);
+ if (SUCCEEDED(hr))
+ {
+ hr = StringCchCat(szTemp, ARRAYSIZE(szTemp), pszRest);
+ if (SUCCEEDED(hr))
+ {
+ hr = StringCchCopy(pszDigit, pszUniqueName + cchMax - pszDigit, szTemp);
+ if (SUCCEEDED(hr))
+ {
+ if (!PathFileExists(pszUniqueName))
+ {
+ (*pulNumUsed) = ul;
+ fRet = TRUE;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!fRet)
+ {
+ *pszUniqueName = L'\0';
+ }
+
+ return fRet;
+}
diff --git a/src/modules/powerrename/lib/Helpers.h b/src/modules/powerrename/lib/Helpers.h
new file mode 100644
index 0000000000..4f6f753e18
--- /dev/null
+++ b/src/modules/powerrename/lib/Helpers.h
@@ -0,0 +1,10 @@
+#pragma once
+#include "stdafx.h"
+
+HRESULT EnumerateDataObject(_In_ IDataObject* pdo, _In_ IPowerRenameManager* psrm);
+HRESULT GetIconIndexFromPath(_In_ PCWSTR path, _Out_ int* index);
+HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p);
+BOOL GetEnumeratedFileName(
+ __out_ecount(cchMax) PWSTR pszUniqueName, UINT cchMax,
+ __in PCWSTR pszTemplate, __in_opt PCWSTR pszDir, unsigned long ulMinLong,
+ __inout unsigned long* pulNumUsed);
\ No newline at end of file
diff --git a/src/modules/powerrename/lib/PowerRenameInterfaces.h b/src/modules/powerrename/lib/PowerRenameInterfaces.h
new file mode 100644
index 0000000000..a69b4fe9f6
--- /dev/null
+++ b/src/modules/powerrename/lib/PowerRenameInterfaces.h
@@ -0,0 +1,109 @@
+#pragma once
+#include "stdafx.h"
+
+enum PowerRenameFlags
+{
+ CaseSensitive = 0x1,
+ MatchAllOccurences = 0x2,
+ UseRegularExpressions = 0x4,
+ EnumerateItems = 0x8,
+ ExcludeFiles = 0x10,
+ ExcludeFolders = 0x20,
+ ExcludeSubfolders = 0x40,
+ NameOnly = 0x80,
+ ExtensionOnly = 0x100
+};
+
+interface __declspec(uuid("3ECBA62B-E0F0-4472-AA2E-DEE7A1AA46B9")) IPowerRenameRegExEvents : public IUnknown
+{
+public:
+ IFACEMETHOD(OnSearchTermChanged)(_In_ PCWSTR searchTerm) = 0;
+ IFACEMETHOD(OnReplaceTermChanged)(_In_ PCWSTR replaceTerm) = 0;
+ IFACEMETHOD(OnFlagsChanged)(_In_ DWORD flags) = 0;
+};
+
+interface __declspec(uuid("E3ED45B5-9CE0-47E2-A595-67EB950B9B72")) IPowerRenameRegEx : public IUnknown
+{
+public:
+ IFACEMETHOD(Advise)(_In_ IPowerRenameRegExEvents* regExEvents, _Out_ DWORD* cookie) = 0;
+ IFACEMETHOD(UnAdvise)(_In_ DWORD cookie) = 0;
+ IFACEMETHOD(get_searchTerm)(_Outptr_ PWSTR* searchTerm) = 0;
+ IFACEMETHOD(put_searchTerm)(_In_ PCWSTR searchTerm) = 0;
+ IFACEMETHOD(get_replaceTerm)(_Outptr_ PWSTR* replaceTerm) = 0;
+ IFACEMETHOD(put_replaceTerm)(_In_ PCWSTR replaceTerm) = 0;
+ IFACEMETHOD(get_flags)(_Out_ DWORD* flags) = 0;
+ IFACEMETHOD(put_flags)(_In_ DWORD flags) = 0;
+ IFACEMETHOD(Replace)(_In_ PCWSTR source, _Outptr_ PWSTR* result) = 0;
+};
+
+interface __declspec(uuid("C7F59201-4DE1-4855-A3A2-26FC3279C8A5")) IPowerRenameItem : public IUnknown
+{
+public:
+ IFACEMETHOD(get_path)(_Outptr_ PWSTR* path) = 0;
+ IFACEMETHOD(get_shellItem)(_Outptr_ IShellItem** ppsi) = 0;
+ IFACEMETHOD(get_originalName)(_Outptr_ PWSTR* originalName) = 0;
+ IFACEMETHOD(get_newName)(_Outptr_ PWSTR* newName) = 0;
+ IFACEMETHOD(put_newName)(_In_opt_ PCWSTR newName) = 0;
+ IFACEMETHOD(get_isFolder)(_Out_ bool* isFolder) = 0;
+ IFACEMETHOD(get_isSubFolderContent)(_Out_ bool* isSubFolderContent) = 0;
+ IFACEMETHOD(get_selected)(_Out_ bool* selected) = 0;
+ IFACEMETHOD(put_selected)(_In_ bool selected) = 0;
+ IFACEMETHOD(get_id)(_Out_ int *id) = 0;
+ IFACEMETHOD(get_iconIndex)(_Out_ int* iconIndex) = 0;
+ IFACEMETHOD(get_depth)(_Out_ UINT* depth) = 0;
+ IFACEMETHOD(put_depth)(_In_ int depth) = 0;
+ IFACEMETHOD(ShouldRenameItem)(_In_ DWORD flags, _Out_ bool* shouldRename) = 0;
+ IFACEMETHOD(Reset)() = 0;
+};
+
+interface __declspec(uuid("{26CBFFD9-13B3-424E-BAC9-D12B0539149C}")) IPowerRenameItemFactory : public IUnknown
+{
+public:
+ IFACEMETHOD(Create)(_In_ IShellItem* psi, _COM_Outptr_ IPowerRenameItem** ppItem) = 0;
+};
+
+interface __declspec(uuid("87FC43F9-7634-43D9-99A5-20876AFCE4AD")) IPowerRenameManagerEvents : public IUnknown
+{
+public:
+ IFACEMETHOD(OnItemAdded)(_In_ IPowerRenameItem* renameItem) = 0;
+ IFACEMETHOD(OnUpdate)(_In_ IPowerRenameItem* renameItem) = 0;
+ IFACEMETHOD(OnError)(_In_ IPowerRenameItem* renameItem) = 0;
+ IFACEMETHOD(OnRegExStarted)(_In_ DWORD threadId) = 0;
+ IFACEMETHOD(OnRegExCanceled)(_In_ DWORD threadId) = 0;
+ IFACEMETHOD(OnRegExCompleted)(_In_ DWORD threadId) = 0;
+ IFACEMETHOD(OnRenameStarted)() = 0;
+ IFACEMETHOD(OnRenameCompleted)() = 0;
+};
+
+interface __declspec(uuid("001BBD88-53D2-4FA6-95D2-F9A9FA4F9F70")) IPowerRenameManager : public IUnknown
+{
+public:
+ IFACEMETHOD(Advise)(_In_ IPowerRenameManagerEvents* renameManagerEvent, _Out_ DWORD* cookie) = 0;
+ IFACEMETHOD(UnAdvise)(_In_ DWORD cookie) = 0;
+ IFACEMETHOD(Start)() = 0;
+ IFACEMETHOD(Stop)() = 0;
+ IFACEMETHOD(Reset)() = 0;
+ IFACEMETHOD(Shutdown)() = 0;
+ IFACEMETHOD(Rename)(_In_ HWND hwndParent) = 0;
+ IFACEMETHOD(AddItem)(_In_ IPowerRenameItem* pItem) = 0;
+ IFACEMETHOD(GetItemByIndex)(_In_ UINT index, _COM_Outptr_ IPowerRenameItem** ppItem) = 0;
+ IFACEMETHOD(GetItemById)(_In_ int id, _COM_Outptr_ IPowerRenameItem** ppItem) = 0;
+ IFACEMETHOD(GetItemCount)(_Out_ UINT* count) = 0;
+ IFACEMETHOD(GetSelectedItemCount)(_Out_ UINT* count) = 0;
+ IFACEMETHOD(GetRenameItemCount)(_Out_ UINT* count) = 0;
+ IFACEMETHOD(get_flags)(_Out_ DWORD* flags) = 0;
+ IFACEMETHOD(put_flags)(_In_ DWORD flags) = 0;
+ IFACEMETHOD(get_smartRenameRegEx)(_COM_Outptr_ IPowerRenameRegEx** ppRegEx) = 0;
+ IFACEMETHOD(put_smartRenameRegEx)(_In_ IPowerRenameRegEx* pRegEx) = 0;
+ IFACEMETHOD(get_smartRenameItemFactory)(_COM_Outptr_ IPowerRenameItemFactory** ppItemFactory) = 0;
+ IFACEMETHOD(put_smartRenameItemFactory)(_In_ IPowerRenameItemFactory* pItemFactory) = 0;
+};
+
+interface __declspec(uuid("E6679DEB-460D-42C1-A7A8-E25897061C99")) IPowerRenameUI : public IUnknown
+{
+public:
+ IFACEMETHOD(Show)() = 0;
+ IFACEMETHOD(Close)() = 0;
+ IFACEMETHOD(Update)() = 0;
+};
+
diff --git a/src/modules/powerrename/lib/PowerRenameItem.cpp b/src/modules/powerrename/lib/PowerRenameItem.cpp
new file mode 100644
index 0000000000..789de11c5b
--- /dev/null
+++ b/src/modules/powerrename/lib/PowerRenameItem.cpp
@@ -0,0 +1,221 @@
+#include "stdafx.h"
+#include "PowerRenameItem.h"
+#include "helpers.h"
+
+int CPowerRenameItem::s_id = 0;
+
+IFACEMETHODIMP_(ULONG) CPowerRenameItem::AddRef()
+{
+ return InterlockedIncrement(&m_refCount);
+}
+
+IFACEMETHODIMP_(ULONG) CPowerRenameItem::Release()
+{
+ long refCount = InterlockedDecrement(&m_refCount);
+
+ if (refCount == 0)
+ {
+ delete this;
+ }
+ return refCount;
+}
+
+IFACEMETHODIMP CPowerRenameItem::QueryInterface(_In_ REFIID riid, _Outptr_ void** ppv)
+{
+ static const QITAB qit[] = {
+ QITABENT(CPowerRenameItem, IPowerRenameItem),
+ QITABENT(CPowerRenameItem, IPowerRenameItemFactory),
+ { 0 }
+ };
+ return QISearch(this, qit, riid, ppv);
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_path(_Outptr_ PWSTR* path)
+{
+ *path = nullptr;
+ CSRWSharedAutoLock lock(&m_lock);
+ HRESULT hr = m_path ? S_OK : E_FAIL;
+ if (SUCCEEDED(hr))
+ {
+ hr = SHStrDup(m_path, path);
+ }
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_shellItem(_Outptr_ IShellItem** ppsi)
+{
+ return SHCreateItemFromParsingName(m_path, nullptr, IID_PPV_ARGS(ppsi));
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_originalName(_Outptr_ PWSTR* originalName)
+{
+ CSRWSharedAutoLock lock(&m_lock);
+ HRESULT hr = m_originalName ? S_OK : E_FAIL;
+ if (SUCCEEDED(hr))
+ {
+ hr = SHStrDup(m_originalName, originalName);
+ }
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameItem::put_newName(_In_opt_ PCWSTR newName)
+{
+ CSRWSharedAutoLock lock(&m_lock);
+ CoTaskMemFree(m_newName);
+ m_newName = nullptr;
+ HRESULT hr = S_OK;
+ if (newName != nullptr)
+ {
+ hr = SHStrDup(newName, &m_newName);
+ }
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_newName(_Outptr_ PWSTR* newName)
+{
+ CSRWSharedAutoLock lock(&m_lock);
+ HRESULT hr = m_newName ? S_OK : E_FAIL;
+ if (SUCCEEDED(hr))
+ {
+ hr = SHStrDup(m_newName, newName);
+ }
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_isFolder(_Out_ bool* isFolder)
+{
+ CSRWSharedAutoLock lock(&m_lock);
+ *isFolder = m_isFolder;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_isSubFolderContent(_Out_ bool* isSubFolderContent)
+{
+ CSRWSharedAutoLock lock(&m_lock);
+ *isSubFolderContent = m_depth > 0;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_selected(_Out_ bool* selected)
+{
+ CSRWSharedAutoLock lock(&m_lock);
+ *selected = m_selected;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameItem::put_selected(_In_ bool selected)
+{
+ CSRWSharedAutoLock lock(&m_lock);
+ m_selected = selected;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_id(_Out_ int* id)
+{
+ CSRWSharedAutoLock lock(&m_lock);
+ *id = m_id;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_iconIndex(_Out_ int* iconIndex)
+{
+ if (m_iconIndex == -1)
+ {
+ GetIconIndexFromPath((PCWSTR)m_path, &m_iconIndex);
+ }
+ *iconIndex = m_iconIndex;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameItem::get_depth(_Out_ UINT* depth)
+{
+ *depth = m_depth;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameItem::put_depth(_In_ int depth)
+{
+ m_depth = depth;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameItem::ShouldRenameItem(_In_ DWORD flags, _Out_ bool* shouldRename)
+{
+ // Should we perform a rename on this item given its
+ // state and the options that were set?
+ bool hasChanged = m_newName != nullptr && (lstrcmp(m_originalName, m_newName) != 0);
+ bool excludeBecauseFolder = (m_isFolder && (flags & PowerRenameFlags::ExcludeFolders));
+ bool excludeBecauseFile = (!m_isFolder && (flags & PowerRenameFlags::ExcludeFiles));
+ bool excludeBecauseSubFolderContent = (m_depth > 0 && (flags & PowerRenameFlags::ExcludeSubfolders));
+ *shouldRename = (m_selected && hasChanged && !excludeBecauseFile &&
+ !excludeBecauseFolder && !excludeBecauseSubFolderContent);
+
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameItem::Reset()
+{
+ CSRWSharedAutoLock lock(&m_lock);
+ CoTaskMemFree(m_newName);
+ m_newName = nullptr;
+ return S_OK;
+}
+
+HRESULT CPowerRenameItem::s_CreateInstance(_In_opt_ IShellItem* psi, _In_ REFIID iid, _Outptr_ void** resultInterface)
+{
+ *resultInterface = nullptr;
+
+ CPowerRenameItem *newRenameItem = new CPowerRenameItem();
+ HRESULT hr = newRenameItem ? S_OK : E_OUTOFMEMORY;
+ if (SUCCEEDED(hr))
+ {
+ if (psi != nullptr)
+ {
+ hr = newRenameItem->_Init(psi);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = newRenameItem->QueryInterface(iid, resultInterface);
+ }
+
+ newRenameItem->Release();
+ }
+ return hr;
+}
+
+CPowerRenameItem::CPowerRenameItem() :
+ m_refCount(1),
+ m_id(++s_id)
+{
+}
+
+CPowerRenameItem::~CPowerRenameItem()
+{
+ CoTaskMemFree(m_path);
+ CoTaskMemFree(m_newName);
+ CoTaskMemFree(m_originalName);
+}
+
+HRESULT CPowerRenameItem::_Init(_In_ IShellItem* psi)
+{
+ // Get the full filesystem path from the shell item
+ HRESULT hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &m_path);
+ if (SUCCEEDED(hr))
+ {
+ hr = psi->GetDisplayName(SIGDN_NORMALDISPLAY, &m_originalName);
+ if (SUCCEEDED(hr))
+ {
+ // Check if we are a folder now so we can check this attribute quickly later
+ SFGAOF att = 0;
+ hr = psi->GetAttributes(SFGAO_STREAM | SFGAO_FOLDER, &att);
+ if (SUCCEEDED(hr))
+ {
+ // Some items can be both folders and streams (ex: zip folders).
+ m_isFolder = (att & SFGAO_FOLDER) && !(att & SFGAO_STREAM);
+ }
+ }
+ }
+
+ return hr;
+}
diff --git a/src/modules/powerrename/lib/PowerRenameItem.h b/src/modules/powerrename/lib/PowerRenameItem.h
new file mode 100644
index 0000000000..27be6a55f3
--- /dev/null
+++ b/src/modules/powerrename/lib/PowerRenameItem.h
@@ -0,0 +1,60 @@
+#pragma once
+#include "stdafx.h"
+#include "PowerRenameInterfaces.h"
+#include "srwlock.h"
+
+class CPowerRenameItem :
+ public IPowerRenameItem,
+ public IPowerRenameItemFactory
+{
+public:
+ // IUnknown
+ IFACEMETHODIMP QueryInterface(_In_ REFIID iid, _Outptr_ void** resultInterface);
+ IFACEMETHODIMP_(ULONG) AddRef();
+ IFACEMETHODIMP_(ULONG) Release();
+
+ // IPowerRenameItem
+ IFACEMETHODIMP get_path(_Outptr_ PWSTR* path);
+ IFACEMETHODIMP get_shellItem(_Outptr_ IShellItem** ppsi);
+ IFACEMETHODIMP get_originalName(_Outptr_ PWSTR* originalName);
+ IFACEMETHODIMP put_newName(_In_opt_ PCWSTR newName);
+ IFACEMETHODIMP get_newName(_Outptr_ PWSTR* newName);
+ IFACEMETHODIMP get_isFolder(_Out_ bool* isFolder);
+ IFACEMETHODIMP get_isSubFolderContent(_Out_ bool* isSubFolderContent);
+ IFACEMETHODIMP get_selected(_Out_ bool* selected);
+ IFACEMETHODIMP put_selected(_In_ bool selected);
+ IFACEMETHODIMP get_id(_Out_ int* id);
+ IFACEMETHODIMP get_iconIndex(_Out_ int* iconIndex);
+ IFACEMETHODIMP get_depth(_Out_ UINT* depth);
+ IFACEMETHODIMP put_depth(_In_ int depth);
+ IFACEMETHODIMP Reset();
+ IFACEMETHODIMP ShouldRenameItem(_In_ DWORD flags, _Out_ bool* shouldRename);
+
+ // IPowerRenameItemFactory
+ IFACEMETHODIMP Create(_In_ IShellItem* psi, _Outptr_ IPowerRenameItem** ppItem)
+ {
+ return CPowerRenameItem::s_CreateInstance(psi, IID_PPV_ARGS(ppItem));
+ }
+
+public:
+ static HRESULT s_CreateInstance(_In_opt_ IShellItem* psi, _In_ REFIID iid, _Outptr_ void** resultInterface);
+
+protected:
+ static int s_id;
+ CPowerRenameItem();
+ virtual ~CPowerRenameItem();
+
+ HRESULT _Init(_In_ IShellItem* psi);
+
+ bool m_selected = true;
+ bool m_isFolder = false;
+ int m_id = -1;
+ int m_iconIndex = -1;
+ UINT m_depth = 0;
+ HRESULT m_error = S_OK;
+ PWSTR m_path = nullptr;
+ PWSTR m_originalName = nullptr;
+ PWSTR m_newName = nullptr;
+ CSRWLock m_lock;
+ long m_refCount = 0;
+};
\ No newline at end of file
diff --git a/src/modules/powerrename/lib/PowerRenameLib.vcxproj b/src/modules/powerrename/lib/PowerRenameLib.vcxproj
new file mode 100644
index 0000000000..6c61aa9f26
--- /dev/null
+++ b/src/modules/powerrename/lib/PowerRenameLib.vcxproj
@@ -0,0 +1,167 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {51920F1F-C28C-4ADF-8660-4238766796C2}
+ Win32Proj
+ PowerRenameLib
+ 10.0
+
+
+
+ StaticLibrary
+ true
+ v142
+ Unicode
+
+
+ StaticLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ StaticLibrary
+ true
+ v142
+ Unicode
+
+
+ StaticLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)$(Platform)\$(Configuration)\
+
+
+ $(SolutionDir)$(Platform)\$(Configuration)\
+
+
+
+ Use
+ Level4
+ Disabled
+ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ stdcpp17
+ MultiThreadedDebug
+
+
+ Windows
+
+
+
+
+ Use
+ Level4
+ Disabled
+ _DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ stdcpp17
+ MultiThreadedDebug
+
+
+ Windows
+
+
+
+
+ Level3
+ Use
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ stdcpp17
+ MultiThreaded
+
+
+ Windows
+ true
+ true
+
+
+
+
+ Level3
+ Use
+ MaxSpeed
+ true
+ true
+ NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ stdcpp17
+ MultiThreaded
+
+
+ Windows
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+ Create
+ Create
+ Create
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/powerrename/lib/PowerRenameManager.cpp b/src/modules/powerrename/lib/PowerRenameManager.cpp
new file mode 100644
index 0000000000..69cd8c4001
--- /dev/null
+++ b/src/modules/powerrename/lib/PowerRenameManager.cpp
@@ -0,0 +1,1000 @@
+#include "stdafx.h"
+#include "PowerRenameManager.h"
+#include "PowerRenameRegEx.h" // Default RegEx handler
+#include
+#include
+#include "helpers.h"
+#include
+namespace fs = std::filesystem;
+
+extern HINSTANCE g_hInst;
+
+// The default FOF flags to use in the rename operations
+#define FOF_DEFAULTFLAGS (FOF_ALLOWUNDO | FOFX_ADDUNDORECORD | FOFX_SHOWELEVATIONPROMPT | FOF_RENAMEONCOLLISION)
+
+IFACEMETHODIMP_(ULONG) CPowerRenameManager::AddRef()
+{
+ return InterlockedIncrement(&m_refCount);
+}
+
+IFACEMETHODIMP_(ULONG) CPowerRenameManager::Release()
+{
+ long refCount = InterlockedDecrement(&m_refCount);
+
+ if (refCount == 0)
+ {
+ delete this;
+ }
+ return refCount;
+}
+
+IFACEMETHODIMP CPowerRenameManager::QueryInterface(_In_ REFIID riid, _Outptr_ void** ppv)
+{
+ static const QITAB qit[] = {
+ QITABENT(CPowerRenameManager, IPowerRenameManager),
+ QITABENT(CPowerRenameManager, IPowerRenameRegExEvents),
+ { 0 }
+ };
+ return QISearch(this, qit, riid, ppv);
+}
+
+IFACEMETHODIMP CPowerRenameManager::Advise(_In_ IPowerRenameManagerEvents* renameOpEvents, _Out_ DWORD* cookie)
+{
+ CSRWExclusiveAutoLock lock(&m_lockEvents);
+ m_cookie++;
+ SMART_RENAME_MGR_EVENT srme;
+ srme.cookie = m_cookie;
+ srme.pEvents = renameOpEvents;
+ renameOpEvents->AddRef();
+ m_PowerRenameManagerEvents.push_back(srme);
+
+ *cookie = m_cookie;
+
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::UnAdvise(_In_ DWORD cookie)
+{
+ HRESULT hr = E_FAIL;
+ CSRWExclusiveAutoLock lock(&m_lockEvents);
+
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ if (it.cookie == cookie)
+ {
+ hr = S_OK;
+ it.cookie = 0;
+ if (it.pEvents)
+ {
+ it.pEvents->Release();
+ it.pEvents = nullptr;
+ }
+ break;
+ }
+ }
+
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameManager::Start()
+{
+ return E_NOTIMPL;
+}
+
+IFACEMETHODIMP CPowerRenameManager::Stop()
+{
+ return E_NOTIMPL;
+}
+
+IFACEMETHODIMP CPowerRenameManager::Rename(_In_ HWND hwndParent)
+{
+ m_hwndParent = hwndParent;
+ return _PerformFileOperation();
+}
+
+IFACEMETHODIMP CPowerRenameManager::Reset()
+{
+ // Stop all threads and wait
+ // Reset all rename items
+ return E_NOTIMPL;
+}
+
+IFACEMETHODIMP CPowerRenameManager::Shutdown()
+{
+ _ClearRegEx();
+ _Cleanup();
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::AddItem(_In_ IPowerRenameItem* pItem)
+{
+ HRESULT hr = E_FAIL;
+ // Scope lock
+ {
+ CSRWExclusiveAutoLock lock(&m_lockItems);
+ int id = 0;
+ pItem->get_id(&id);
+ // Verify the item isn't already added
+ if (m_smartRenameItems.find(id) == m_smartRenameItems.end())
+ {
+ m_smartRenameItems[id] = pItem;
+ pItem->AddRef();
+ hr = S_OK;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ _OnItemAdded(pItem);
+ }
+
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameManager::GetItemByIndex(_In_ UINT index, _COM_Outptr_ IPowerRenameItem** ppItem)
+{
+ *ppItem = nullptr;
+ CSRWSharedAutoLock lock(&m_lockItems);
+ HRESULT hr = E_FAIL;
+ if (index < m_smartRenameItems.size())
+ {
+ std::map::iterator it = m_smartRenameItems.begin();
+ std::advance(it, index);
+ *ppItem = it->second;
+ (*ppItem)->AddRef();
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameManager::GetItemById(_In_ int id, _COM_Outptr_ IPowerRenameItem** ppItem)
+{
+ *ppItem = nullptr;
+
+ CSRWSharedAutoLock lock(&m_lockItems);
+ HRESULT hr = E_FAIL;
+ std::map::iterator it;
+ it = m_smartRenameItems.find(id);
+ if (it != m_smartRenameItems.end())
+ {
+ *ppItem = m_smartRenameItems[id];
+ (*ppItem)->AddRef();
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameManager::GetItemCount(_Out_ UINT* count)
+{
+ CSRWSharedAutoLock lock(&m_lockItems);
+ *count = static_cast(m_smartRenameItems.size());
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::GetSelectedItemCount(_Out_ UINT* count)
+{
+ *count = 0;
+ CSRWSharedAutoLock lock(&m_lockItems);
+
+ for (auto it : m_smartRenameItems)
+ {
+ IPowerRenameItem* pItem = it.second;
+ bool selected = false;
+ if (SUCCEEDED(pItem->get_selected(&selected)) && selected)
+ {
+ (*count)++;
+ }
+ }
+
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::GetRenameItemCount(_Out_ UINT* count)
+{
+ *count = 0;
+ CSRWSharedAutoLock lock(&m_lockItems);
+
+ for (auto it : m_smartRenameItems)
+ {
+ IPowerRenameItem* pItem = it.second;
+ bool shouldRename = false;
+ if (SUCCEEDED(pItem->ShouldRenameItem(m_flags, &shouldRename)) && shouldRename)
+ {
+ (*count)++;
+ }
+ }
+
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::get_flags(_Out_ DWORD* flags)
+{
+ _EnsureRegEx();
+ *flags = m_flags;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::put_flags(_In_ DWORD flags)
+{
+ if (flags != m_flags)
+ {
+ m_flags = flags;
+ _EnsureRegEx();
+ m_spRegEx->put_flags(flags);
+ }
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::get_smartRenameRegEx(_COM_Outptr_ IPowerRenameRegEx** ppRegEx)
+{
+ *ppRegEx = nullptr;
+ HRESULT hr = _EnsureRegEx();
+ if (SUCCEEDED(hr))
+ {
+ *ppRegEx = m_spRegEx;
+ (*ppRegEx)->AddRef();
+ }
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameManager::put_smartRenameRegEx(_In_ IPowerRenameRegEx* pRegEx)
+{
+ _ClearRegEx();
+ m_spRegEx = pRegEx;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::get_smartRenameItemFactory(_COM_Outptr_ IPowerRenameItemFactory** ppItemFactory)
+{
+ *ppItemFactory = nullptr;
+ HRESULT hr = E_FAIL;
+ if (m_spItemFactory)
+ {
+ hr = S_OK;
+ *ppItemFactory = m_spItemFactory;
+ (*ppItemFactory)->AddRef();
+ }
+ return hr;
+}
+
+IFACEMETHODIMP CPowerRenameManager::put_smartRenameItemFactory(_In_ IPowerRenameItemFactory* pItemFactory)
+{
+ m_spItemFactory = pItemFactory;
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::OnSearchTermChanged(_In_ PCWSTR /*searchTerm*/)
+{
+ _PerformRegExRename();
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::OnReplaceTermChanged(_In_ PCWSTR /*replaceTerm*/)
+{
+ _PerformRegExRename();
+ return S_OK;
+}
+
+IFACEMETHODIMP CPowerRenameManager::OnFlagsChanged(_In_ DWORD flags)
+{
+ // Flags were updated in the smart rename regex. Update our preview.
+ m_flags = flags;
+ _PerformRegExRename();
+ return S_OK;
+}
+
+HRESULT CPowerRenameManager::s_CreateInstance(_Outptr_ IPowerRenameManager** ppsrm)
+{
+ *ppsrm = nullptr;
+ CPowerRenameManager *psrm = new CPowerRenameManager();
+ HRESULT hr = psrm ? S_OK : E_OUTOFMEMORY;
+ if (SUCCEEDED(hr))
+ {
+ hr = psrm->_Init();
+ if (SUCCEEDED(hr))
+ {
+ hr = psrm->QueryInterface(IID_PPV_ARGS(ppsrm));
+ }
+ psrm->Release();
+ }
+ return hr;
+}
+
+CPowerRenameManager::CPowerRenameManager() :
+ m_refCount(1)
+{
+ InitializeCriticalSection(&m_critsecReentrancy);
+}
+
+CPowerRenameManager::~CPowerRenameManager()
+{
+ DeleteCriticalSection(&m_critsecReentrancy);
+}
+
+HRESULT CPowerRenameManager::_Init()
+{
+ // Guaranteed to succeed
+ m_startFileOpWorkerEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
+ m_startRegExWorkerEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
+ m_cancelRegExWorkerEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
+
+ m_hwndMessage = CreateMsgWindow(g_hInst, s_msgWndProc, this);
+
+ return S_OK;
+}
+
+// Custom messages for worker threads
+enum
+{
+ SRM_REGEX_ITEM_UPDATED = (WM_APP + 1), // Single smart rename item processed by regex worker thread
+ SRM_REGEX_STARTED, // RegEx operation was started
+ SRM_REGEX_CANCELED, // Regex operation was canceled
+ SRM_REGEX_COMPLETE, // Regex worker thread completed
+ SRM_FILEOP_COMPLETE // File Operation worker thread completed
+};
+
+struct WorkerThreadData
+{
+ HWND hwndManager = nullptr;
+ HANDLE startEvent = nullptr;
+ HANDLE cancelEvent = nullptr;
+ HWND hwndParent = nullptr;
+ CComPtr spsrm;
+};
+
+// Msg-only worker window proc for communication from our worker threads
+LRESULT CALLBACK CPowerRenameManager::s_msgWndProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam)
+{
+ LRESULT lRes = 0;
+
+ CPowerRenameManager* pThis = (CPowerRenameManager*)GetWindowLongPtr(hwnd, 0);
+ if (pThis != nullptr)
+ {
+ lRes = pThis->_WndProc(hwnd, uMsg, wParam, lParam);
+ if (uMsg == WM_NCDESTROY)
+ {
+ SetWindowLongPtr(hwnd, 0, NULL);
+ pThis->m_hwndMessage = nullptr;
+ }
+ }
+ else
+ {
+ lRes = DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+ return lRes;
+}
+
+LRESULT CPowerRenameManager::_WndProc(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam)
+{
+ LRESULT lRes = 0;
+
+ AddRef();
+
+ switch (msg)
+ {
+ case SRM_REGEX_ITEM_UPDATED:
+ {
+ int id = static_cast(lParam);
+ CComPtr spItem;
+ if (SUCCEEDED(GetItemById(id, &spItem)))
+ {
+ _OnUpdate(spItem);
+ }
+ break;
+ }
+ case SRM_REGEX_STARTED:
+ _OnRegExStarted(static_cast(wParam));
+ break;
+
+ case SRM_REGEX_CANCELED:
+ _OnRegExCanceled(static_cast(wParam));
+ break;
+
+ case SRM_REGEX_COMPLETE:
+ _OnRegExCompleted(static_cast(wParam));
+ break;
+
+ default:
+ lRes = DefWindowProc(hwnd, msg, wParam, lParam);
+ break;
+ }
+
+ Release();
+
+ return lRes;
+}
+
+HRESULT CPowerRenameManager::_PerformFileOperation()
+{
+ // Do we have items to rename?
+ UINT renameItemCount = 0;
+ if (FAILED(GetRenameItemCount(&renameItemCount)) || renameItemCount == 0)
+ {
+ return E_FAIL;
+ }
+
+ // Wait for existing regex thread to finish
+ _WaitForRegExWorkerThread();
+
+ // Create worker thread which will perform the actual rename
+ HRESULT hr = _CreateFileOpWorkerThread();
+ if (SUCCEEDED(hr))
+ {
+ _OnRenameStarted();
+
+ // Signal the worker thread that they can start working. We needed to wait until we
+ // were ready to process thread messages.
+ SetEvent(m_startFileOpWorkerEvent);
+
+ while (true)
+ {
+ // Check if worker thread has exited
+ if (WaitForSingleObject(m_fileOpWorkerThreadHandle, 0) == WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ MSG msg;
+ while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
+ {
+ if (msg.message == SRM_FILEOP_COMPLETE)
+ {
+ // Worker thread completed
+ break;
+ }
+ else
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+ }
+
+ _OnRenameCompleted();
+ }
+
+ return 0;
+}
+
+HRESULT CPowerRenameManager::_CreateFileOpWorkerThread()
+{
+ WorkerThreadData* pwtd = new WorkerThreadData;
+ HRESULT hr = pwtd ? S_OK : E_OUTOFMEMORY;
+ if (SUCCEEDED(hr))
+ {
+ pwtd->hwndManager = m_hwndMessage;
+ pwtd->startEvent = m_startRegExWorkerEvent;
+ pwtd->cancelEvent = nullptr;
+ pwtd->spsrm = this;
+ m_fileOpWorkerThreadHandle = CreateThread(nullptr, 0, s_fileOpWorkerThread, pwtd, 0, nullptr);
+ hr = (m_fileOpWorkerThreadHandle) ? S_OK : E_FAIL;
+ if (FAILED(hr))
+ {
+ delete pwtd;
+ }
+ }
+
+ return hr;
+}
+
+DWORD WINAPI CPowerRenameManager::s_fileOpWorkerThread(_In_ void* pv)
+{
+ if (SUCCEEDED(CoInitializeEx(NULL, 0)))
+ {
+ WorkerThreadData* pwtd = reinterpret_cast(pv);
+ if (pwtd)
+ {
+ // Wait to be told we can begin
+ if (WaitForSingleObject(pwtd->startEvent, INFINITE) == WAIT_OBJECT_0)
+ {
+ CComPtr spRenameRegEx;
+ if (SUCCEEDED(pwtd->spsrm->get_smartRenameRegEx(&spRenameRegEx)))
+ {
+ // Create IFileOperation interface
+ CComPtr spFileOp;
+ if (SUCCEEDED(CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spFileOp))))
+ {
+ DWORD flags = 0;
+ spRenameRegEx->get_flags(&flags);
+
+ UINT itemCount = 0;
+ pwtd->spsrm->GetItemCount(&itemCount);
+ // Add each rename operation
+ for (UINT u = 0; u <= itemCount; u++)
+ {
+ CComPtr spItem;
+ if (SUCCEEDED(pwtd->spsrm->GetItemByIndex(u, &spItem)))
+ {
+ bool shouldRename = false;
+ if (SUCCEEDED(spItem->ShouldRenameItem(flags, &shouldRename)) && shouldRename)
+ {
+ PWSTR newName = nullptr;
+ if (SUCCEEDED(spItem->get_newName(&newName)))
+ {
+ CComPtr spShellItem;
+ if (SUCCEEDED(spItem->get_shellItem(&spShellItem)))
+ {
+ spFileOp->RenameItem(spShellItem, newName, nullptr);
+ }
+ CoTaskMemFree(newName);
+ }
+ }
+ }
+ }
+
+ // Set the operation flags
+ if (SUCCEEDED(spFileOp->SetOperationFlags(FOF_DEFAULTFLAGS)))
+ {
+ // Set the parent window
+ if (pwtd->hwndParent)
+ {
+ spFileOp->SetOwnerWindow(pwtd->hwndParent);
+ }
+
+ // Perform the operation
+ // We don't care about the return code here. We would rather
+ // return control back to explorer so the user can cleanly
+ // undo the operation if it failed halfway through.
+ spFileOp->PerformOperations();
+ }
+ }
+ }
+ }
+
+ // Send the manager thread the completion message
+ PostMessage(pwtd->hwndManager, SRM_FILEOP_COMPLETE, GetCurrentThreadId(), 0);
+
+ delete pwtd;
+ }
+ CoUninitialize();
+ }
+
+ return 0;
+}
+
+HRESULT CPowerRenameManager::_PerformRegExRename()
+{
+ HRESULT hr = E_FAIL;
+
+ if (!TryEnterCriticalSection(&m_critsecReentrancy))
+ {
+ // Ensure we do not re-enter since we pump messages here.
+ // TODO: If we do, post a message back to ourselves
+ }
+ else
+ {
+ // Ensure previous thread is canceled
+ _CancelRegExWorkerThread();
+
+ // Create worker thread which will message us progress and completion.
+ hr = _CreateRegExWorkerThread();
+ if (SUCCEEDED(hr))
+ {
+ ResetEvent(m_cancelRegExWorkerEvent);
+
+ // Signal the worker thread that they can start working. We needed to wait until we
+ // were ready to process thread messages.
+ SetEvent(m_startRegExWorkerEvent);
+ }
+ }
+
+ return hr;
+}
+
+HRESULT CPowerRenameManager::_CreateRegExWorkerThread()
+{
+ WorkerThreadData* pwtd = new WorkerThreadData;
+ HRESULT hr = pwtd ? S_OK : E_OUTOFMEMORY;
+ if (SUCCEEDED(hr))
+ {
+ pwtd->hwndManager = m_hwndMessage;
+ pwtd->startEvent = m_startRegExWorkerEvent;
+ pwtd->cancelEvent = m_cancelRegExWorkerEvent;
+ pwtd->hwndParent = m_hwndParent;
+ pwtd->spsrm = this;
+ m_regExWorkerThreadHandle = CreateThread(nullptr, 0, s_regexWorkerThread, pwtd, 0, nullptr);
+ hr = (m_regExWorkerThreadHandle) ? S_OK : E_FAIL;
+ if (FAILED(hr))
+ {
+ delete pwtd;
+ }
+ }
+
+ return hr;
+}
+
+DWORD WINAPI CPowerRenameManager::s_regexWorkerThread(_In_ void* pv)
+{
+ if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)))
+ {
+ WorkerThreadData* pwtd = reinterpret_cast(pv);
+ if (pwtd)
+ {
+ PostMessage(pwtd->hwndManager, SRM_REGEX_STARTED, GetCurrentThreadId(), 0);
+
+ // Wait to be told we can begin
+ if (WaitForSingleObject(pwtd->startEvent, INFINITE) == WAIT_OBJECT_0)
+ {
+ CComPtr spRenameRegEx;
+ if (SUCCEEDED(pwtd->spsrm->get_smartRenameRegEx(&spRenameRegEx)))
+ {
+ DWORD flags = 0;
+ spRenameRegEx->get_flags(&flags);
+
+ UINT itemCount = 0;
+ unsigned long itemEnumIndex = 1;
+ pwtd->spsrm->GetItemCount(&itemCount);
+ for (UINT u = 0; u <= itemCount; u++)
+ {
+ // Check if cancel event is signaled
+ if (WaitForSingleObject(pwtd->cancelEvent, 0) == WAIT_OBJECT_0)
+ {
+ // Canceled from manager
+ // Send the manager thread the canceled message
+ PostMessage(pwtd->hwndManager, SRM_REGEX_CANCELED, GetCurrentThreadId(), 0);
+ break;
+ }
+
+ CComPtr spItem;
+ if (SUCCEEDED(pwtd->spsrm->GetItemByIndex(u, &spItem)))
+ {
+ int id = -1;
+ spItem->get_id(&id);
+
+ bool isFolder = false;
+ bool isSubFolderContent = false;
+ spItem->get_isFolder(&isFolder);
+ spItem->get_isSubFolderContent(&isSubFolderContent);
+ if ((isFolder && (flags & PowerRenameFlags::ExcludeFolders)) ||
+ (!isFolder && (flags & PowerRenameFlags::ExcludeFiles)) ||
+ (isSubFolderContent && (flags & PowerRenameFlags::ExcludeSubfolders)))
+ {
+ // Exclude this item from renaming. Ensure new name is cleared.
+ spItem->put_newName(nullptr);
+
+ // Send the manager thread the item processed message
+ PostMessage(pwtd->hwndManager, SRM_REGEX_ITEM_UPDATED, GetCurrentThreadId(), id);
+
+ continue;
+ }
+
+ PWSTR originalName = nullptr;
+ if (SUCCEEDED(spItem->get_originalName(&originalName)))
+ {
+ PWSTR currentNewName = nullptr;
+ spItem->get_newName(¤tNewName);
+
+ wchar_t sourceName[MAX_PATH] = { 0 };
+ if (flags & NameOnly)
+ {
+ StringCchCopy(sourceName, ARRAYSIZE(sourceName), fs::path(originalName).stem().c_str());
+ }
+ else if (flags & ExtensionOnly)
+ {
+ std::wstring extension = fs::path(originalName).extension().wstring();
+ if (!extension.empty() && extension.front() == '.')
+ {
+ extension = extension.erase(0, 1);
+ }
+ StringCchCopy(sourceName, ARRAYSIZE(sourceName), extension.c_str());
+ }
+ else
+ {
+ StringCchCopy(sourceName, ARRAYSIZE(sourceName), originalName);
+ }
+
+
+ PWSTR newName = nullptr;
+ // Failure here means we didn't match anything or had nothing to match
+ // Call put_newName with null in that case to reset it
+ spRenameRegEx->Replace(sourceName, &newName);
+
+ wchar_t resultName[MAX_PATH] = { 0 };
+
+ PWSTR newNameToUse = nullptr;
+
+ // newName == nullptr likely means we have an empty search string. We should leave newNameToUse
+ // as nullptr so we clear the renamed column
+ if (newName != nullptr)
+ {
+ newNameToUse = resultName;
+ if (flags & NameOnly)
+ {
+ StringCchPrintf(resultName, ARRAYSIZE(resultName), L"%s%s", newName, fs::path(originalName).extension().c_str());
+ }
+ else if (flags & ExtensionOnly)
+ {
+ std::wstring extension = fs::path(originalName).extension().wstring();
+ if (!extension.empty())
+ {
+ StringCchPrintf(resultName, ARRAYSIZE(resultName), L"%s.%s", fs::path(originalName).stem().c_str(), newName);
+ }
+ else
+ {
+ StringCchCopy(resultName, ARRAYSIZE(resultName), originalName);
+ }
+ }
+ else
+ {
+ StringCchCopy(resultName, ARRAYSIZE(resultName), newName);
+ }
+ }
+
+ // No change from originalName so set newName to
+ // null so we clear it from our UI as well.
+ if (lstrcmp(originalName, newNameToUse) == 0)
+ {
+ newNameToUse = nullptr;
+ }
+
+ wchar_t uniqueName[MAX_PATH] = { 0 };
+ if (newNameToUse != nullptr && (flags & EnumerateItems))
+ {
+ unsigned long countUsed = 0;
+ if (GetEnumeratedFileName(uniqueName, ARRAYSIZE(uniqueName), newNameToUse, nullptr, itemEnumIndex, &countUsed))
+ {
+ newNameToUse = uniqueName;
+ }
+ itemEnumIndex++;
+ }
+
+ spItem->put_newName(newNameToUse);
+
+ // Was there a change?
+ if (lstrcmp(currentNewName, newNameToUse) != 0)
+ {
+ // Send the manager thread the item processed message
+ PostMessage(pwtd->hwndManager, SRM_REGEX_ITEM_UPDATED, GetCurrentThreadId(), id);
+ }
+
+ CoTaskMemFree(newName);
+ CoTaskMemFree(currentNewName);
+ CoTaskMemFree(originalName);
+ }
+ }
+ }
+ }
+ }
+
+ // Send the manager thread the completion message
+ PostMessage(pwtd->hwndManager, SRM_REGEX_COMPLETE, GetCurrentThreadId(), 0);
+
+ delete pwtd;
+ }
+ CoUninitialize();
+ }
+
+ return 0;
+}
+
+void CPowerRenameManager::_CancelRegExWorkerThread()
+{
+ if (m_startRegExWorkerEvent)
+ {
+ SetEvent(m_startRegExWorkerEvent);
+ }
+
+ if (m_cancelRegExWorkerEvent)
+ {
+ SetEvent(m_cancelRegExWorkerEvent);
+ }
+
+ _WaitForRegExWorkerThread();
+}
+
+void CPowerRenameManager::_WaitForRegExWorkerThread()
+{
+ if (m_regExWorkerThreadHandle)
+ {
+ WaitForSingleObject(m_regExWorkerThreadHandle, INFINITE);
+ CloseHandle(m_regExWorkerThreadHandle);
+ m_regExWorkerThreadHandle = nullptr;
+ }
+}
+
+void CPowerRenameManager::_Cancel()
+{
+ SetEvent(m_startFileOpWorkerEvent);
+ _CancelRegExWorkerThread();
+}
+
+HRESULT CPowerRenameManager::_EnsureRegEx()
+{
+ HRESULT hr = S_OK;
+ if (!m_spRegEx)
+ {
+ // Create the default regex handler
+ hr = CPowerRenameRegEx::s_CreateInstance(&m_spRegEx);
+ if (SUCCEEDED(hr))
+ {
+ hr = _InitRegEx();
+ // Get the flags
+ if (SUCCEEDED(hr))
+ {
+ m_spRegEx->get_flags(&m_flags);
+ }
+ }
+ }
+ return hr;
+}
+
+HRESULT CPowerRenameManager::_InitRegEx()
+{
+ HRESULT hr = E_FAIL;
+ if (m_spRegEx)
+ {
+ hr = m_spRegEx->Advise(this, &m_regExAdviseCookie);
+ }
+
+ return hr;
+}
+
+void CPowerRenameManager::_ClearRegEx()
+{
+ if (m_spRegEx)
+ {
+ m_spRegEx->UnAdvise(m_regExAdviseCookie);
+ m_regExAdviseCookie = 0;
+ }
+}
+
+void CPowerRenameManager::_OnItemAdded(_In_ IPowerRenameItem* renameItem)
+{
+ CSRWSharedAutoLock lock(&m_lockEvents);
+
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ if (it.pEvents)
+ {
+ it.pEvents->OnItemAdded(renameItem);
+ }
+ }
+}
+
+void CPowerRenameManager::_OnUpdate(_In_ IPowerRenameItem* renameItem)
+{
+ CSRWSharedAutoLock lock(&m_lockEvents);
+
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ if (it.pEvents)
+ {
+ it.pEvents->OnUpdate(renameItem);
+ }
+ }
+}
+
+void CPowerRenameManager::_OnError(_In_ IPowerRenameItem* renameItem)
+{
+ CSRWSharedAutoLock lock(&m_lockEvents);
+
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ if (it.pEvents)
+ {
+ it.pEvents->OnError(renameItem);
+ }
+ }
+}
+
+void CPowerRenameManager::_OnRegExStarted(_In_ DWORD threadId)
+{
+ CSRWSharedAutoLock lock(&m_lockEvents);
+
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ if (it.pEvents)
+ {
+ it.pEvents->OnRegExStarted(threadId);
+ }
+ }
+}
+
+void CPowerRenameManager::_OnRegExCanceled(_In_ DWORD threadId)
+{
+ CSRWSharedAutoLock lock(&m_lockEvents);
+
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ if (it.pEvents)
+ {
+ it.pEvents->OnRegExCanceled(threadId);
+ }
+ }
+}
+
+void CPowerRenameManager::_OnRegExCompleted(_In_ DWORD threadId)
+{
+ CSRWSharedAutoLock lock(&m_lockEvents);
+
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ if (it.pEvents)
+ {
+ it.pEvents->OnRegExCompleted(threadId);
+ }
+ }
+}
+
+void CPowerRenameManager::_OnRenameStarted()
+{
+ CSRWSharedAutoLock lock(&m_lockEvents);
+
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ if (it.pEvents)
+ {
+ it.pEvents->OnRenameStarted();
+ }
+ }
+}
+
+void CPowerRenameManager::_OnRenameCompleted()
+{
+ CSRWSharedAutoLock lock(&m_lockEvents);
+
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ if (it.pEvents)
+ {
+ it.pEvents->OnRenameCompleted();
+ }
+ }
+}
+
+void CPowerRenameManager::_ClearEventHandlers()
+{
+ CSRWExclusiveAutoLock lock(&m_lockEvents);
+
+ // Cleanup event handlers
+ for (auto it : m_PowerRenameManagerEvents)
+ {
+ it.cookie = 0;
+ if (it.pEvents)
+ {
+ it.pEvents->Release();
+ it.pEvents = nullptr;
+ }
+ }
+
+ m_PowerRenameManagerEvents.clear();
+}
+
+void CPowerRenameManager::_ClearPowerRenameItems()
+{
+ CSRWExclusiveAutoLock lock(&m_lockItems);
+
+ // Cleanup smart rename items
+ for (auto it : m_smartRenameItems)
+ {
+ IPowerRenameItem* pItem = it.second;
+ pItem->Release();
+ }
+
+ m_smartRenameItems.clear();
+}
+
+void CPowerRenameManager::_Cleanup()
+{
+ if (m_hwndMessage)
+ {
+ DestroyWindow(m_hwndMessage);
+ m_hwndMessage = nullptr;
+ }
+
+ CloseHandle(m_startFileOpWorkerEvent);
+ m_startFileOpWorkerEvent = nullptr;
+
+ CloseHandle(m_startRegExWorkerEvent);
+ m_startRegExWorkerEvent = nullptr;
+
+ CloseHandle(m_cancelRegExWorkerEvent);
+ m_cancelRegExWorkerEvent = nullptr;
+
+ _ClearRegEx();
+ _ClearEventHandlers();
+ _ClearPowerRenameItems();
+}
diff --git a/src/modules/powerrename/lib/PowerRenameManager.h b/src/modules/powerrename/lib/PowerRenameManager.h
new file mode 100644
index 0000000000..0179e3b4fe
--- /dev/null
+++ b/src/modules/powerrename/lib/PowerRenameManager.h
@@ -0,0 +1,120 @@
+#pragma once
+#include
+#include