https://git.reactos.org/?p=reactos.git;a=commitdiff;h=4d0cc20681dff4b15e7b1…
commit 4d0cc20681dff4b15e7b1c950a35952af67346d9
Author: He Yang <1160386205(a)qq.com>
AuthorDate: Wed Sep 29 17:30:32 2021 +0800
Commit: GitHub <noreply(a)github.com>
CommitDate: Wed Sep 29 11:30:32 2021 +0200
[IERNONCE] [RUNONCEEX] Add RunOnceEx functionality for ReactOS (#3926)
* [IERNONCE] Implement the registry management code.
* [EXPLORER] handle RunOnceEx by invoking RunOnceEx in iernonce.dll
* [IERNONCE] Display a dialog to show progress, and execute entries.
* [IERNONCE] Add `InitCallback` function
---
base/shell/explorer/startup.cpp | 71 ++++++-
dll/win32/iernonce/CMakeLists.txt | 16 +-
dll/win32/iernonce/dialog.cpp | 237 ++++++++++++++++++++++
dll/win32/iernonce/dialog.h | 46 +++++
dll/win32/iernonce/iernonce.c | 40 ----
dll/win32/iernonce/iernonce.cpp | 61 ++++++
dll/win32/iernonce/iernonce.h | 24 +++
dll/win32/iernonce/iernonce.spec | 3 +-
dll/win32/iernonce/include/registry.h | 107 ++++++++++
dll/win32/iernonce/registry.cpp | 360 ++++++++++++++++++++++++++++++++++
sdk/include/reactos/iernonce_undoc.h | 30 +++
11 files changed, 949 insertions(+), 46 deletions(-)
diff --git a/base/shell/explorer/startup.cpp b/base/shell/explorer/startup.cpp
index 9e6f60705c6..e76a9cd6e35 100644
--- a/base/shell/explorer/startup.cpp
+++ b/base/shell/explorer/startup.cpp
@@ -3,6 +3,7 @@
* Copyright (C) 2002 Shachar Shemesh
* Copyright (C) 2013 Edijs Kolesnikovics
* Copyright (C) 2018 Katayama Hirofumi MZ
+ * Copyright (C) 2021 He Yang
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -28,7 +29,7 @@
* The operations performed are (by order of execution):
*
* After log in
- * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnceEx (synch, no
imp)
+ * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnceEx (synch)
* - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce (synch)
* - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run (asynch)
* - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run (asynch)
@@ -36,7 +37,7 @@
* - Current user Startup folder "%USERPROFILE%\Start Menu\Programs\Startup"
(asynch, no imp)
* - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce (asynch)
*
- * None is processed in Safe Mode // FIXME: Check RunOnceEx in Safe Mode
+ * None is processed in Safe Mode
*/
#include "precomp.h"
@@ -317,6 +318,68 @@ end:
return res == ERROR_SUCCESS ? TRUE : FALSE;
}
+/**
+ * Process "RunOnceEx" type registry key.
+ * rundll32.exe will be invoked if the corresponding key has items inside, and wait for
it.
+ * hkRoot is the HKEY from which
+ * "Software\Microsoft\Windows\CurrentVersion\RunOnceEx"
+ * is opened.
+ */
+static BOOL ProcessRunOnceEx(HKEY hkRoot)
+{
+ HKEY hkRunOnceEx = NULL;
+ LONG res = ERROR_SUCCESS;
+ WCHAR cmdLine[] = L"rundll32 iernonce.dll RunOnceExProcess";
+ DWORD dwSubKeyCnt;
+
+ res = RegOpenKeyExW(hkRoot,
+
L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx",
+ 0,
+ KEY_READ,
+ &hkRunOnceEx);
+ if (res != ERROR_SUCCESS)
+ {
+ TRACE("RegOpenKeyW failed on
Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx (%ld)\n", res);
+ goto end;
+ }
+
+ res = RegQueryInfoKeyW(hkRunOnceEx,
+ NULL,
+ NULL,
+ NULL,
+ &dwSubKeyCnt,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ if (res != ERROR_SUCCESS)
+ {
+ TRACE("RegQueryInfoKeyW failed on
Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx (%ld)\n", res);
+ goto end;
+ }
+
+ if (dwSubKeyCnt != 0)
+ {
+ if (runCmd(cmdLine, NULL, TRUE, TRUE) == INVALID_RUNCMD_RETURN)
+ {
+ TRACE("runCmd failed (%ld)\n", res = GetLastError());
+ goto end;
+ }
+ }
+
+end:
+ if (hkRunOnceEx != NULL)
+ RegCloseKey(hkRunOnceEx);
+
+ TRACE("done\n");
+
+ return res == ERROR_SUCCESS ? TRUE : FALSE;
+}
+
static BOOL
AutoStartupApplications(INT nCSIDL_Folder)
{
@@ -414,7 +477,9 @@ INT ProcessStartupItems(VOID)
* stopping if one fails, skipping if necessary.
*/
res = TRUE;
- /* TODO: RunOnceEx */
+
+ if (res && bNormalBoot)
+ ProcessRunOnceEx(HKEY_LOCAL_MACHINE);
if (res && (SHRestricted(REST_NOLOCALMACHINERUNONCE) == 0))
res = ProcessRunKeys(HKEY_LOCAL_MACHINE, L"RunOnce", TRUE, TRUE);
diff --git a/dll/win32/iernonce/CMakeLists.txt b/dll/win32/iernonce/CMakeLists.txt
index 85b73cdf6e8..78d7143c049 100644
--- a/dll/win32/iernonce/CMakeLists.txt
+++ b/dll/win32/iernonce/CMakeLists.txt
@@ -1,11 +1,23 @@
+project(iernonce)
+
+include_directories(include)
spec2def(iernonce.dll iernonce.spec)
+list(APPEND SOURCE
+ dialog.cpp
+ iernonce.cpp
+ registry.cpp
+ iernonce.h)
+
add_library(iernonce MODULE
- iernonce.c
+ ${SOURCE}
iernonce.rc
${CMAKE_CURRENT_BINARY_DIR}/iernonce.def)
set_module_type(iernonce win32dll UNICODE)
-add_importlibs(iernonce msvcrt kernel32 ntdll)
+target_link_libraries(iernonce cppstl atl_classes)
+set_target_cpp_properties(iernonce WITH_EXCEPTIONS)
+add_importlibs(iernonce advapi32 msvcrt gdi32 ole32 shell32 shlwapi kernel32 user32
ntdll)
+add_pch(iernonce iernonce.h SOURCE)
add_cd_file(TARGET iernonce DESTINATION reactos/system32 FOR all)
diff --git a/dll/win32/iernonce/dialog.cpp b/dll/win32/iernonce/dialog.cpp
new file mode 100644
index 00000000000..1f3756a2745
--- /dev/null
+++ b/dll/win32/iernonce/dialog.cpp
@@ -0,0 +1,237 @@
+/*
+ * PROJECT: ReactOS system libraries
+ * LICENSE: GPL-2.0-or-later (
https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Classes for displaying progress dialog.
+ * COPYRIGHT: Copyright 2021 He Yang <1160386205(a)qq.com>
+ */
+
+#include "iernonce.h"
+#include <process.h>
+
+#define ITEM_VPADDING 3
+#define ITEM_LEFTPADDING 22
+
+HFONT CreateBoldFont(_In_ HFONT hOrigFont)
+{
+ LOGFONTW fontAttributes = { 0 };
+ GetObjectW(hOrigFont, sizeof(fontAttributes), &fontAttributes);
+ fontAttributes.lfWeight = FW_BOLD;
+
+ return CreateFontIndirectW(&fontAttributes);
+}
+
+ProgressDlg::ProgressDlg(_In_ RunOnceExInstance &RunOnceExInst) :
+ m_hListBox(NULL),
+ m_hBoldFont(NULL),
+ m_PointedItem(0),
+ m_RunOnceExInst(RunOnceExInst)
+{ ; }
+
+BOOL ProgressDlg::RunDialogBox()
+{
+ // Show the dialog and run the items only when the list is not empty.
+ if (m_RunOnceExInst.m_SectionList.GetSize() != 0)
+ {
+ return (DoModal() == 1);
+ }
+ return TRUE;
+}
+
+void ProgressDlg::CalcTextRect(
+ _In_ LPCWSTR lpText,
+ _Inout_ PRECT pRect)
+{
+ HDC hdc = ::GetDC(m_hListBox);
+ ::GetClientRect(m_hListBox, pRect);
+
+ pRect->bottom = pRect->top;
+ pRect->left += ITEM_LEFTPADDING;
+
+ HFONT OldFont = SelectFont(hdc, GetFont());
+ DrawTextW(hdc, lpText, -1, pRect, DT_CALCRECT | DT_WORDBREAK);
+ SelectFont(hdc, OldFont);
+ ::ReleaseDC(m_hListBox, hdc);
+
+ pRect->bottom -= pRect->top;
+ pRect->bottom += ITEM_VPADDING * 2;
+ pRect->top = 0;
+ pRect->right -= pRect->left;
+ pRect->left = 0;
+}
+
+void ProgressDlg::ResizeListBoxAndDialog(_In_ int NewHeight)
+{
+ RECT ListBoxRect;
+ RECT DlgRect;
+ ::GetWindowRect(m_hListBox, &ListBoxRect);
+ GetWindowRect(&DlgRect);
+
+ int HeightDiff = NewHeight - (ListBoxRect.bottom - ListBoxRect.top);
+
+ ::SetWindowPos(m_hListBox, NULL, 0, 0,
+ ListBoxRect.right - ListBoxRect.left, NewHeight,
+ SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
+
+ SetWindowPos(HWND_TOP, 0, 0,
+ DlgRect.right - DlgRect.left,
+ DlgRect.bottom - DlgRect.top + HeightDiff,
+ SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
+}
+
+unsigned int __stdcall
+RunOnceExExecThread(_In_ void *Param)
+{
+ ProgressDlg *pProgressDlg = (ProgressDlg *)Param;
+
+ pProgressDlg->m_RunOnceExInst.Exec(pProgressDlg->m_hWnd);
+ return 0;
+}
+
+BOOL
+ProgressDlg::ProcessWindowMessage(
+ _In_ HWND hwnd,
+ _In_ UINT message,
+ _In_ WPARAM wParam,
+ _In_ LPARAM lParam,
+ _Out_ LRESULT& lResult,
+ _In_ DWORD dwMsgMapID)
+{
+ lResult = 0;
+ switch (message)
+ {
+ case WM_INITDIALOG:
+ {
+ if (!m_RunOnceExInst.m_Title.IsEmpty())
+ {
+ SetWindowTextW(m_RunOnceExInst.m_Title);
+ }
+
+ m_hListBox = GetDlgItem(IDC_LB_ITEMS);
+
+ m_hBoldFont = CreateBoldFont(GetFont());
+
+ m_hArrowBmp = LoadBitmapW(NULL, MAKEINTRESOURCE(OBM_MNARROW));
+ GetObjectW(m_hArrowBmp, sizeof(BITMAP), &m_ArrowBmp);
+
+ // Add all sections with non-empty title into listbox
+ int TotalHeight = 0;
+ for (int i = 0; i < m_RunOnceExInst.m_SectionList.GetSize(); i++)
+ {
+ RunOnceExSection &Section = m_RunOnceExInst.m_SectionList[i];
+
+ if (!Section.m_SectionTitle.IsEmpty())
+ {
+ INT Index = ListBox_AddString(m_hListBox, Section.m_SectionTitle);
+ TotalHeight += ListBox_GetItemHeight(m_hListBox, Index);
+ ListBox_SetItemData(m_hListBox, Index, i);
+ }
+ }
+
+ // Remove the sunken-edged border from the listbox.
+ ::SetWindowLongPtr(m_hListBox, GWL_EXSTYLE, ::GetWindowLongPtr(m_hListBox,
GWL_EXSTYLE) & ~WS_EX_CLIENTEDGE);
+
+ ResizeListBoxAndDialog(TotalHeight);
+
+ // Launch a thread to execute tasks.
+ HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, RunOnceExExecThread, (void
*)this, 0, NULL);
+ if (hThread == INVALID_HANDLE_VALUE)
+ {
+ EndDialog(0);
+ return TRUE;
+ }
+ CloseHandle(hThread);
+
+ lResult = TRUE; // set keyboard focus to the dialog box control.
+ break;
+ }
+
+ case WM_MEASUREITEM:
+ {
+ PMEASUREITEMSTRUCT pMeasureItem = (PMEASUREITEMSTRUCT)lParam;
+ RECT TextRect = { 0 };
+
+ CStringW ItemText;
+ ListBox_GetText(m_hListBox, pMeasureItem->itemID,
+ ItemText.GetBuffer(ListBox_GetTextLen(m_hListBox,
+ pMeasureItem->itemID) + 1));
+
+ CalcTextRect(ItemText, &TextRect);
+
+ ItemText.ReleaseBuffer();
+
+ pMeasureItem->itemHeight = TextRect.bottom - TextRect.top;
+ pMeasureItem->itemWidth = TextRect.right - TextRect.left;
+
+ break;
+ }
+
+ case WM_DRAWITEM:
+ {
+ LPDRAWITEMSTRUCT pDrawItem = (PDRAWITEMSTRUCT)lParam;
+ CStringW ItemText;
+
+ ListBox_GetText(m_hListBox, pDrawItem->itemID,
+ ItemText.GetBuffer(ListBox_GetTextLen(m_hListBox,
+ pDrawItem->itemID) + 1));
+
+ SetBkMode(pDrawItem->hDC, TRANSPARENT);
+
+ HFONT hOldFont = NULL;
+ if (m_PointedItem == (INT)pDrawItem->itemData)
+ {
+ HDC hCompDC = CreateCompatibleDC(pDrawItem->hDC);
+
+ SelectBitmap(hCompDC, m_hArrowBmp);
+
+ int IconLeftPadding = (ITEM_LEFTPADDING - m_ArrowBmp.bmWidth) / 2;
+ int IconTopPadding = (pDrawItem->rcItem.bottom -
pDrawItem->rcItem.top - m_ArrowBmp.bmHeight) / 2;
+
+ BitBlt(pDrawItem->hDC, IconLeftPadding, pDrawItem->rcItem.top +
IconTopPadding,
+ m_ArrowBmp.bmWidth, m_ArrowBmp.bmHeight, hCompDC, 0, 0, SRCAND);
+
+ DeleteDC(hCompDC);
+
+ hOldFont = SelectFont(pDrawItem->hDC, m_hBoldFont);
+ }
+
+ pDrawItem->rcItem.left += ITEM_LEFTPADDING;
+ pDrawItem->rcItem.top += ITEM_VPADDING;
+ DrawTextW(pDrawItem->hDC, ItemText, -1,
+ &(pDrawItem->rcItem), DT_WORDBREAK);
+
+ if (hOldFont)
+ {
+ SelectFont(pDrawItem->hDC, hOldFont);
+ }
+ ItemText.ReleaseBuffer();
+
+ break;
+ }
+
+ case WM_SETINDEX:
+ {
+ if ((int)wParam == m_RunOnceExInst.m_SectionList.GetSize())
+ {
+ // All sections are handled, lParam is bSuccess.
+ EndDialog(lParam);
+ }
+ m_PointedItem = wParam;
+ InvalidateRect(NULL);
+ break;
+ }
+
+ case WM_CTLCOLORLISTBOX:
+ {
+ lResult = (LRESULT)GetStockBrush(NULL_BRUSH);
+ break;
+ }
+
+ case WM_DESTROY:
+ {
+ DeleteObject(m_hArrowBmp);
+ DeleteFont(m_hBoldFont);
+ break;
+ }
+ }
+ return TRUE;
+}
diff --git a/dll/win32/iernonce/dialog.h b/dll/win32/iernonce/dialog.h
new file mode 100644
index 00000000000..e459c9b09bd
--- /dev/null
+++ b/dll/win32/iernonce/dialog.h
@@ -0,0 +1,46 @@
+/*
+ * PROJECT: ReactOS system libraries
+ * LICENSE: GPL-2.0-or-later (
https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Classes for displaying progress dialog.
+ * COPYRIGHT: Copyright 2021 He Yang <1160386205(a)qq.com>
+ */
+
+#pragma once
+
+#include <atlbase.h>
+#include <atlwin.h>
+
+#include "resource.h"
+#include "registry.h"
+
+// When wParam < item count ==> wParam is item index (0 based)
+// wParam = item count ==> all finished, lParam = bSuccess
+#define WM_SETINDEX (WM_USER + 1)
+
+class ProgressDlg : public CDialogImpl<ProgressDlg>
+{
+private:
+ INT_PTR m_DialogID;
+ HWND m_hListBox;
+ HFONT m_hBoldFont;
+ HBITMAP m_hArrowBmp;
+ BITMAP m_ArrowBmp;
+ INT m_PointedItem;
+
+public:
+ enum { IDD = IDD_DIALOG };
+
+ RunOnceExInstance &m_RunOnceExInst;
+
+ ProgressDlg(_In_ RunOnceExInstance &RunOnceExInst);
+
+ BOOL RunDialogBox();
+
+ void CalcTextRect(_In_ LPCWSTR lpText, _Inout_ RECT *pRect);
+
+ void ResizeListBoxAndDialog(_In_ int NewHeight);
+
+ BOOL ProcessWindowMessage(_In_ HWND hwnd, _In_ UINT message, _In_ WPARAM wParam,
+ _In_ LPARAM lParam, _Out_ LRESULT& lResult,
+ _In_ DWORD dwMsgMapID);
+};
diff --git a/dll/win32/iernonce/iernonce.c b/dll/win32/iernonce/iernonce.c
deleted file mode 100644
index c5314c3f7c7..00000000000
--- a/dll/win32/iernonce/iernonce.c
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * PROJECT: ReactOS system libraries
- * LICENSE: GPL - See COPYING in the top level directory
- * FILE: dll\win32\iernonce\iernonce.c
- * PURPOSE: ReactOS Extended RunOnce processing with UI
- * PROGRAMMERS: Copyright 2013-2016 Robert Naumann
- */
-
-
-#define WIN32_NO_STATUS
-#include <windef.h>
-#include <winbase.h>
-
-#define NDEBUG
-#include <debug.h>
-
-HINSTANCE hInstance;
-
-BOOL
-WINAPI
-DllMain(HINSTANCE hinstDLL,
- DWORD dwReason,
- LPVOID reserved)
-{
- switch (dwReason)
- {
- case DLL_PROCESS_ATTACH:
- break;
- case DLL_PROCESS_DETACH:
- hInstance = hinstDLL;
- break;
- }
-
- return TRUE;
-}
-
-VOID WINAPI RunOnceExProcess(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
-{
- DPRINT1("RunOnceExProcess() not implemented\n");
-}
diff --git a/dll/win32/iernonce/iernonce.cpp b/dll/win32/iernonce/iernonce.cpp
new file mode 100644
index 00000000000..2569c9e164c
--- /dev/null
+++ b/dll/win32/iernonce/iernonce.cpp
@@ -0,0 +1,61 @@
+/*
+ * PROJECT: ReactOS system libraries
+ * LICENSE: GPL-2.0-or-later (
https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: ReactOS Extended RunOnce processing with UI.
+ * COPYRIGHT: Copyright 2013-2016 Robert Naumann
+ * Copyright 2021 He Yang <1160386205(a)qq.com>
+ */
+
+#include "iernonce.h"
+
+RUNONCEEX_CALLBACK g_Callback = NULL;
+BOOL g_bSilence = FALSE;
+
+BOOL
+WINAPI
+DllMain(_In_ HINSTANCE hinstDLL,
+ _In_ DWORD dwReason,
+ _In_ LPVOID reserved)
+{
+ switch (dwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ DisableThreadLibraryCalls(hinstDLL);
+ break;
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+
+ return TRUE;
+}
+
+extern "C" VOID WINAPI
+RunOnceExProcess(_In_ HWND hwnd,
+ _In_ HINSTANCE hInst,
+ _In_ LPCSTR pszCmdLine,
+ _In_ int nCmdShow)
+{
+ // iernonce may use shell32 API.
+ HRESULT Result = CoInitialize(NULL);
+ if (Result != S_OK && Result != S_FALSE)
+ {
+ return;
+ }
+
+ HKEY RootKeys[] = { HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER };
+ for (UINT i = 0; i < _countof(RootKeys); ++i)
+ {
+ RunOnceExInstance Instance(RootKeys[i]);
+ Instance.Run(g_bSilence);
+ }
+
+ CoUninitialize();
+}
+
+extern "C" VOID WINAPI
+InitCallback(_In_ RUNONCEEX_CALLBACK Callback,
+ _In_ BOOL bSilence)
+{
+ g_Callback = Callback;
+ g_bSilence = bSilence;
+}
diff --git a/dll/win32/iernonce/iernonce.h b/dll/win32/iernonce/iernonce.h
new file mode 100644
index 00000000000..5c46ed85cc5
--- /dev/null
+++ b/dll/win32/iernonce/iernonce.h
@@ -0,0 +1,24 @@
+/*
+ * PROJECT: ReactOS system libraries
+ * LICENSE: GPL-2.0-or-later (
https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: ReactOS Extended RunOnce processing with UI.
+ * COPYRIGHT: Copyright 2021 He Yang <1160386205(a)qq.com>
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstdlib>
+
+#define WIN32_NO_STATUS
+#include <windef.h>
+#include <winbase.h>
+#include <windowsx.h>
+#include <shlwapi.h>
+#include <iernonce_undoc.h>
+
+#include <atlbase.h>
+#include <atlwin.h>
+
+#include "registry.h"
+#include "dialog.h"
diff --git a/dll/win32/iernonce/iernonce.spec b/dll/win32/iernonce/iernonce.spec
index 808989a74af..23898e470b1 100644
--- a/dll/win32/iernonce/iernonce.spec
+++ b/dll/win32/iernonce/iernonce.spec
@@ -1 +1,2 @@
-@ stdcall RunOnceExProcess(ptr ptr str long)
\ No newline at end of file
+@ stdcall RunOnceExProcess(ptr ptr str long)
+@ stdcall InitCallback(ptr long)
diff --git a/dll/win32/iernonce/include/registry.h
b/dll/win32/iernonce/include/registry.h
new file mode 100644
index 00000000000..39cdeeb77f7
--- /dev/null
+++ b/dll/win32/iernonce/include/registry.h
@@ -0,0 +1,107 @@
+/*
+ * PROJECT: ReactOS system libraries
+ * LICENSE: GPL-2.0-or-later (
https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Functions to read RunOnceEx registry.
+ * COPYRIGHT: Copyright 2021 He Yang <1160386205(a)qq.com>
+ */
+
+#pragma once
+
+#include <windows.h>
+#include <atlbase.h>
+#include <atlstr.h>
+#include <atlcoll.h>
+#include <atlsimpcoll.h>
+
+#define FLAGS_NO_STAT_DIALOG 0x00000080
+
+#ifndef UNICODE
+#error This project must be compiled with UNICODE!
+#endif
+
+class CRegKeyEx : public CRegKey
+{
+public:
+ LONG EnumValueName(
+ _In_ DWORD iIndex,
+ _Out_ LPTSTR pszName,
+ _Inout_ LPDWORD pnNameLength);
+};
+
+class RunOnceExEntry
+{
+private:
+ ATL::CStringW m_Value;
+ ATL::CStringW m_Name;
+
+public:
+
+ RunOnceExEntry(
+ _In_ const ATL::CStringW &Name,
+ _In_ const ATL::CStringW &Value);
+
+ BOOL Delete(_In_ CRegKeyEx &hParentKey);
+ BOOL Exec() const;
+
+ friend int RunOnceExEntryCmp(
+ _In_ const void *a,
+ _In_ const void *b);
+};
+
+class RunOnceExSection
+{
+private:
+ ATL::CStringW m_SectionName;
+ CRegKeyEx m_RegKey;
+
+ BOOL HandleValue(
+ _In_ CRegKeyEx &hKey,
+ _In_ const CStringW &ValueName);
+
+public:
+ BOOL m_bSuccess;
+ ATL::CStringW m_SectionTitle;
+ CSimpleArray<RunOnceExEntry> m_EntryList;
+
+ RunOnceExSection(
+ _In_ CRegKeyEx &hParentKey,
+ _In_ const CStringW &lpSubKeyName);
+
+ RunOnceExSection(_In_ const RunOnceExSection &Section);
+
+ BOOL CloseAndDelete(_In_ CRegKeyEx &hParentKey);
+
+ UINT GetEntryCnt() const;
+
+ BOOL Exec(
+ _Inout_ UINT& iCompleteCnt,
+ _In_ const UINT iTotalCnt);
+
+ friend int RunOnceExSectionCmp(
+ _In_ const void *a,
+ _In_ const void *b);
+
+ friend class RunOnceExInstance;
+};
+
+class RunOnceExInstance
+{
+private:
+ CRegKeyEx m_RegKey;
+
+ BOOL HandleSubKey(
+ _In_ CRegKeyEx &hKey,
+ _In_ const CStringW &SubKeyName);
+
+public:
+ BOOL m_bSuccess;
+ CSimpleArray<RunOnceExSection> m_SectionList;
+ CStringW m_Title;
+ DWORD m_dwFlags;
+ BOOL m_bShowDialog;
+
+ RunOnceExInstance(_In_ HKEY BaseKey);
+
+ BOOL Exec(_In_opt_ HWND hwnd);
+ BOOL Run(_In_ BOOL bSilence);
+};
diff --git a/dll/win32/iernonce/registry.cpp b/dll/win32/iernonce/registry.cpp
new file mode 100644
index 00000000000..a5c764b6c58
--- /dev/null
+++ b/dll/win32/iernonce/registry.cpp
@@ -0,0 +1,360 @@
+/*
+ * PROJECT: ReactOS system libraries
+ * LICENSE: GPL-2.0-or-later (
https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Functions to read RunOnceEx registry.
+ * COPYRIGHT: Copyright 2021 He Yang <1160386205(a)qq.com>
+ */
+
+#include "iernonce.h"
+
+extern RUNONCEEX_CALLBACK g_Callback;
+
+LONG CRegKeyEx::EnumValueName(
+ _In_ DWORD iIndex,
+ _Out_ LPTSTR pszName,
+ _Inout_ LPDWORD pnNameLength)
+{
+ return RegEnumValueW(m_hKey, iIndex, pszName, pnNameLength,
+ NULL, NULL, NULL, NULL);
+}
+
+RunOnceExEntry::RunOnceExEntry(
+ _In_ const ATL::CStringW &Name,
+ _In_ const ATL::CStringW &Value) :
+ m_Value(Value), m_Name(Name)
+{ ; }
+
+BOOL RunOnceExEntry::Delete(
+ _In_ CRegKeyEx &hParentKey)
+{
+ return hParentKey.DeleteValue(m_Name) == ERROR_SUCCESS;
+}
+
+BOOL RunOnceExEntry::Exec() const
+{
+ CStringW CommandLine;
+ if (wcsncmp(m_Value, L"||", 2) == 0)
+ {
+ // Remove the prefix.
+ CommandLine = (LPCWSTR)m_Value + 2;
+ }
+ else
+ {
+ CommandLine = m_Value;
+ }
+
+ // FIXME: SHEvaluateSystemCommandTemplate is not implemented
+ // using PathGetArgsW, PathRemoveArgsW as a workaround.
+ LPWSTR szCommandLine = CommandLine.GetBuffer();
+ LPCWSTR szParam = PathGetArgsW(szCommandLine);
+ PathRemoveArgsW(szCommandLine);
+
+ SHELLEXECUTEINFOW Info = { 0 };
+ Info.cbSize = sizeof(Info);
+ Info.fMask = SEE_MASK_NOCLOSEPROCESS;
+ Info.lpFile = szCommandLine;
+ Info.lpParameters = szParam;
+ Info.nShow = SW_SHOWNORMAL;
+
+ BOOL bSuccess = ShellExecuteExW(&Info);
+
+ CommandLine.ReleaseBuffer();
+
+ if (!bSuccess)
+ {
+ return FALSE;
+ }
+
+ if (Info.hProcess)
+ {
+ WaitForSingleObject(Info.hProcess, INFINITE);
+ CloseHandle(Info.hProcess);
+ }
+
+ return TRUE;
+}
+
+int RunOnceExEntryCmp(
+ _In_ const void *a,
+ _In_ const void *b)
+{
+ return lstrcmpW(((RunOnceExEntry *)a)->m_Name,
+ ((RunOnceExEntry *)b)->m_Name);
+}
+
+BOOL RunOnceExSection::HandleValue(
+ _In_ CRegKeyEx &hKey,
+ _In_ const CStringW &ValueName)
+{
+ DWORD dwType;
+ DWORD cbData;
+
+ // Query data size
+ if (hKey.QueryValue(ValueName, &dwType, NULL, &cbData) != ERROR_SUCCESS)
+ return FALSE;
+
+ // Validate its format and size.
+ if (dwType != REG_SZ)
+ return TRUE;
+
+ if (cbData % sizeof(WCHAR) != 0)
+ return FALSE;
+
+ CStringW Buffer;
+ LPWSTR szBuffer = Buffer.GetBuffer((cbData / sizeof(WCHAR)) + 1);
+
+ if (hKey.QueryValue(ValueName, &dwType, szBuffer, &cbData) != ERROR_SUCCESS)
+ {
+ Buffer.ReleaseBuffer();
+ return FALSE;
+ }
+ szBuffer[cbData / sizeof(WCHAR)] = L'\0';
+ Buffer.ReleaseBuffer();
+
+ CStringW ExpandStr;
+ DWORD dwcchExpand = ExpandEnvironmentStringsW(Buffer, NULL, 0);
+ ExpandEnvironmentStringsW(Buffer, ExpandStr.GetBuffer(dwcchExpand + 1),
dwcchExpand);
+ ExpandStr.ReleaseBuffer();
+
+ if (ValueName.IsEmpty())
+ {
+ // The default value specifies the section title.
+ m_SectionTitle = Buffer;
+ }
+ else
+ {
+ m_EntryList.Add(RunOnceExEntry(ValueName, ExpandStr));
+ }
+
+ return TRUE;
+}
+
+RunOnceExSection::RunOnceExSection(
+ _In_ CRegKeyEx &hParentKey,
+ _In_ const CStringW &lpSubKeyName) :
+ m_SectionName(lpSubKeyName)
+{
+ m_bSuccess = FALSE;
+ DWORD dwValueNum;
+ DWORD dwMaxValueNameLen;
+ LSTATUS Error;
+ CStringW ValueName;
+
+ if (m_RegKey.Open(hParentKey, lpSubKeyName) != ERROR_SUCCESS)
+ return;
+
+ Error = RegQueryInfoKeyW(m_RegKey, NULL, 0, NULL, NULL, NULL, NULL,
+ &dwValueNum, &dwMaxValueNameLen,
+ NULL, NULL, NULL);
+ if (Error != ERROR_SUCCESS)
+ return;
+
+ for (DWORD i = 0; i < dwValueNum; i++)
+ {
+ LPWSTR szValueName;
+ DWORD dwcchName = dwMaxValueNameLen + 1;
+
+ szValueName = ValueName.GetBuffer(dwMaxValueNameLen + 1);
+ Error = m_RegKey.EnumValueName(i, szValueName, &dwcchName);
+ ValueName.ReleaseBuffer();
+
+ if (Error != ERROR_SUCCESS)
+ {
+ // TODO: error handling
+ return;
+ }
+
+ if (!HandleValue(m_RegKey, ValueName))
+ return;
+ }
+
+ // Sort entries by name in string order.
+ qsort(m_EntryList.GetData(), m_EntryList.GetSize(),
+ sizeof(RunOnceExEntry), RunOnceExEntryCmp);
+
+ m_bSuccess = TRUE;
+ return;
+}
+
+// Copy constructor, CSimpleArray needs it.
+RunOnceExSection::RunOnceExSection(_In_ const RunOnceExSection& Section) :
+ m_SectionName(Section.m_SectionName),
+ m_bSuccess(Section.m_bSuccess),
+ m_SectionTitle(Section.m_SectionTitle),
+ m_EntryList(Section.m_EntryList)
+{
+ m_RegKey.Attach(Section.m_RegKey);
+}
+
+BOOL RunOnceExSection::CloseAndDelete(
+ _In_ CRegKeyEx &hParentKey)
+{
+ m_RegKey.Close();
+ return hParentKey.RecurseDeleteKey(m_SectionName) == ERROR_SUCCESS;
+}
+
+UINT RunOnceExSection::GetEntryCnt() const
+{
+ return m_EntryList.GetSize();
+}
+
+BOOL RunOnceExSection::Exec(
+ _Inout_ UINT& iCompleteCnt,
+ _In_ const UINT iTotalCnt)
+{
+ BOOL bSuccess = TRUE;
+
+ for (int i = 0; i < m_EntryList.GetSize(); i++)
+ {
+ m_EntryList[i].Delete(m_RegKey);
+ bSuccess &= m_EntryList[i].Exec();
+ iCompleteCnt++;
+ // TODO: the meaning of the third param is still unknown, seems it's always
0.
+ if (g_Callback)
+ g_Callback(iCompleteCnt, iTotalCnt, NULL);
+ }
+ return bSuccess;
+}
+
+int RunOnceExSectionCmp(
+ _In_ const void *a,
+ _In_ const void *b)
+{
+ return lstrcmpW(((RunOnceExSection *)a)->m_SectionName,
+ ((RunOnceExSection *)b)->m_SectionName);
+}
+
+RunOnceExInstance::RunOnceExInstance(_In_ HKEY BaseKey)
+{
+ m_bSuccess = FALSE;
+ DWORD dwSubKeyNum;
+ DWORD dwMaxSubKeyNameLen;
+ LSTATUS Error;
+ CStringW SubKeyName;
+
+ Error = m_RegKey.Open(BaseKey,
+
L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx\\");
+ if (Error != ERROR_SUCCESS)
+ {
+ return;
+ }
+
+ ULONG cchTitle;
+ Error = m_RegKey.QueryStringValue(L"Title", NULL, &cchTitle);
+ if (Error == ERROR_SUCCESS)
+ {
+ Error = m_RegKey.QueryStringValue(L"Title", m_Title.GetBuffer(cchTitle
+ 1), &cchTitle);
+ m_Title.ReleaseBuffer();
+ if (Error != ERROR_SUCCESS)
+ return;
+ }
+
+ Error = m_RegKey.QueryDWORDValue(L"Flags", m_dwFlags);
+ if (Error != ERROR_SUCCESS)
+ {
+ m_dwFlags = 0;
+ }
+
+ Error = RegQueryInfoKeyW(m_RegKey, NULL, 0, NULL,
+ &dwSubKeyNum, &dwMaxSubKeyNameLen,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (Error != ERROR_SUCCESS)
+ return;
+
+ m_bShowDialog = FALSE;
+
+ for (DWORD i = 0; i < dwSubKeyNum; i++)
+ {
+ LPWSTR szSubKeyName;
+ DWORD dwcchName = dwMaxSubKeyNameLen + 1;
+
+ szSubKeyName = SubKeyName.GetBuffer(dwMaxSubKeyNameLen + 1);
+ Error = m_RegKey.EnumKey(i, szSubKeyName, &dwcchName);
+ SubKeyName.ReleaseBuffer();
+
+ if (Error != ERROR_SUCCESS)
+ {
+ // TODO: error handling
+ return;
+ }
+
+ if (!HandleSubKey(m_RegKey, SubKeyName))
+ return;
+ }
+
+ // Sort sections by name in string order.
+ qsort(m_SectionList.GetData(), m_SectionList.GetSize(),
+ sizeof(RunOnceExSection), RunOnceExSectionCmp);
+
+ m_bSuccess = TRUE;
+ return;
+}
+
+BOOL RunOnceExInstance::Exec(_In_opt_ HWND hwnd)
+{
+ BOOL bSuccess = TRUE;
+
+ UINT TotalCnt = 0;
+ UINT CompleteCnt = 0;
+ for (int i = 0; i < m_SectionList.GetSize(); i++)
+ {
+ TotalCnt += m_SectionList[i].GetEntryCnt();
+ }
+
+ // Execute items from registry one by one, and remove them.
+ for (int i = 0; i < m_SectionList.GetSize(); i++)
+ {
+ if (hwnd)
+ SendMessageW(hwnd, WM_SETINDEX, i, 0);
+
+ bSuccess &= m_SectionList[i].Exec(CompleteCnt, TotalCnt);
+ m_SectionList[i].CloseAndDelete(m_RegKey);
+ }
+
+ m_RegKey.DeleteValue(L"Title");
+ m_RegKey.DeleteValue(L"Flags");
+
+ // Notify the dialog all sections are handled.
+ if (hwnd)
+ SendMessageW(hwnd, WM_SETINDEX, m_SectionList.GetSize(), bSuccess);
+ return bSuccess;
+}
+
+BOOL RunOnceExInstance::Run(_In_ BOOL bSilence)
+{
+ if (bSilence ||
+ (m_dwFlags & FLAGS_NO_STAT_DIALOG) ||
+ !m_bShowDialog)
+ {
+ return Exec(NULL);
+ }
+ else
+ {
+ // The dialog is responsible to create a thread and execute.
+ ProgressDlg dlg(*this);
+ return dlg.RunDialogBox();
+ }
+}
+
+BOOL RunOnceExInstance::HandleSubKey(
+ _In_ CRegKeyEx &hKey,
+ _In_ const CStringW& SubKeyName)
+{
+ RunOnceExSection Section(hKey, SubKeyName);
+ if (!Section.m_bSuccess)
+ {
+ return FALSE;
+ }
+
+ if (!Section.m_SectionTitle.IsEmpty())
+ {
+ m_bShowDialog = TRUE;
+ }
+ m_SectionList.Add(Section);
+
+ // The copy constructor of RunOnceExSection didn't detach
+ // the m_RegKey while it's attached to the one in the array.
+ // So we have to detach it manually.
+ Section.m_RegKey.Detach();
+ return TRUE;
+}
diff --git a/sdk/include/reactos/iernonce_undoc.h b/sdk/include/reactos/iernonce_undoc.h
new file mode 100644
index 00000000000..530f10f0dd0
--- /dev/null
+++ b/sdk/include/reactos/iernonce_undoc.h
@@ -0,0 +1,30 @@
+#ifndef _IERNONCE_UNDOC_H_
+#define _IERNONCE_UNDOC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef VOID
+(CALLBACK *RUNONCEEX_CALLBACK)(
+ _In_ UINT CompleteCnt,
+ _In_ UINT TotalCnt,
+ _In_ DWORD_PTR dwReserved);
+
+VOID WINAPI
+InitCallback(
+ _In_ RUNONCEEX_CALLBACK Callback,
+ _In_ BOOL bSilence);
+
+VOID WINAPI
+RunOnceExProcess(
+ _In_ HWND hwnd,
+ _In_ HINSTANCE hInst,
+ _In_ LPCSTR pszCmdLine,
+ _In_ int nCmdShow);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IERNONCE_UNDOC_H_ */