From 4d0cc20681dff4b15e7b1c950a35952af67346d9 Mon Sep 17 00:00:00 2001 From: He Yang <1160386205@qq.com> Date: Wed, 29 Sep 2021 17:30:32 +0800 Subject: [PATCH] [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(-) create mode 100644 dll/win32/iernonce/dialog.cpp create mode 100644 dll/win32/iernonce/dialog.h delete mode 100644 dll/win32/iernonce/iernonce.c create mode 100644 dll/win32/iernonce/iernonce.cpp create mode 100644 dll/win32/iernonce/iernonce.h create mode 100644 dll/win32/iernonce/include/registry.h create mode 100644 dll/win32/iernonce/registry.cpp create mode 100644 sdk/include/reactos/iernonce_undoc.h 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@qq.com> + */ + +#include "iernonce.h" +#include + +#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@qq.com> + */ + +#pragma once + +#include +#include + +#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 +{ +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 -#include - -#define NDEBUG -#include - -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@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@qq.com> + */ + +#pragma once + +#include +#include + +#define WIN32_NO_STATUS +#include +#include +#include +#include +#include + +#include +#include + +#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@qq.com> + */ + +#pragma once + +#include +#include +#include +#include +#include + +#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 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 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@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_ */