PowerRename is a PowerToys module that adds a bulk file renaming tool to the Windows shell context menu. This page covers the shell extension integration, the COM interface design, the renaming engine, text transformations, date/time and metadata pattern substitution, and the settings system. For the general module lifecycle and how modules are loaded by the runner, see Core Architecture and Module Loading System. For Group Policy integration patterns shared across modules, see Group Policy Integration.
PowerRename consists of two distinct processes:
PowerRename.dll) โ registered with Windows Explorer. Adds the context menu entry and launches the UI process.PowerToys.PowerRename.exe) โ a standalone WinUI 3 application that displays the rename preview and drives the rename operation.The DLL also houses the PowerRenameModule class, which implements PowertoyModuleIface so the PowerToys Runner can enable or disable the module.
Component architecture
Sources: src/modules/powerrename/dll/dllmain.cpp162-355 src/modules/powerrename/dll/PowerRenameExt.cpp134-255 src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp57-58
PowerRenameModule)PowerRenameModule in src/modules/powerrename/dll/dllmain.cpp162-350 implements PowertoyModuleIface. Key behaviors:
enable() โ On Windows 11 and later, calls package::RegisterSparsePackage() to register the MSIX package (PowerRenameContextMenuPackage.msix) needed for the modern context menu. Then calls PowerRenameRuntimeRegistration::EnsureRegistered().disable() โ Calls PowerRenameRuntimeRegistration::Unregister() to remove the context menu entry.gpo_policy_enabled_configuration() โ Delegates to powertoys_gpo::getConfiguredPowerRenameEnabledValue().get_config() / set_config() โ Serialize and deserialize module settings using PowerToysSettings::Settings and CSettingsInstance().The DLL entry point powertoy_create() at src/modules/powerrename/dll/dllmain.cpp352-355 returns a new PowerRenameModule.
CPowerRenameClassFactory src/modules/powerrename/dll/dllmain.cpp25-103 is a standard IClassFactory implementation. DllGetClassObject creates one for CLSID_PowerRenameMenu, which in turn calls CPowerRenameMenu::s_CreateInstance.
CPowerRenameMenu)CPowerRenameMenu src/modules/powerrename/dll/PowerRenameExt.h6-77 implements three COM interfaces simultaneously:
| Interface | Purpose |
|---|---|
IShellExtInit | Classic context menu path (Windows 10 and pre-MSIX) |
IContextMenu | Populates and handles classic context menu items |
IExplorerCommand | Modern context menu path (Windows 11 MSIX) |
IShellExtInit / IContextMenu)Initialize() src/modules/powerrename/dll/PowerRenameExt.cpp49-70 โ Caches the IDataObject from the shell, which provides the list of selected file paths.QueryContextMenu() src/modules/powerrename/dll/PowerRenameExt.cpp73-127 โ Checks CSettingsInstance().GetEnabled(), verifies at least one selected item has SFGAO_CANRENAME, respects the GetExtendedContextMenuOnly() setting, and inserts the "Rename with PowerRename" menu item.InvokeCommand() src/modules/powerrename/dll/PowerRenameExt.cpp129-132 โ Calls RunPowerRename(pici, nullptr).IExplorerCommand)On Windows 11 with the MSIX package, IShellExtInit is never called. Invoke() src/modules/powerrename/dll/PowerRenameExt.cpp296-325 marshals the IShellItemArray cross-thread and calls RunPowerRename(nullptr, psiItemArray).
RunPowerRename() src/modules/powerrename/dll/PowerRenameExt.cpp134-255 launches PowerToys.PowerRename.exe and streams selected file paths through an anonymous pipe:
CreatePipe). The read end is passed as hStdInput to the child process.? as a delimiter.The UI process reads file paths from its standard input on startup.
The renaming engine is structured around a small set of COM interfaces defined in src/modules/powerrename/lib/PowerRenameInterfaces.h
COM interface relationships
Sources: src/modules/powerrename/lib/PowerRenameInterfaces.h src/modules/powerrename/lib/PowerRenameManager.h src/modules/powerrename/lib/PowerRenameRegEx.h src/modules/powerrename/lib/PowerRenameItem.h
PowerRenameFlagsThe DWORD flags value passed through the system is a bitmask of PowerRenameFlags src/modules/powerrename/lib/PowerRenameInterfaces.h9-31:
| Flag | Value | Meaning |
|---|---|---|
CaseSensitive | 0x1 | Case-sensitive search |
MatchAllOccurrences | 0x2 | Replace all matches, not just the first |
UseRegularExpressions | 0x4 | Treat search term as regex |
EnumerateItems | 0x8 | Insert a counter into replace term |
ExcludeFiles | 0x10 | Skip files, rename folders only |
ExcludeFolders | 0x20 | Skip folders, rename files only |
ExcludeSubfolders | 0x40 | Only rename top-level selected items |
NameOnly | 0x80 | Apply search only to filename stem |
ExtensionOnly | 0x100 | Apply search only to extension |
Uppercase | 0x200 | Transform result to UPPERCASE |
Lowercase | 0x400 | Transform result to lowercase |
Titlecase | 0x800 | Transform result to Title Case |
Capitalized | 0x1000 | Capitalize Each Word |
RandomizeItems | 0x2000 | Insert random values into replace term |
CreationTime | 0x4000 | Use file creation time for $ tokens |
ModificationTime | 0x8000 | Use file modification time |
AccessTime | 0x10000 | Use file access time |
MetadataSourceEXIF | 0x20000 | Use EXIF metadata source |
MetadataSourceXMP | 0x40000 | Use XMP metadata source |
CPowerRenameManagerCPowerRenameManager src/modules/powerrename/lib/PowerRenameManager.cpp manages the collection of IPowerRenameItem objects and coordinates two background worker threads.
Items are stored in m_renameItems, a std::map<int, IPowerRenameItem*> keyed by item ID. A parallel m_isVisible vector tracks which items are visible under the current filter. GetVisibleItemByIndex() maps visible indices back to real indices via GetVisibleItemRealIndex().
A hidden message-only window (m_hwndMessage, created via CreateMsgWindow) receives messages from worker threads:
| Message | Meaning |
|---|---|
SRM_REGEX_ITEM_UPDATED | One item processed by RegEx worker |
SRM_REGEX_ITEM_RENAMED_KEEP_UI | One item renamed (UI stays open) |
SRM_REGEX_STARTED | RegEx pass began |
SRM_REGEX_CANCELED | RegEx pass was canceled |
SRM_REGEX_COMPLETE | RegEx pass finished |
SRM_FILEOP_COMPLETE | File operations finished |
RegEx worker thread (s_regExWorkerThread): iterates all items, calls CPowerRenameRegEx::Replace() for each, updates IPowerRenameItem::PutNewName().
FileOp worker thread (s_fileOpWorkerThread): creates a COM IFileOperation instance, adds rename operations in deepest-first depth order, then calls PerformOperations(). Rename flags used: FOF_ALLOWUNDO | FOFX_ADDUNDORECORD | FOFX_SHOWELEVATIONPROMPT | FOF_RENAMEONCOLLISION.
src/modules/powerrename/lib/PowerRenameManager.cpp16
Preview update flow (triggered by any UI change):
Execute (Apply) flow:
Sources: src/modules/powerrename/lib/PowerRenameManager.cpp649-875
CPowerRenameRegEx โ Replacement EngineCPowerRenameRegEx src/modules/powerrename/lib/PowerRenameRegEx.cpp implements IPowerRenameRegEx. Its Replace() method performs all substitution logic on a single filename string.
SanitizeAndNormalize() src/modules/powerrename/lib/PowerRenameRegEx.cpp23-58 runs on both search term and source filename before matching:
U+00A0) with regular spaces.NormalizeString(NormalizationC, ...).CPowerRenameRegEx supports two regex backends, selected at runtime via CSettingsInstance().GetUseBoostLib():
The standard library engine (std::wregex) does not support lookbehind assertions; Boost.Regex does. This is the primary reason for the optional Boost path.
src/modules/powerrename/lib/PowerRenameRegEx.cpp421-432
Within Replace() src/modules/powerrename/lib/PowerRenameRegEx.cpp434-634 the replace term goes through a staged pipeline before being used in actual substitution:
Sources: src/modules/powerrename/lib/PowerRenameRegEx.cpp434-634
When EnumerateItems is set, parseEnumOptions() (from Enumerating.h) scans the raw replace term for counter expressions. The counter syntax supports custom start, increment, and padding. Counter expressions are removed from the replace term and their insertion offsets stored in m_replaceWithEnumeratorOffsets. The counter index (enumIndex) is incremented per match.
When RandomizeItems is set, parseRandomizerOptions() (from Randomizer.h) similarly parses random value expressions. The Randomizer::randomize() method produces a random string per file.
Both can coexist in a single replace term; _OnEnumerateOrRandomizeItemsChanged() src/modules/powerrename/lib/PowerRenameRegEx.cpp181-281 merges them by offset order.
src/modules/powerrename/lib/Helpers.cpp provides the key filename transformation functions.
GetTransformedFileName)GetTransformedFileName() src/modules/powerrename/lib/Helpers.cpp93-294 applies case transforms based on flag bits. Supports NameOnly and ExtensionOnly scoping:
| Flag | Behavior |
|---|---|
Uppercase | towupper on each character |
Lowercase | towlower on each character |
Titlecase | Capitalizes first letter of each word; a built-in exception list (a, an, the, at, by, for, in, of, on, up, and, as, but, or, nor) are left lowercase unless they appear first or last |
Capitalized | Capitalizes first letter of every word (no exceptions) |
Apostrophe-containing words (e.g., I'll, you're) are handled to avoid incorrectly capitalizing the letter after the apostrophe.
GetDatedFileName)GetDatedFileName() src/modules/powerrename/lib/Helpers.cpp393-511 replaces $-prefixed tokens in the replace term with values from a SYSTEMTIME. Longer patterns are matched first (e.g., $YYYY before $YY before $Y). $$ escapes a literal $.
Available tokens (processed longest-first to avoid prefix collisions):
| Token | Output |
|---|---|
$YYYY | 4-digit year |
$YY | 2-digit year (last two digits) |
$Y | 1-digit year (last digit) |
$MMMM | Full month name (locale-aware) |
$MMM | Abbreviated month name |
$MM | 2-digit month (leading zero) |
$M | Month digits (no padding) |
$DDDD | Full weekday name |
$DDD | Abbreviated weekday name |
$DD | 2-digit day (leading zero) |
$D | Day digits (no padding) |
$HH | 12-hour clock (leading zero) |
$H | 12-hour clock (no padding) |
$TT | AM/PM uppercase |
$tt | am/pm lowercase |
$hh | 24-hour clock (leading zero) |
$h | 24-hour clock (no padding) |
$mm | Minutes (leading zero) |
$m | Minutes (no padding) |
$ss | Seconds (leading zero) |
$s | Seconds (no padding) |
$fff | Milliseconds (3 digits) |
$ff | Milliseconds (first 2 digits) |
$f | Milliseconds (first digit) |
Month and day names use GetDateFormatEx() with the user's default locale.
Sources: src/modules/powerrename/lib/Helpers.cpp393-511
GetMetadataFileName)GetMetadataFileName() src/modules/powerrename/lib/Helpers.cpp513-595 replaces $PATTERN_NAME tokens with values from a MetadataPatternMap. Pattern names are defined by PowerRenameLib::MetadataPatternExtractor::GetAllPossiblePatterns(). Greedy longest-match is used so that $DATE_TAKEN_YYYY is not mistakenly matched as $DATE_TAKEN_YY + Y.
If a pattern is recognized but has no value in the map (e.g., metadata not present), the token is kept as-is ($PATTERN_NAME) to give visual feedback.
isMetadataUsed() src/modules/powerrename/lib/Helpers.cpp322-391 provides a fast early-exit check. It verifies the file extension is in the supported set before scanning for pattern tokens. Supported extensions: .jpg, .jpeg, .png, .tif, .tiff, .heic, .heif, .avif.
Sources: src/modules/powerrename/lib/Helpers.cpp513-595 src/modules/powerrename/lib/Helpers.cpp322-391
CPowerRenameItemCPowerRenameItem src/modules/powerrename/lib/PowerRenameItem.cpp implements both IPowerRenameItem and IPowerRenameItemFactory. Key responsibilities:
GetTime() src/modules/powerrename/lib/PowerRenameItem.cpp59-135 opens the file with CreateFileW and calls GetFileTime() to retrieve creation, modification, or access time, then converts to local SYSTEMTIME. Results are cached in m_time.ShouldRenameItem() evaluates ExcludeFiles, ExcludeFolders, ExcludeSubfolders, NameOnly, ExtensionOnly flags, and checks that m_newName != m_originalName.s_CreateInstance() is the factory method; it also implements IPowerRenameItemFactory::Create().MainWindow)MainWindow src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp is a WinUI 3 window that owns g_prManager (IPowerRenameManager) and drives the preview/apply workflow.
The listView_ExplorerItems is backed by ExplorerItemsSource and uses VirtualizingStackPanel.VirtualizationMode=Standard.
When CSettingsInstance().GetMRUEnabled() is true, the search and replace text boxes show m_searchMRUList and m_replaceMRUList as IObservableVector<hstring>. The most recent entries are persisted via LastRunSettingsInstance().
Sources: src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml
Settings are split between two classes in src/modules/powerrename/lib/Settings.h and src/modules/powerrename/lib/Settings.cpp
CSettingsSingleton accessed via CSettingsInstance(). Persists to power-rename-settings.json in the PowerToys module data folder.
| Setting | Default | Description |
|---|---|---|
showIconOnMenu | true | Show the PowerRename icon in context menu |
extendedContextMenuOnly | false | Only appear in extended (Shift+right-click) menu |
persistState | true | Restore last search/replace terms on next launch |
MRUEnabled | true | Enable most-recently-used dropdown lists |
maxMRUSize | 10 | Maximum number of MRU entries |
useBoostLib | false | Use Boost.Regex instead of std::wregex |
GetEnabled() checks the GPO value first via powertoys_gpo::getConfiguredPowerRenameEnabledValue() before falling back to the JSON-stored value. Reload() compares lastLoadedTime against the file's ftLastWriteTime before re-parsing.
UI flags (the bitmask of PowerRenameFlags from the UI state) are stored separately in a binary file power-rename-ui-flags via ReadFlags() / WriteFlags().
LastRunSettingsSingleton accessed via LastRunSettingsInstance(). Persists to power-rename-last-run-data.json. Stores:
SearchText โ last search termReplaceText โ last replace termLastWindowWidth / LastWindowHeight โ window size (default 1400ร800)Sources: src/modules/powerrename/lib/Settings.h src/modules/powerrename/lib/Settings.cpp
Trace src/modules/powerrename/lib/trace.h src/modules/powerrename/lib/trace.cpp emits ETW events under the Microsoft.PowerToys provider:
Trace::EnablePowerRename(enabled) โ module enable/disable.Trace::Invoked() / Trace::InvokedRet(hr) โ context menu invocations.Trace::RenameOperation(totalItems, selected, renamed, flags, extensionList) โ logged before each actual file operation, includes flag bitmask and a comma-separated list of file extensions with counts.Trace::SettingsChanged() โ settings page save.Unit tests are in src/modules/powerrename/unittests/:
PowerRenameRegExTests.cpp and PowerRenameRegExBoostTests.cpp โ test CPowerRenameRegEx::Replace() using both backends. CommonRegExTests.h src/modules/powerrename/unittests/CommonRegExTests.h is #included into both test classes so the same test cases run against both engines.PowerRenameManagerTests.cpp src/modules/powerrename/unittests/PowerRenameManagerTests.cpp โ integration tests that create real temp files using CTestFileHelper, run CPowerRenameManager, and verify the files on disk were actually renamed or not. Covers flag combinations (files-only, folders-only, subfolders excluded, name-only, extension-only, case transforms, date patterns).HelpersTests.cpp src/modules/powerrename/unittests/HelpersTests.cpp โ unit tests for GetMetadataFileName(), GetTransformedFileName(), and GetTrimmedFileName().Refresh this wiki
This wiki was recently refreshed. Please wait 4 days to refresh again.