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

commit d8f9f7f2566614d8c9800edeffafc086836a3cef
Author:     Eric Kohl <[email protected]>
AuthorDate: Sat Jul 15 12:27:09 2023 +0200
Commit:     Eric Kohl <[email protected]>
CommitDate: Sat Jul 15 12:27:09 2023 +0200

    [NETSH] Replace the wine stub by a slightly more functional version
    
    - Implement a basic command interpreter.
    - Add basic support for helper dlls and contexts.
    - Add interactive help system with context support.
    
    Everything is still under construction and subject to change.
---
 base/applications/network/netsh/CMakeLists.txt |  27 +-
 base/applications/network/netsh/context.c      | 374 ++++++++++++++++
 base/applications/network/netsh/help.c         | 152 +++++++
 base/applications/network/netsh/helper.c       | 593 +++++++++++++++++++++++++
 base/applications/network/netsh/interpreter.c  | 238 ++++++++++
 base/applications/network/netsh/lang/en-US.rc  |  36 ++
 base/applications/network/netsh/netsh.c        | 267 ++++++++++-
 base/applications/network/netsh/netsh.rc       |  15 +
 base/applications/network/netsh/netsh.spec     |   7 +
 base/applications/network/netsh/precomp.h      | 205 +++++++++
 base/applications/network/netsh/resource.h     |  37 ++
 11 files changed, 1923 insertions(+), 28 deletions(-)

diff --git a/base/applications/network/netsh/CMakeLists.txt 
b/base/applications/network/netsh/CMakeLists.txt
index 5e4005282da..0c838e60c7c 100644
--- a/base/applications/network/netsh/CMakeLists.txt
+++ b/base/applications/network/netsh/CMakeLists.txt
@@ -1,8 +1,25 @@
 
-add_definitions(-D__WINESRC__)
-include_directories(${REACTOS_SOURCE_DIR}/sdk/include/wine)
-add_executable(netsh netsh.c)
-target_link_libraries(netsh wine)
+include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/conutils)
+spec2def(netsh.exe netsh.spec ADD_IMPORTLIB)
+
+list(APPEND SOURCE
+    context.c
+    help.c
+    helper.c
+    interpreter.c
+    netsh.c
+    precomp.h)
+
+add_executable(netsh ${SOURCE} netsh.rc ${CMAKE_CURRENT_BINARY_DIR}/netsh.def)
+
+set_target_properties(netsh
+    PROPERTIES
+    ENABLE_EXPORTS TRUE
+    DEFINE_SYMBOL "")
+
 set_module_type(netsh win32cui UNICODE)
-add_importlibs(netsh msvcrt kernel32 ntdll)
+target_link_libraries(netsh conutils ${PSEH_LIB})
+add_importlibs(netsh advapi32 msvcrt user32 kernel32 ntdll)
+
+add_pch(netsh precomp.h SOURCE)
 add_cd_file(TARGET netsh DESTINATION reactos/system32 FOR all)
