https://git.reactos.org/?p=reactos.git;a=commitdiff;h=4d0cc20681dff4b15e7b1c950a35952af67346d9

commit 4d0cc20681dff4b15e7b1c950a35952af67346d9
Author:     He Yang <[email protected]>
AuthorDate: Wed Sep 29 17:30:32 2021 +0800
Commit:     GitHub <[email protected]>
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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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_ */

Reply via email to