diff --git a/base/applications/network/netsh/context.c 
b/base/applications/network/netsh/context.c
new file mode 100644
index 00000000000..dded888c879
--- /dev/null
+++ b/base/applications/network/netsh/context.c
@@ -0,0 +1,374 @@
+/*
+ * PROJECT:    ReactOS NetSh
+ * LICENSE:    GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:    Network Shell context management functions
+ * COPYRIGHT:  Copyright 2023 Eric Kohl <[email protected]>
+ */
+
+/* INCLUDES 
*******************************************************************/
+
+#include "precomp.h"
+
+#define NDEBUG
+#include <debug.h>
+
+/* GLOBALS 
********************************************************************/
+
+PCONTEXT_ENTRY pRootContext = NULL;
+PCONTEXT_ENTRY pCurrentContext = NULL;
+
+/* FUNCTIONS 
******************************************************************/
+
+PCONTEXT_ENTRY
+AddContext(
+    PCONTEXT_ENTRY pParentContext,
+    PWSTR pszName,
+    GUID *pGuid)
+{
+    PCONTEXT_ENTRY pEntry;
+
+    if (pParentContext != NULL && pszName == NULL)
+        return NULL;
+
+    /* Allocate the entry */
+    pEntry = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
sizeof(CONTEXT_ENTRY));
+    if (pEntry == NULL)
+        return NULL;
+
+    /* Allocate the name buffer */
+    if (pszName != NULL)
+    {
+        pEntry->pszContextName = HeapAlloc(GetProcessHeap(),
+                                           HEAP_ZERO_MEMORY,
+                                           (wcslen(pszName) + 1) * 
sizeof(WCHAR));
+        if (pEntry->pszContextName == NULL)
+        {
+            HeapFree(GetProcessHeap(), 0, pEntry);
+            return NULL;
+        }
+
+        /* Fill the entry */
+        wcscpy(pEntry->pszContextName, pszName);
+    }
+
+    pEntry->pParentContext = pParentContext;
+    if (pGuid != NULL)
+        CopyMemory(&pEntry->Guid, pGuid, sizeof(pEntry->Guid));
+
+    /* Insert it */
+    if (pParentContext != NULL)
+    {
+        if (pParentContext->pSubContextHead == NULL && 
pParentContext->pSubContextTail == NULL)
+        {
+            pParentContext->pSubContextHead = pEntry;
+            pParentContext->pSubContextTail = pEntry;
+        }
+        else
+        {
+            pEntry->pPrev = pParentContext->pSubContextTail;
+            pParentContext->pSubContextTail->pNext = pEntry;
+            pParentContext->pSubContextTail = pEntry;
+        }
+    }
+
+    return pEntry;
+}
+
+
+PCOMMAND_ENTRY
+AddContextCommand(
+    PCONTEXT_ENTRY pContext,
+    LPCWSTR pwszCmdToken,
+    PFN_HANDLE_CMD pfnCmdHandler,
+    DWORD dwShortCmdHelpToken,
+    DWORD dwCmdHlpToken,
+    DWORD dwFlags)
+{
+    PCOMMAND_ENTRY pEntry;
+
+    if (pfnCmdHandler == NULL)
+        return NULL;
+
+    /* Allocate the entry */
+    pEntry = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
sizeof(COMMAND_ENTRY));
+    if (pEntry == NULL)
+        return NULL;
+
+    pEntry->pwszCmdToken = HeapAlloc(GetProcessHeap(),
+                                     HEAP_ZERO_MEMORY,
+                                     (wcslen(pwszCmdToken) + 1) * 
sizeof(WCHAR));
+    if (pEntry->pwszCmdToken == NULL)
+    {
+        HeapFree(GetProcessHeap(), 0, pEntry);
+        return NULL;
+    }
+
+    wcscpy((LPWSTR)pEntry->pwszCmdToken, pwszCmdToken);
+
+    pEntry->pfnCmdHandler = pfnCmdHandler;
+    pEntry->dwShortCmdHelpToken = dwShortCmdHelpToken;
+    pEntry->dwCmdHlpToken = dwCmdHlpToken;
+    pEntry->dwFlags = dwFlags;
+
+    if (pContext->pCommandListHead == NULL && pContext->pCommandListTail == 
NULL)
+    {
+        pContext->pCommandListHead = pEntry;
+        pContext->pCommandListTail = pEntry;
+    }
+    else
+    {
+        pEntry->pPrev = pContext->pCommandListTail;
+        pContext->pCommandListTail->pNext = pEntry;
+        pContext->pCommandListTail = pEntry;
+    }
+
+    return pEntry;
+}
+
+
+PCOMMAND_GROUP
+AddCommandGroup(
+    PCONTEXT_ENTRY pContext,
+    LPCWSTR pwszCmdGroupToken,
+    DWORD dwShortCmdHelpToken,
+    DWORD dwFlags)
+{
+    PCOMMAND_GROUP pEntry;
+
+    /* Allocate the entry */
+    pEntry = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
sizeof(COMMAND_GROUP));
+    if (pEntry == NULL)
+        return NULL;
+
+    pEntry->pwszCmdGroupToken = HeapAlloc(GetProcessHeap(),
+                                          HEAP_ZERO_MEMORY,
+                                          (wcslen(pwszCmdGroupToken) + 1) * 
sizeof(WCHAR));
+    if (pEntry->pwszCmdGroupToken == NULL)
+    {
+        HeapFree(GetProcessHeap(), 0, pEntry);
+        return NULL;
+    }
+
+    wcscpy((LPWSTR)pEntry->pwszCmdGroupToken, pwszCmdGroupToken);
+    pEntry->dwShortCmdHelpToken = dwShortCmdHelpToken;
+    pEntry->dwFlags = dwFlags;
+
+    if (pContext->pGroupListHead == NULL && pContext->pGroupListTail == NULL)
+    {
+        pContext->pGroupListHead = pEntry;
+        pContext->pGroupListTail = pEntry;
+    }
+    else
+    {
+        pEntry->pPrev = pContext->pGroupListTail;
+        pContext->pGroupListTail->pNext = pEntry;
+        pContext->pGroupListTail = pEntry;
+    }
+
+    return pEntry;
+}
+
+
+PCOMMAND_ENTRY
+AddGroupCommand(
+    PCOMMAND_GROUP pGroup,
+    LPCWSTR pwszCmdToken,
+    PFN_HANDLE_CMD pfnCmdHandler,
+    DWORD dwShortCmdHelpToken,
+    DWORD dwCmdHlpToken,
+    DWORD dwFlags)
+{
+    PCOMMAND_ENTRY pEntry;
+
+    if (pfnCmdHandler == NULL)
+        return NULL;
+
+    /* Allocate the entry */
+    pEntry = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
sizeof(COMMAND_ENTRY));
+    if (pEntry == NULL)
+        return NULL;
+
+    pEntry->pwszCmdToken = HeapAlloc(GetProcessHeap(),
+                                     HEAP_ZERO_MEMORY,
+                                     (wcslen(pwszCmdToken) + 1) * 
sizeof(WCHAR));
+    if (pEntry->pwszCmdToken == NULL)
+    {
+        HeapFree(GetProcessHeap(), 0, pEntry);
+        return NULL;
+    }
+
+    wcscpy((LPWSTR)pEntry->pwszCmdToken, pwszCmdToken);
+
+    pEntry->pfnCmdHandler = pfnCmdHandler;
+    pEntry->dwShortCmdHelpToken = dwShortCmdHelpToken;
+    pEntry->dwCmdHlpToken = dwCmdHlpToken;
+    pEntry->dwFlags = dwFlags;
+
+    if (pGroup->pCommandListHead == NULL && pGroup->pCommandListTail == NULL)
+    {
+        pGroup->pCommandListHead = pEntry;
+        pGroup->pCommandListTail = pEntry;
+    }
+    else
+    {
+        pEntry->pPrev = pGroup->pCommandListTail;
+        pGroup->pCommandListTail->pNext = pEntry;
+        pGroup->pCommandListTail = pEntry;
+    }
+
+    return pEntry;
+}
+
+
+VOID
+DeleteContext(
+    PWSTR pszName)
+{
+    /* Delete all commands */
+    /* Delete the context */
+}
+
+
+DWORD
+WINAPI
+UpCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *argv,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone)
+{
+    if (pCurrentContext != pRootContext)
+        pCurrentContext = pCurrentContext->pParentContext;
+
+    return 0;
+}
+
+
+DWORD
+WINAPI
+ExitCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *argv,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone)
+{
+    *pbDone = TRUE;
+    return 0;
+}
+
+
+DWORD
+WINAPI
+RemCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *argv,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone)
+{
+    return 0;
+}
+
+
+BOOL
+CreateRootContext(VOID)
+{
+    PCOMMAND_GROUP pGroup;
+
+    pRootContext = AddContext(NULL, NULL, NULL);
+    DPRINT1("pRootContext: %p\n", pRootContext);
+    if (pRootContext == NULL)
+        return FALSE;
+
+    pRootContext->hModule = GetModuleHandle(NULL);
+
+    AddContextCommand(pRootContext, L"..",   UpCommand, IDS_HLP_UP, 
IDS_HLP_UP_EX, 0);
+    AddContextCommand(pRootContext, L"?",    HelpCommand, IDS_HLP_HELP, 
IDS_HLP_HELP_EX, 0);
+    AddContextCommand(pRootContext, L"bye",  ExitCommand, IDS_HLP_EXIT, 
IDS_HLP_EXIT_EX, 0);
+    AddContextCommand(pRootContext, L"exit", ExitCommand, IDS_HLP_EXIT, 
IDS_HLP_EXIT_EX, 0);
+    AddContextCommand(pRootContext, L"help", HelpCommand, IDS_HLP_HELP, 
IDS_HLP_HELP_EX, 0);
+    AddContextCommand(pRootContext, L"quit", ExitCommand, IDS_HLP_EXIT, 
IDS_HLP_EXIT_EX, 0);
+
+    pGroup = AddCommandGroup(pRootContext, L"add", IDS_HLP_GROUP_ADD, 0);
+    if (pGroup)
+    {
+        AddGroupCommand(pGroup, L"helper", AddHelperCommand, 
IDS_HLP_ADD_HELPER, IDS_HLP_ADD_HELPER_EX, 0);
+    }
+
+    pGroup = AddCommandGroup(pRootContext, L"delete", IDS_HLP_GROUP_DELETE, 0);
+    if (pGroup)
+    {
+        AddGroupCommand(pGroup, L"helper", DeleteHelperCommand, 
IDS_HLP_DEL_HELPER, IDS_HLP_DEL_HELPER_EX, 0);
+    }
+
+    pGroup = AddCommandGroup(pRootContext, L"show", IDS_HLP_GROUP_SHOW, 0);
+    if (pGroup)
+    {
+        AddGroupCommand(pGroup, L"helper", ShowHelperCommand, 
IDS_HLP_SHOW_HELPER, IDS_HLP_SHOW_HELPER_EX, 0);
+    }
+
+    pCurrentContext = pRootContext;
+
+    return TRUE;
+}
+
+
+DWORD
+WINAPI
+RegisterContext(
+    _In_ const NS_CONTEXT_ATTRIBUTES *pChildContext)
+{
+    PCONTEXT_ENTRY pContext;
+    DWORD i;
+
+    DPRINT1("RegisterContext(%p)\n", pChildContext);
+    if (pChildContext == NULL)
+    {
+        DPRINT1("Invalid child context!\n");
+        return ERROR_INVALID_PARAMETER;
+    }
+
+    if ((pChildContext->pwszContext == NULL) ||
+        (wcslen(pChildContext->pwszContext) == 0) ||
+        (wcschr(pChildContext->pwszContext, L' ') != 0) ||
+        (wcschr(pChildContext->pwszContext, L'=') != 0))
+    {
+        DPRINT1("Invalid context name!\n");
+        return ERROR_INVALID_PARAMETER;
+    }
+
+    DPRINT1("Name: %S\n", pChildContext->pwszContext);
+
+    pContext = AddContext(pRootContext, pChildContext->pwszContext, 
(GUID*)&pChildContext->guidHelper);
+    if (pContext != NULL)
+    {
+        for (i = 0; i < pChildContext->ulNumTopCmds; i++)
+        {
+            AddContextCommand(pContext,
+                pChildContext->pTopCmds[i].pwszCmdToken,
+                pChildContext->pTopCmds[i].pfnCmdHandler,
+                pChildContext->pTopCmds[i].dwShortCmdHelpToken,
+                pChildContext->pTopCmds[i].dwCmdHlpToken,
+                pChildContext->pTopCmds[i].dwFlags);
+        }
+
+        /* Add command groups */
+        for (i = 0; i < pChildContext->ulNumGroups; i++)
+        {
+            AddCommandGroup(pContext,
+                pChildContext->pCmdGroups[i].pwszCmdGroupToken,
+                pChildContext->pCmdGroups[i].dwShortCmdHelpToken,
+                pChildContext->pCmdGroups[i].dwFlags);
+        }
+    }
+
+    return ERROR_SUCCESS;
+}
diff --git a/base/applications/network/netsh/help.c 
b/base/applications/network/netsh/help.c
new file mode 100644
index 00000000000..a446e1ec10c
--- /dev/null
+++ b/base/applications/network/netsh/help.c
@@ -0,0 +1,152 @@
+/*
+ * PROJECT:    ReactOS NetSh
+ * LICENSE:    GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:    Network Shell builtin help command and support functions
+ * COPYRIGHT:  Copyright 2023 Eric Kohl <[email protected]>
+ */
+
+/* INCLUDES 
*******************************************************************/
+
+#include "precomp.h"
+
+#define NDEBUG
+#include <debug.h>
+
+/* FUNCTIONS 
******************************************************************/
+
+static
+VOID
+GetContextFullName(
+    _In_ PCONTEXT_ENTRY pContext,
+    _Inout_ LPWSTR pszBuffer,
+    _In_ DWORD cchLength)
+{
+    if (pContext->pParentContext != NULL)
+    {
+        GetContextFullName(pContext->pParentContext, pszBuffer, cchLength);
+        wcscat(pszBuffer, L" ");
+        wcscat(pszBuffer, pContext->pszContextName);
+    }
+    else
+    {
+        wcscpy(pszBuffer, L"netsh");
+    }
+}
+
+
+static
+VOID
+HelpContext(
+    PCONTEXT_ENTRY pContext)
+{
+    PCONTEXT_ENTRY pSubContext;
+    PCOMMAND_ENTRY pCommand;
+    PCOMMAND_GROUP pGroup;
+    WCHAR szBuffer[80];
+
+    if (pContext != pRootContext)
+        HelpContext(pContext->pParentContext);
+
+    if (pContext == pCurrentContext)
+    {
+        ConPrintf(StdOut, L"\nCommands in this context:\n");
+    }
+    else if (pContext == pRootContext)
+    {
+        ConPrintf(StdOut, L"\nCommands in the netsh-context:\n");
+    }
+    else
+    {
+        GetContextFullName(pContext, szBuffer, 80);
+        ConPrintf(StdOut, L"\nCommands in the %s-context:\n", szBuffer);
+    }
+
+    pCommand = pContext->pCommandListHead;
+    while (pCommand != NULL)
+    {
+        if (LoadStringW(pContext->hModule, pCommand->dwShortCmdHelpToken, 
szBuffer, 80) == 0)
+            szBuffer[0] = UNICODE_NULL;
+        ConPrintf(StdOut, L"%-15s - %s\n", pCommand->pwszCmdToken, szBuffer);
+        pCommand = pCommand->pNext;
+    }
+
+    pGroup = pContext->pGroupListHead;
+    while (pGroup != NULL)
+    {
+        if (LoadStringW(pContext->hModule, pGroup->dwShortCmdHelpToken, 
szBuffer, 80) == 0)
+            szBuffer[0] = UNICODE_NULL;
+        ConPrintf(StdOut, L"%-15s - %s\n", pGroup->pwszCmdGroupToken, 
szBuffer);
+        pGroup = pGroup->pNext;
+    }
+
+    pSubContext = pContext->pSubContextHead;
+    while (pSubContext != NULL)
+    {
+        GetContextFullName(pSubContext, szBuffer, 80);
+        ConPrintf(StdOut, L"%-15s - Changes to the \"%s\" context.\n", 
pSubContext->pszContextName, szBuffer);
+        pSubContext = pSubContext->pNext;
+    }
+}
+
+
+VOID
+HelpGroup(
+    PCOMMAND_GROUP pGroup)
+{
+    PCOMMAND_ENTRY pCommand;
+    WCHAR szBuffer[64];
+
+    ConResPrintf(StdOut, IDS_HELP_HEADER);
+
+    ConPrintf(StdOut, L"\nCommands in this context:\n");
+
+    pCommand = pGroup->pCommandListHead;
+    while (pCommand != NULL)
+    {
+        swprintf(szBuffer, L"%s %s", pGroup->pwszCmdGroupToken, 
pCommand->pwszCmdToken);
+        ConPrintf(StdOut, L"%-15s - ", szBuffer);
+        ConResPuts(StdOut, pCommand->dwShortCmdHelpToken);
+        pCommand = pCommand->pNext;
+    }
+}
+
+
+DWORD
+WINAPI
+HelpCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *ppwcArguments,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone)
+{
+    PCONTEXT_ENTRY pContext;
+
+    ConResPrintf(StdOut, IDS_HELP_HEADER);
+
+    pContext = pCurrentContext;
+    if (pContext == NULL)
+    {
+        DPRINT1("HelpCommand: invalid context %p\n", pContext);
+        return 1;
+    }
+
+    HelpContext(pContext);
+
+    if (pCurrentContext->pSubContextHead != NULL)
+    {
+        ConResPrintf(StdOut, IDS_SUBCONTEXT_HEADER);
+        pContext = pCurrentContext->pSubContextHead;
+        while (pContext != NULL)
+        {
+            ConPrintf(StdOut, L" %s", pContext->pszContextName);
+            pContext = pContext->pNext;
+        }
+        ConPuts(StdOut, L"\n");
+    }
+    ConPuts(StdOut, L"\n");
+
+    return ERROR_SUCCESS;
+}
diff --git a/base/applications/network/netsh/helper.c 
b/base/applications/network/netsh/helper.c
new file mode 100644
index 00000000000..2054a647019
--- /dev/null
+++ b/base/applications/network/netsh/helper.c
@@ -0,0 +1,593 @@
+/*
+ * PROJECT:    ReactOS NetSh
+ * LICENSE:    GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:    Network Shell helper dll management and support functions
+ * COPYRIGHT:  Copyright 2023 Eric Kohl <[email protected]>
+ */
+
+/* INCLUDES 
*******************************************************************/
+
+#include "precomp.h"
+
+#define NDEBUG
+#include <debug.h>
+
+/* GLOBALS 
********************************************************************/
+
+PDLL_LIST_ENTRY pDllListHead = NULL;
+PDLL_LIST_ENTRY pDllListTail = NULL;
+
+PHELPER_ENTRY pHelperListHead = NULL;
+PHELPER_ENTRY pHelperListTail = NULL;
+
+PDLL_LIST_ENTRY pCurrentDll = NULL;
+
+/* FUNCTIONS 
******************************************************************/
+
+static
+VOID
+StartHelpers(VOID)
+{
+    PHELPER_ENTRY pHelper;
+    DWORD dwError;
+
+    pHelper = pHelperListHead;
+    while (pHelper != NULL)
+    {
+        if (pHelper->bStarted == FALSE)
+        {
+            if (pHelper->Attributes.pfnStart)
+            {
+                dwError = pHelper->Attributes.pfnStart(NULL, 0);
+                if (dwError == ERROR_SUCCESS)
+                    pHelper->bStarted = TRUE;
+            }
+        }
+
+        pHelper = pHelper->pNext;
+    }
+}
+
+
+static
+VOID
+RegisterHelperDll(
+    _In_ PDLL_LIST_ENTRY pEntry)
+{
+    PWSTR pszValueName = NULL;
+    HKEY hKey;
+    DWORD dwError;
+
+    dwError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
+                              REG_NETSH_PATH,
+                              0,
+                              NULL,
+                              REG_OPTION_NON_VOLATILE,
+                              KEY_WRITE,
+                              NULL,
+                              &hKey,
+                              NULL);
+    if (dwError == ERROR_SUCCESS)
+    {
+        RegSetValueExW(hKey,
+                       pEntry->pszValueName,
+                       0,
+                       REG_SZ,
+                       (PBYTE)pEntry->pszDllName,
+                       (wcslen(pEntry->pszDllName) + 1) * sizeof(WCHAR));
+
+        RegCloseKey(hKey);
+    }
+
+    HeapFree(GetProcessHeap(), 0, pszValueName);
+}
+
+
+static
+VOID
+FreeHelperDll(
+    _In_ PDLL_LIST_ENTRY pEntry)
+{
+    if (pEntry->hModule)
+        FreeLibrary(pEntry->hModule);
+
+    if (pEntry->pszValueName)
+        HeapFree(GetProcessHeap(), 0, pEntry->pszValueName);
+
+    if (pEntry->pszShortName)
+        HeapFree(GetProcessHeap(), 0, pEntry->pszShortName);
+
+    if (pEntry->pszDllName)
+        HeapFree(GetProcessHeap(), 0, pEntry->pszDllName);
+
+    HeapFree(GetProcessHeap(), 0, pEntry);
+}
+
+
+static
+DWORD
+LoadHelperDll(
+    _In_ PWSTR pszDllName,
+    _In_ BOOL bRegister)
+{
+    PNS_DLL_INIT_FN pInitHelperDll;
+    PDLL_LIST_ENTRY pEntry;
+    PWSTR pszStart, pszEnd;
+    BOOL bInserted = FALSE;
+    DWORD dwError;
+
+    pEntry = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
sizeof(DLL_LIST_ENTRY));
+    if (pEntry == NULL)
+    {
+        return ERROR_OUTOFMEMORY;
+    }
+
+    pEntry->pszDllName = HeapAlloc(GetProcessHeap(),
+                                   HEAP_ZERO_MEMORY,
+                                   (wcslen(pszDllName) + 1) * sizeof(WCHAR));
+    if (pEntry->pszDllName == NULL)
+    {
+        dwError = ERROR_OUTOFMEMORY;
+        goto done;
+    }
+
+    wcscpy(pEntry->pszDllName, pszDllName);
+
+    pszStart = wcsrchr(pszDllName, L'\\');
+    if (pszStart == NULL)
+        pszStart = pszDllName;
+
+    pEntry->pszShortName = HeapAlloc(GetProcessHeap(),
+                                     HEAP_ZERO_MEMORY,
+                                     (wcslen(pszStart) + 1) * sizeof(WCHAR));
+    if (pEntry->pszShortName == NULL)
+    {
+        dwError = ERROR_OUTOFMEMORY;
+        goto done;
+    }
+
+    wcscpy(pEntry->pszShortName, pszStart);
+
+    pEntry->pszValueName = HeapAlloc(GetProcessHeap(),
+                                     HEAP_ZERO_MEMORY,
+                                     (wcslen(pEntry->pszShortName) + 1) * 
sizeof(WCHAR));
+    if (pEntry->pszValueName == NULL)
+    {
+        dwError = ERROR_OUTOFMEMORY;
+        goto done;
+    }
+
+    wcscpy(pEntry->pszValueName, pEntry->pszShortName);
+
+    pszEnd = wcsrchr(pEntry->pszValueName, L'.');
+    if (pszEnd != NULL)
+        *pszEnd = UNICODE_NULL;
+
+    if (pDllListTail == NULL)
+    {
+        pEntry->pPrev = NULL;
+        pEntry->pNext = NULL;
+        pDllListHead = pEntry;
+        pDllListTail = pEntry;
+    }
+    else
+    {
+        pEntry->pPrev = NULL;
+        pEntry->pNext = pDllListHead;
+        pDllListHead->pPrev = pEntry;
+        pDllListHead = pEntry;
+    }
+
+    bInserted = TRUE;
+
+    pEntry->hModule = LoadLibraryW(pEntry->pszDllName);
+    if (pEntry->hModule == NULL)
+    {
+        dwError = GetLastError();
+        DPRINT1("Could not load the helper dll %S (Error: %lu)\n", 
pEntry->pszDllName, dwError);
+        goto done;
+    }
+
+    pInitHelperDll = (PNS_DLL_INIT_FN)GetProcAddress(pEntry->hModule, 
"InitHelperDll");
+    if (pInitHelperDll == NULL)
+    {
+        dwError = GetLastError();
+        DPRINT1("Could not find 'InitHelperDll' (Error: %lu)\n", dwError);
+        goto done;
+    }
+
+    pCurrentDll = pEntry;
+    dwError = pInitHelperDll(5, NULL);
+    pCurrentDll = NULL;
+
+    DPRINT1("InitHelperDll returned %lu\n", dwError);
+    if (dwError != ERROR_SUCCESS)
+    {
+        DPRINT1("Call to InitHelperDll failed (Error: %lu)\n", dwError);
+        goto done;
+    }
+
+//    if (pEntry->Attributes.pfnStart)
+//        pEntry->Attributes.pfnStart(NULL, 0);
+
+    if (bRegister)
+        RegisterHelperDll(pEntry);
+
+done:
+    if (dwError != ERROR_SUCCESS)
+    {
+        if (bInserted)
+        {
+            if (pEntry->pPrev != NULL)
+                pEntry->pPrev->pNext = pEntry->pNext;
+            if (pEntry->pNext != NULL)
+                pEntry->pNext->pPrev = pEntry->pPrev;
+            if (pDllListTail == pEntry)
+                pDllListTail = pEntry->pPrev;
+            if (pDllListHead == pEntry)
+                pDllListHead = pEntry->pNext;
+            pEntry->pPrev = NULL;
+            pEntry->pNext = NULL;
+        }
+
+        FreeHelperDll(pEntry);
+    }
+
+    return dwError;
+}
+
+
+VOID
+LoadHelpers(VOID)
+{
+    PWSTR pszNameBuffer = NULL;
+    PWSTR pszValueBuffer = NULL;
+    HKEY hKey;
+    DWORD dwValueCount, dwMaxNameLength, dwMaxValueLength;
+    DWORD dwNameLength, dwValueLength, dwType;
+    DWORD dwIndex, dwError;
+
+    DPRINT1("LoadHelpers()\n");
+
+    dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+                            REG_NETSH_PATH,
+                            0,
+                            KEY_READ,
+                            &hKey);
+    if (dwError != ERROR_SUCCESS)
+        return;
+
+    dwError = RegQueryInfoKeyW(hKey,
+                               NULL,
+                               NULL,
+                               NULL,
+                               NULL,
+                               NULL,
+                               NULL,
+                               &dwValueCount,
+                               &dwMaxNameLength,
+                               &dwMaxValueLength,
+                               NULL,
+                               NULL);
+    if (dwError != ERROR_SUCCESS)
+        goto done;
+
+    pszNameBuffer = HeapAlloc(GetProcessHeap(), 0,
+                              (dwMaxNameLength + 1) * sizeof(WCHAR));
+    if (pszNameBuffer == NULL)
+        goto done;
+
+    pszValueBuffer = HeapAlloc(GetProcessHeap(), 0,
+                               dwMaxValueLength + sizeof(WCHAR));
+    if (pszValueBuffer == NULL)
+        goto done;
+
+    for (dwIndex = 0; dwIndex < dwValueCount; dwIndex++)
+    {
+        dwNameLength = dwMaxNameLength + 1;
+        dwValueLength = dwMaxValueLength + sizeof(WCHAR);
+        dwError = RegEnumValueW(hKey,
+                                dwIndex,
+                                pszNameBuffer,
+                                &dwNameLength,
+                                NULL,
+                                &dwType,
+                                (PBYTE)pszValueBuffer,
+                                &dwValueLength);
+        if (dwError != ERROR_SUCCESS)
+            break;
+
+        DPRINT1("Dll: %S --> %S  %lu\n", pszNameBuffer, pszValueBuffer, 
dwError);
+        LoadHelperDll(pszValueBuffer, FALSE);
+    }
+
+done:
+    if (pszValueBuffer)
+        HeapFree(GetProcessHeap(), 0, pszValueBuffer);
+
+    if (pszNameBuffer)
+        HeapFree(GetProcessHeap(), 0, pszNameBuffer);
+
+    RegCloseKey(hKey);
+
+    StartHelpers();
+}
+
+
+VOID
+UnloadHelpers(VOID)
+{
+    PDLL_LIST_ENTRY pEntry;
+
+    while (pDllListHead != NULL)
+    {
+        pEntry = pDllListHead;
+        pDllListHead = pEntry->pNext;
+
+//        if (pEntry->Attributes.pfnStop)
+//            pEntry->Attributes.pfnStop(0);
+
+        FreeHelperDll(pEntry);
+    }
+
+    pDllListTail = NULL;
+}
+
+
+PHELPER_ENTRY
+FindHelper(
+    _In_ const GUID *pguidHelper)
+{
+    PHELPER_ENTRY pHelper;
+
+    pHelper = pHelperListHead;
+    while (pHelper != NULL)
+    {
+        if (IsEqualGUID(pguidHelper, &pHelper->Attributes.guidHelper))
+            return pHelper;
+
+        pHelper = pHelper->pNext;
+    }
+
+    return NULL;
+}
+
+
+DWORD
+WINAPI
+RegisterHelper(
+    _In_ const GUID *pguidParentHelper,
+    _In_ const NS_HELPER_ATTRIBUTES *pHelperAttributes)
+{
+    PHELPER_ENTRY pHelper = NULL, pParentHelper;
+    DWORD dwError = ERROR_SUCCESS;
+
+    DPRINT("RegisterHelper(%p %p)\n", pguidParentHelper, pHelperAttributes);
+
+    if (FindHelper(&pHelperAttributes->guidHelper) != NULL)
+    {
+        DPRINT1("The Helper has already been registered!\n");
+        return 1;
+    }
+
+    pHelper = (PHELPER_ENTRY)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
sizeof(HELPER_ENTRY));
+    if (pHelper == NULL)
+    {
+        dwError = ERROR_OUTOFMEMORY;
+        goto done;
+    }
+
+    CopyMemory(&pHelper->Attributes, pHelperAttributes, 
sizeof(NS_HELPER_ATTRIBUTES));
+    pHelper->pDllEntry = pCurrentDll;
+    DPRINT("pHelper->pDllEntry: %p\n", pHelper->pDllEntry);
+
+    if (pguidParentHelper == NULL)
+    {
+        if (pHelperListTail == NULL)
+        {
+            pHelperListHead = pHelper;
+            pHelperListTail = pHelper;
+        }
+        else
+        {
+            pHelper->pNext = pHelperListHead;
+            pHelperListHead->pPrev = pHelper;
+            pHelperListHead = pHelper;
+        }
+    }
+    else
+    {
+        pParentHelper = FindHelper(&pHelperAttributes->guidHelper);
+        if (pParentHelper == NULL)
+            return ERROR_INVALID_PARAMETER;
+
+        if (pParentHelper->pSubHelperHead == NULL && 
pParentHelper->pSubHelperTail == NULL)
+        {
+            pParentHelper->pSubHelperHead = pHelper;
+            pParentHelper->pSubHelperTail = pHelper;
+        }
+        else
+        {
+            pHelper->pPrev = pParentHelper->pSubHelperTail;
+            pParentHelper->pSubHelperTail->pNext = pHelper;
+            pParentHelper->pSubHelperTail = pHelper;
+        }
+    }
+
+done:
+
+    return dwError;
+}
+
+
+DWORD
+WINAPI
+AddHelperCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *ppwcArguments,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone)
+{
+    DWORD dwError = ERROR_SUCCESS;
+
+    DPRINT("AddHelperCommand()\n");
+
+    if (dwArgCount == 2)
+    {
+//        ConResPrintf(StdErr, IDS_INVALID_SYNTAX);
+//        ConResPrintf(StdErr, IDS_HLP_ADD_HELPER_EX);
+        return 1;
+    }
+
+    dwError = LoadHelperDll(ppwcArguments[2], TRUE);
+    if (dwError != ERROR_SUCCESS)
+        return dwError;
+
+    StartHelpers();
+
+    return ERROR_SUCCESS;
+}
+
+
+DWORD
+WINAPI
+DeleteHelperCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *ppwcArguments,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone)
+{
+    PDLL_LIST_ENTRY pEntry;
+    HKEY hKey;
+    DWORD dwError;
+
+    DPRINT("DeleteHelper()\n");
+
+    if (dwArgCount == 2)
+    {
+//        ConResPrintf(StdErr, IDS_INVALID_SYNTAX);
+//        ConResPrintf(StdErr, IDS_HLP_DEL_HELPER_EX);
+        return 1;
+    }
+
+    pEntry = pDllListHead;
+    while (pEntry != NULL)
+    {
+        if (wcscmp(pEntry->pszShortName, ppwcArguments[2]) == 0)
+        {
+            DPRINT1("remove %S\n", pEntry->pszShortName);
+
+            if (pEntry->pPrev != NULL)
+                pEntry->pPrev->pNext = pEntry->pNext;
+            if (pEntry->pNext != NULL)
+                pEntry->pNext->pPrev = pEntry->pPrev;
+            if (pDllListTail == pEntry)
+                pDllListTail = pEntry->pPrev;
+            if (pDllListHead == pEntry)
+                pDllListHead = pEntry->pNext;
+            pEntry->pPrev = NULL;
+            pEntry->pNext = NULL;
+
+            dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+                                    REG_NETSH_PATH,
+                                    0,
+                                    KEY_WRITE,
+                                    &hKey);
+            if (dwError == ERROR_SUCCESS)
+            {
+                RegDeleteValue(hKey, pEntry->pszValueName);
+                RegCloseKey(hKey);
+            }
+
+            FreeHelperDll(pEntry);
+
+            return 1;
+        }
+
+        pEntry = pEntry->pNext;
+    }
+
+    return ERROR_SUCCESS;
+}
+
+
+static
+VOID
+PrintSubContext(
+    _In_ PCONTEXT_ENTRY pParentContext,
+    _In_ DWORD dwLevel)
+{
+    PCONTEXT_ENTRY pContext;
+    PHELPER_ENTRY pHelper;
+    WCHAR szPrefix[22];
+    DWORD i;
+
+    if (pParentContext == NULL)
+        return;
+
+    pContext = pParentContext->pSubContextHead;
+    while (pContext != NULL)
+    {
+        pHelper = FindHelper(&pContext->Guid);
+        if (pHelper != NULL)
+        {
+            if (dwLevel > 10)
+                dwLevel = 10;
+
+            for (i = 0; i < dwLevel * 2; i++)
+                szPrefix[i] = L' ';
+            szPrefix[i] = UNICODE_NULL;
+
+            ConPrintf(StdOut, 
L"{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}  %-16s  %s%s\n",
+                pHelper->Attributes.guidHelper.Data1,
+                pHelper->Attributes.guidHelper.Data2,
+                pHelper->Attributes.guidHelper.Data3,
+                pHelper->Attributes.guidHelper.Data4[0],
+                pHelper->Attributes.guidHelper.Data4[1],
+                pHelper->Attributes.guidHelper.Data4[2],
+                pHelper->Attributes.guidHelper.Data4[3],
+                pHelper->Attributes.guidHelper.Data4[4],
+                pHelper->Attributes.guidHelper.Data4[5],
+                pHelper->Attributes.guidHelper.Data4[6],
+                pHelper->Attributes.guidHelper.Data4[7],
+                pHelper->pDllEntry->pszShortName,
+                szPrefix,
+                pContext->pszContextName);
+        }
+
+        PrintSubContext(pContext, dwLevel + 1);
+
+        pContext = pContext->pNext;
+    }
+}
+
+
+DWORD
+WINAPI
+ShowHelperCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *ppwcArguments,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone)
+{
+    DPRINT("ShowHelperCommand()\n");
+
+    ConPrintf(StdOut, L"Helper GUID                             DLL Name       
   Command\n");
+    ConPrintf(StdOut, L"--------------------------------------  
----------------  --------\n");
+
+    if (pRootContext == NULL)
+        return ERROR_SUCCESS;
+
+    PrintSubContext(pRootContext, 0);
+
+    return ERROR_SUCCESS;
+}
diff --git a/base/applications/network/netsh/interpreter.c 
b/base/applications/network/netsh/interpreter.c
new file mode 100644
index 00000000000..87c5c2fc2fe
--- /dev/null
+++ b/base/applications/network/netsh/interpreter.c
@@ -0,0 +1,238 @@
+/*
+ * PROJECT:    ReactOS NetSh
+ * LICENSE:    GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:    Network Shell command interpreter
+ * COPYRIGHT:  Copyright 2023 Eric Kohl <[email protected]>
+ */
+
+/* INCLUDES 
*******************************************************************/
+
+#include "precomp.h"
+
+#define NDEBUG
+#include <debug.h>
+
+/* FUNCTIONS *****************************************************************/
+
+BOOL
+InterpretCommand(
+    _In_ LPWSTR *argv,
+    _In_ DWORD dwArgCount)
+{
+    PCONTEXT_ENTRY pContext, pSubContext;
+    PCOMMAND_ENTRY pCommand;
+    PCOMMAND_GROUP pGroup;
+    BOOL bDone = FALSE;
+    DWORD dwHelpLevel = 0;
+    DWORD dwError = ERROR_SUCCESS;
+
+    /* If no args provided */
+    if (dwArgCount == 0)
+        return TRUE;
+
+    if (pCurrentContext == NULL)
+    {
+        DPRINT1("InterpretCmd: invalid context %p\n", pCurrentContext);
+        return FALSE;
+    }
+
+    if ((wcsicmp(argv[dwArgCount - 1], L"?") == 0) ||
+        (wcsicmp(argv[dwArgCount - 1], L"help") == 0))
+    {
+        dwHelpLevel = dwArgCount - 1;
+    }
+
+    pContext = pCurrentContext;
+
+    while (TRUE)
+    {
+        pCommand = pContext->pCommandListHead;
+        while (pCommand != NULL)
+        {
+            if (wcsicmp(argv[0], pCommand->pwszCmdToken) == 0) 
+            {
+                if (dwHelpLevel == 1)
+                {
+                    ConResPrintf(StdOut, pCommand->dwCmdHlpToken);
+                    return TRUE;
+                }
+                else
+                {
+                    dwError = pCommand->pfnCmdHandler(NULL, argv, 0, 
dwArgCount, 0, NULL, &bDone);
+                    if (dwError != ERROR_SUCCESS)
+                    {
+                        ConPrintf(StdOut, L"Error: %lu\n\n");
+                        ConResPrintf(StdOut, pCommand->dwCmdHlpToken);
+                    }
+                    return !bDone;
+                }
+            }
+
+            pCommand = pCommand->pNext;
+        }
+
+        pGroup = pContext->pGroupListHead;
+        while (pGroup != NULL)
+        {
+            if (wcsicmp(argv[0], pGroup->pwszCmdGroupToken) == 0)
+            {
+                if (dwHelpLevel == 1)
+                {
+                    HelpGroup(pGroup);
+                    return TRUE;
+                }
+
+                pCommand = pGroup->pCommandListHead;
+                while (pCommand != NULL)
+                {
+                    if ((dwArgCount > 1) && (wcsicmp(argv[1], 
pCommand->pwszCmdToken) == 0))
+                    {
+                        if (dwHelpLevel == 2)
+                        {
+                            ConResPrintf(StdOut, pCommand->dwCmdHlpToken);
+                            return TRUE;
+                        }
+                        else
+                        {
+                            dwError = pCommand->pfnCmdHandler(NULL, argv, 1, 
dwArgCount, 0, NULL, &bDone);
+                            if (dwError != ERROR_SUCCESS)
+                            {
+                                ConPrintf(StdOut, L"Error: %lu\n\n");
+                                ConResPrintf(StdOut, pCommand->dwCmdHlpToken);
+                                return TRUE;
+                            }
+                            return !bDone;
+                        }
+                    }
+
+                    pCommand = pCommand->pNext;
+                }
+
+                HelpGroup(pGroup);
+                return TRUE;
+            }
+
+            pGroup = pGroup->pNext;
+        }
+
+        if (pContext == pCurrentContext)
+        {
+            pSubContext = pContext->pSubContextHead;
+            while (pSubContext != NULL)
+            {
+                if (wcsicmp(argv[0], pSubContext->pszContextName) == 0) 
+                {
+                    pCurrentContext = pSubContext;
+                    return TRUE;
+                }
+
+                pSubContext = pSubContext->pNext;
+            }
+        }
+
+        if (pContext == pRootContext)
+            break;
+
+        pContext = pContext->pParentContext;
+    }
+
+    ConResPrintf(StdErr, IDS_INVALID_COMMAND, argv[0]);
+
+    return TRUE;
+}
+
+
+/*
+ * InterpretScript(char *line):
+ * The main function used for when reading commands from scripts.
+ */
+BOOL
+InterpretScript(
+    _In_ LPWSTR pszInputLine)
+{
+    LPWSTR args_vector[MAX_ARGS_COUNT];
+    DWORD dwArgCount = 0;
+    BOOL bWhiteSpace = TRUE;
+    LPWSTR ptr;
+
+    memset(args_vector, 0, sizeof(args_vector));
+
+    ptr = pszInputLine;
+    while (*ptr != 0)
+    {
+        if (iswspace(*ptr) || *ptr == L'\n')
+        {
+            *ptr = 0;
+            bWhiteSpace = TRUE;
+        }
+        else
+        {
+            if ((bWhiteSpace != FALSE) && (dwArgCount < MAX_ARGS_COUNT))
+            {
+                args_vector[dwArgCount] = ptr;
+                dwArgCount++;
+            }
+
+            bWhiteSpace = FALSE;
+        }
+
+        ptr++;
+    }
+
+    /* sends the string to find the command */
+    return InterpretCommand(args_vector, dwArgCount);
+}
+
+
+VOID
+InterpretInteractive(VOID)
+{
+    WCHAR input_line[MAX_STRING_SIZE];
+    LPWSTR args_vector[MAX_ARGS_COUNT];
+    DWORD dwArgCount = 0;
+    BOOL bWhiteSpace = TRUE;
+    BOOL bRun = TRUE;
+    LPWSTR ptr;
+
+    while (bRun != FALSE)
+    {
+        dwArgCount = 0;
+        memset(args_vector, 0, sizeof(args_vector));
+
+        /* Shown just before the input where the user places commands */
+//        ConResPuts(StdOut, IDS_APP_PROMPT);
+        ConPuts(StdOut, L"netsh");
+        if (pCurrentContext != pRootContext)
+        {
+            ConPuts(StdOut, L" ");
+            ConPuts(StdOut, pCurrentContext->pszContextName);
+        }
+        ConPuts(StdOut, L">");
+
+        /* Get input from the user. */
+        fgetws(input_line, MAX_STRING_SIZE, stdin);
+
+        ptr = input_line;
+        while (*ptr != 0)
+        {
+            if (iswspace(*ptr) || *ptr == L'\n')
+            {
+                *ptr = 0;
+                bWhiteSpace = TRUE;
+            }
+            else
+            {
+                if ((bWhiteSpace != FALSE) && (dwArgCount < MAX_ARGS_COUNT))
+                {
+                    args_vector[dwArgCount] = ptr;
+                    dwArgCount++;
+                }
+                bWhiteSpace = FALSE;
+            }
+            ptr++;
+        }
+
+        /* Send the string to find the command */
+        bRun = InterpretCommand(args_vector, dwArgCount);
+    }
+}
diff --git a/base/applications/network/netsh/lang/en-US.rc 
b/base/applications/network/netsh/lang/en-US.rc
new file mode 100644
index 00000000000..1fd6654e247
--- /dev/null
+++ b/base/applications/network/netsh/lang/en-US.rc
@@ -0,0 +1,36 @@
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+/* Basic application information */
+STRINGTABLE
+BEGIN
+    IDS_APP_USAGE "\nUsage: netsh [-a AliasFile] [-c Context] [-r 
RemoteMachine] \
+\n             [Command | -f ScriptFile]\n"
+    IDS_INVALID_COMMAND "The following command was not found: %ls.\n"
+    IDS_OPEN_FAILED     "The file %ls could not be openend.\n"
+    IDS_INVALID_SYNTAX  "The syntax supplied for this command is not valid. 
Check help for the correct syntax.\n\n"
+END
+
+STRINGTABLE
+BEGIN
+    IDS_HELP_HEADER "\nThe following commands are available:\n"
+    IDS_SUBCONTEXT_HEADER "\nThe following sub-contexts are available:\n"
+    IDS_HLP_UP             "Goes up one context level."
+    IDS_HLP_UP_EX          "Syntax: ..\n\n        Goes up one context 
level.\n\n"
+    IDS_HLP_EXIT           "Exits the program."
+    IDS_HLP_EXIT_EX        "Syntax: exit\n\n        Exits the program.\n\n"
+    IDS_HLP_HELP           "Displays a list of commands."
+    IDS_HLP_HELP_EX        "Syntax: help\n\n        Displays a list of 
commands.\n\n"
+
+    IDS_HLP_ADD_HELPER     "Installs a helper DLL."
+    IDS_HLP_ADD_HELPER_EX  "Syntax: add helper <dll file name>\n\n        
Installs the specified helper DLL in netsh.\n\n"
+
+    IDS_HLP_DEL_HELPER     "Removes a helper DLL."
+    IDS_HLP_DEL_HELPER_EX  "Syntax: delete helper <dll file name>\n\n        
Removes the specified helper DLL from netsh.\n\n"
+
+    IDS_HLP_SHOW_HELPER    "Lists all the top-level helpers."
+    IDS_HLP_SHOW_HELPER_EX "Syntax: show helper\n\n        Lists all the 
top-level helpers.\n\n"
+
+    IDS_HLP_GROUP_ADD      "Adds a configuration entry to a list of entries."
+    IDS_HLP_GROUP_DELETE   "Deletes a configuration entry from a list of 
entries."
+    IDS_HLP_GROUP_SHOW     "Displays information."
+END
diff --git a/base/applications/network/netsh/netsh.c 
b/base/applications/network/netsh/netsh.c
index bf938698d3c..7c0a33b669b 100644
--- a/base/applications/network/netsh/netsh.c
+++ b/base/applications/network/netsh/netsh.c
@@ -1,33 +1,254 @@
 /*
- * Copyright 2010 Hans Leidekker for CodeWeavers
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ * PROJECT:    ReactOS NetSh
+ * LICENSE:    GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:    Network Shell main file
+ * COPYRIGHT:  Copyright 2023 Eric Kohl <[email protected]>
  */
 
-#include <wine/debug.h>
+/* INCLUDES 
*******************************************************************/
 
-WINE_DEFAULT_DEBUG_CHANNEL(netsh);
+#include "precomp.h"
 
-int wmain(int argc, WCHAR *argv[])
+#define NDEBUG
+#include <debug.h>
+
+/* FUNCTIONS 
******************************************************************/
+
+BOOL
+RunScript(
+    _In_ LPCWSTR filename)
+{
+    FILE *script;
+    WCHAR tmp_string[MAX_STRING_SIZE];
+
+    /* Open the file for processing */
+    script = _wfopen(filename, L"r");
+    if (script == NULL)
+    {
+        ConResPrintf(StdErr, IDS_OPEN_FAILED, filename);
+        return FALSE;
+    }
+
+    /* Read and process the script */
+    while (fgetws(tmp_string, MAX_STRING_SIZE, script) != NULL)
+    {
+        if (InterpretScript(tmp_string) == FALSE)
+        {
+            fclose(script);
+            return FALSE;
+        }
+    }
+
+    /* Close the file */
+    fclose(script);
+
+    return TRUE;
+}
+
+/*
+ * wmain():
+ * Main entry point of the application.
+ */
+int
+wmain(
+    _In_ int argc,
+    _In_ const LPWSTR argv[])
 {
-    int i;
+    LPCWSTR tmpBuffer = NULL;
+    LPCWSTR pszFileName = NULL;
+    int index;
+    int result = EXIT_SUCCESS;
+
+    DPRINT("main()\n");
+
+    /* Initialize the Console Standard Streams */
+    ConInitStdStreams();
+
+    /* FIXME: Init code goes here */
+    CreateRootContext();
+    LoadHelpers();
+
+    if (argc < 2)
+    {
+        /* If there are no command arguments, then go straight to the 
interpreter */
+        InterpretInteractive();
+    }
+    else
+    {
+        /* If there are command arguments, then process them */
+        for (index = 1; index < argc; index++)
+        {
+            if ((argv[index][0] == '/')||
+                (argv[index][0] == '-'))
+            {
+                tmpBuffer = argv[index] + 1;
+            }
+            else
+            {
+                if (pszFileName != NULL)
+                {
+                    ConResPuts(StdOut, IDS_APP_USAGE);
+                    result = EXIT_FAILURE;
+                    goto done;
+                }
+
+                /* Run a command from the command line */
+                if (InterpretCommand((LPWSTR*)&argv[index], argc - index) == 
FALSE)
+                    result = EXIT_FAILURE;
+                goto done;
+            }
+
+            if (_wcsicmp(tmpBuffer, L"?") == 0)
+            {
+                /* Help option */
+                ConResPuts(StdOut, IDS_APP_USAGE);
+                result = EXIT_SUCCESS;
+                goto done;
+            }
+            else if (_wcsicmp(tmpBuffer, L"a") == 0)
+            {
+                /* Aliasfile option */
+                if ((index + 1) < argc)
+                {
+                    index++;
+                    ConPuts(StdOut, L"\nThe -a option is not implemented 
yet\n");
+//                    aliasfile = argv[index];
+                }
+                else
+                {
+                    ConResPuts(StdOut, IDS_APP_USAGE);
+                    result = EXIT_FAILURE;
+                }
+            }
+            else if (_wcsicmp(tmpBuffer, L"c") == 0)
+            {
+                /* Context option */
+                if ((index + 1) < argc)
+                {
+                    index++;
+                    ConPuts(StdOut, L"\nThe -c option is not implemented 
yet\n");
+//                    context = argv[index];
+                }
+                else
+                {
+                    ConResPuts(StdOut, IDS_APP_USAGE);
+                    result = EXIT_FAILURE;
+                }
+            }
+            else if (_wcsicmp(tmpBuffer, L"f") == 0)
+            {
+                /* File option */
+                if ((index + 1) < argc)
+                {
+                    index++;
+                    pszFileName = argv[index];
+                }
+                else
+                {
+                    ConResPuts(StdOut, IDS_APP_USAGE);
+                    result = EXIT_FAILURE;
+                }
+            }
+            else if (_wcsicmp(tmpBuffer, L"r") == 0)
+            {
+                /* Remote option */
+                if ((index + 1) < argc)
+                {
+                    index++;
+                    ConPuts(StdOut, L"\nThe -r option is not implemented 
yet\n");
+//                    remote = argv[index];
+                }
+                else
+                {
+                    ConResPuts(StdOut, IDS_APP_USAGE);
+                    result = EXIT_FAILURE;
+                }
+            }
+            else
+            {
+                /* Invalid command */
+                ConResPrintf(StdOut, IDS_INVALID_COMMAND, argv[index]);
+                result = EXIT_FAILURE;
+                goto done;
+            }
+        }
+
+        /* Now we process the filename if it exists */
+        if (pszFileName != NULL)
+        {
+            if (RunScript(pszFileName) == FALSE)
+            {
+                result = EXIT_FAILURE;
+                goto done;
+            }
+        }
+    }
 
-    WINE_FIXME("stub:");
-    for (i = 0; i < argc; i++)
-        WINE_FIXME(" %s", wine_dbgstr_w(argv[i]));
-    WINE_FIXME("\n");
+done:
+    /* FIXME: Cleanup code goes here */
+    UnloadHelpers();
 
+    return result;
+}
+
+
+DWORD
+WINAPI
+MatchEnumTag(
+    _In_ HANDLE hModule,
+    _In_ LPCWSTR pwcArg,
+    _In_ DWORD dwNumArg,
+    _In_ const TOKEN_VALUE *pEnumTable,
+    _Out_ PDWORD pdwValue)
+{
+    DPRINT1("MatchEnumTag()\n");
     return 0;
 }
+
+BOOL
+WINAPI
+MatchToken(
+    _In_ LPCWSTR pwszUserToken,
+    _In_ LPCWSTR pwszCmdToken)
+{
+    DPRINT1("MatchToken %S %S\n", pwszUserToken, pwszCmdToken);
+    return (wcsicmp(pwszUserToken, pwszCmdToken) == 0) ? TRUE : FALSE;
+}
+
+DWORD
+CDECL
+PrintError(
+    _In_opt_ HANDLE hModule,
+    _In_ DWORD dwErrId,
+    ...)
+{
+    DPRINT1("PrintError()\n");
+    return 1;
+}
+
+DWORD
+CDECL
+PrintMessageFromModule(
+    _In_ HANDLE hModule,
+    _In_ DWORD  dwMsgId,
+    ...)
+{
+    DPRINT1("PrintMessageFromModule()\n");
+    return 1;
+}
+
+DWORD
+CDECL
+PrintMessage(
+    _In_ LPCWSTR pwszFormat,
+    ...)
+{
+    INT Length;
+    va_list ap;
+
+    va_start(ap, pwszFormat);
+    Length = ConPrintf(StdOut, pwszFormat);
+    va_end(ap);
+
+    return Length;
+}
diff --git a/base/applications/network/netsh/netsh.rc 
b/base/applications/network/netsh/netsh.rc
new file mode 100644
index 00000000000..448c3239e04
--- /dev/null
+++ b/base/applications/network/netsh/netsh.rc
@@ -0,0 +1,15 @@
+#include <windef.h>
+
+#include "resource.h"
+
+#define REACTOS_STR_FILE_DESCRIPTION   "Net Shell"
+#define REACTOS_STR_INTERNAL_NAME      "netsh"
+#define REACTOS_STR_ORIGINAL_FILENAME  "netsh.exe"
+#include <reactos/version.rc>
+
+/* UTF-8 */
+#pragma code_page(65001)
+
+#ifdef LANGUAGE_EN_US
+    #include "lang/en-US.rc"
+#endif
diff --git a/base/applications/network/netsh/netsh.spec 
b/base/applications/network/netsh/netsh.spec
new file mode 100644
index 00000000000..4d82c1e7f36
--- /dev/null
+++ b/base/applications/network/netsh/netsh.spec
@@ -0,0 +1,7 @@
+@ stdcall MatchEnumTag(ptr wstr long ptr ptr)
+@ stdcall MatchToken(wstr wstr)
+@ varargs PrintError(ptr long)
+@ varargs PrintMessage(wstr)
+@ varargs PrintMessageFromModule(ptr long)
+@ stdcall RegisterContext(ptr)
+@ stdcall RegisterHelper(ptr ptr)
diff --git a/base/applications/network/netsh/precomp.h 
b/base/applications/network/netsh/precomp.h
new file mode 100644
index 00000000000..031b03cc767
--- /dev/null
+++ b/base/applications/network/netsh/precomp.h
@@ -0,0 +1,205 @@
+/*
+ * PROJECT:    ReactOS NetSh
+ * LICENSE:    GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:    Network Shell main header file
+ * COPYRIGHT:  Copyright 2023 Eric Kohl <[email protected]>
+ */
+
+#ifndef PRECOMP_H
+#define PRECOMP_H
+
+/* INCLUDES ******************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#define WIN32_NO_STATUS
+#include <windef.h>
+#include <winbase.h>
+#include <winreg.h>
+#include <wincon.h>
+#include <winuser.h>
+
+#include <errno.h>
+
+#include <conutils.h>
+#include <netsh.h>
+
+#include "resource.h"
+
+
+/* DEFINES *******************************************************************/
+
+#define MAX_STRING_SIZE 1024
+#define MAX_ARGS_COUNT 256
+
+#define REG_NETSH_PATH L"Software\\Microsoft\\NetSh"
+
+
+/* TYPEDEFS ******************************************************************/
+
+typedef struct _DLL_LIST_ENTRY
+{
+    struct _DLL_LIST_ENTRY *pPrev;
+    struct _DLL_LIST_ENTRY *pNext;
+
+    PWSTR pszDllName;
+    PWSTR pszShortName;
+    PWSTR pszValueName;
+
+    HMODULE hModule;
+
+} DLL_LIST_ENTRY, *PDLL_LIST_ENTRY;
+
+typedef struct _HELPER_ENTRY
+{
+    struct _HELPER_ENTRY *pPrev;
+    struct _HELPER_ENTRY *pNext;
+
+    NS_HELPER_ATTRIBUTES Attributes;
+
+    PDLL_LIST_ENTRY pDllEntry;
+    BOOL bStarted;
+
+    struct _HELPER_ENTRY *pSubHelperHead;
+    struct _HELPER_ENTRY *pSubHelperTail;
+
+} HELPER_ENTRY, *PHELPER_ENTRY;
+
+
+
+typedef struct _COMMAND_ENTRY
+{
+    struct _COMMAND_ENTRY *pPrev;
+    struct _COMMAND_ENTRY *pNext;
+
+    LPCWSTR pwszCmdToken;
+    PFN_HANDLE_CMD pfnCmdHandler;
+    DWORD dwShortCmdHelpToken;
+    DWORD dwCmdHlpToken;
+    DWORD dwFlags;
+} COMMAND_ENTRY, *PCOMMAND_ENTRY;
+
+typedef struct _COMMAND_GROUP
+{
+    struct _COMMAND_GROUP *pPrev;
+    struct _COMMAND_GROUP *pNext;
+
+    LPCWSTR pwszCmdGroupToken;
+    DWORD dwShortCmdHelpToken;
+    DWORD dwFlags;
+
+    PCOMMAND_ENTRY pCommandListHead;
+    PCOMMAND_ENTRY pCommandListTail;
+} COMMAND_GROUP, *PCOMMAND_GROUP;
+
+typedef struct _CONTEXT_ENTRY
+{
+    struct _CONTEXT_ENTRY *pPrev;
+    struct _CONTEXT_ENTRY *pNext;
+
+    struct _CONTEXT_ENTRY *pParentContext;
+
+    PWSTR pszContextName;
+    GUID Guid;
+    HMODULE hModule;
+
+    PCOMMAND_ENTRY pCommandListHead;
+    PCOMMAND_ENTRY pCommandListTail;
+
+    PCOMMAND_GROUP pGroupListHead;
+    PCOMMAND_GROUP pGroupListTail;
+
+    struct _CONTEXT_ENTRY *pSubContextHead;
+    struct _CONTEXT_ENTRY *pSubContextTail;
+} CONTEXT_ENTRY, *PCONTEXT_ENTRY;
+
+
+/* GLOBAL VARIABLES 
***********************************************************/
+
+extern PCONTEXT_ENTRY pRootContext;
+extern PCONTEXT_ENTRY pCurrentContext;
+
+
+/* PROTOTYPES 
*****************************************************************/
+
+/* context.c */
+
+BOOL
+CreateRootContext(VOID);
+
+
+/* help.c */
+DWORD
+WINAPI
+HelpCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *ppwcArguments,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone);
+
+VOID
+HelpGroup(
+    PCOMMAND_GROUP pGroup);
+
+
+/* helper.c */
+VOID
+LoadHelpers(VOID);
+
+VOID
+UnloadHelpers(VOID);
+
+
+DWORD
+WINAPI
+AddHelperCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *ppwcArguments,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone);
+
+DWORD
+WINAPI
+DeleteHelperCommand(
+    LPCWSTR pwszMachine,
+    LPWSTR *ppwcArguments,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone);
+
+DWORD
+WINAPI
+ShowHelperCommand(
+    LPCWSTR pwszMachine,
+    PWSTR *ppwcArguments,
+    DWORD dwCurrentIndex,
+    DWORD dwArgCount,
+    DWORD dwFlags,
+    LPCVOID pvData,
+    BOOL *pbDone);
+
+
+/* interpreter.c */
+BOOL
+InterpretScript(
+    LPWSTR pszFileName);
+
+BOOL
+InterpretCommand(
+    LPWSTR *argv,
+    DWORD dwArgCount);
+
+VOID
+InterpretInteractive(VOID);
+
+#endif /* PRECOMP_H */
diff --git a/base/applications/network/netsh/resource.h 
b/base/applications/network/netsh/resource.h
new file mode 100644
index 00000000000..e3875fd655c
--- /dev/null
+++ b/base/applications/network/netsh/resource.h
@@ -0,0 +1,37 @@
+/*
+ * PROJECT:    ReactOS NetSh
+ * LICENSE:    GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:    Network Shell resource id header file
+ * COPYRIGHT:  Copyright 2023 Eric Kohl <[email protected]>
+ */
+
+#pragma once
+
+#define IDS_NONE -1
+
+#define IDS_APP_USAGE            100
+#define IDS_APP_PROMPT           101
+#define IDS_INVALID_COMMAND      102
+#define IDS_OPEN_FAILED          103
+#define IDS_INVALID_SYNTAX       104
+
+#define IDS_HELP_HEADER          200
+#define IDS_SUBCONTEXT_HEADER    201
+
+#define IDS_HLP_EXIT             300
+#define IDS_HLP_EXIT_EX          301
+#define IDS_HLP_HELP             302
+#define IDS_HLP_HELP_EX          303
+#define IDS_HLP_UP               304
+#define IDS_HLP_UP_EX            305
+
+#define IDS_HLP_ADD_HELPER       310
+#define IDS_HLP_ADD_HELPER_EX    311
+#define IDS_HLP_DEL_HELPER       312
+#define IDS_HLP_DEL_HELPER_EX    313
+#define IDS_HLP_SHOW_HELPER      314
+#define IDS_HLP_SHOW_HELPER_EX   315
+
+#define IDS_HLP_GROUP_ADD        320
+#define IDS_HLP_GROUP_DELETE     321
+#define IDS_HLP_GROUP_SHOW       322
\ No newline at end of file

Reply via email to