Michael Haephrati Ответов: 3

Запуск процесса службы windows на windows server 2012


Начиная с Windows 7, безопасность служб Windows была усилена. Службы теперь работают изолированно в сеансе 0, поэтому не может быть никакого взаимодействия между пользователем (обычно сеансы 1, 2, 3 и т. д.) со службами Windows. Видеть: эта статья[^]
Я прочитал старую статью, которая все же касается этого нового изменения этот[^] и я пытаюсь выяснить, почему мой, когда я создаю процесс как пользователь, из службы Windows, я не вижу пользовательского интерфейса, когда я запускаю его на Windows Server 2012, в то время как он отлично работает на Windows 10.

В статье говорится::
“In Windows Server 2012, when bringing online the same Cluster Resource, the UI application does not become visible. This happens, because the Cluster Service is also a Windows Service, and the Cluster Resources launched by the Cluster Service are run in Session 0, which does not have user interaction. The workaround is to create a Windows Service that launches the UI application, and make this Windows Service a Cluster Resource. “


Следующая функция, которую я написал, должна быть вызвана службой Windows для запуска дочернего процесса (как пользователя), чтобы дочерний процесс мог взаимодействовать с пользователем, т. е. иметь пользовательский интерфейс. Он отлично работает на рабочем столе Windows, но не работает должным образом на Windows Server 2012. Пользовательский интерфейс не отображается (даже не окно консоли), и горячая клавиша, которую мы регистрируем для удаления службы, тоже не работает.


Может ли кто-нибудь подсказать мне, как сделать этот обходной путь?

Что я уже пробовал:

Видеть мой код здесь[^].

Я следовал руководящим принципам эта статья.[^] Вызовите мой процесс как пользователя из службы Windows, так как Службы Windows изолированы в сессия 0 Таким образом, поскольку службы не могут иметь ни собственного пользовательского интерфейса, ни каких-либо взаимодействий с пользователем, этот дочерний процесс, который является программой Win32, выполняет такое взаимодействие и вызывается службой с помощью:
bRes = CreateProcessAsUserW(hToken, NULL, &commandLine[0], NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_DEFAULT_ERROR_MODE, pEnv, NULL, &startupInfo, &processInfo);
Следующий код:
void WINAPI Run(DWORD dwTargetSessionId, int desktop, LPTSTR lpszCmdLine)
{
    wprintf(_T("Run client start"));

    if (hPrevAppProcess != NULL)
    {
        TerminateProcess(hPrevAppProcess, 0);
        WaitForSingleObject(hPrevAppProcess, INFINITE);
    }

    HANDLE hToken = 0;
    WTS_SESSION_INFO *si;
    DWORD cnt = 0;
    WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &si, &cnt);
    for (int i = 0; i < (int)cnt; i++)
    {
        if (si[i].SessionId == 0)continue;
        wprintf(_T("Trying session id %i (%s) user admin token"), si[i].SessionId, si[i].pWinStationName);
        HANDLE userToken;
        if (WTSQueryUserToken(si[i].SessionId, &userToken))
        {
            wprintf(_T("WTSQueryUserToken succeced"));
            TOKEN_LINKED_TOKEN admin;
            DWORD len;
            if (GetTokenInformation(userToken, TokenLinkedToken, &admin, sizeof(TOKEN_LINKED_TOKEN), &len))
            {
                wprintf(_T("Success using user admin token"));
                hToken = admin.LinkedToken;
                break;
            }
            else
                wprintf(L"GetTokenInformation() failed");
            CloseHandle(userToken);
        }
        else
            wprintf(L"WTSQueryUserToken() failed");

    }
    if (hToken == 0)
    {
        HANDLE systemToken;
        OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &systemToken);
        DuplicateTokenEx(systemToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hToken);
        CloseHandle(systemToken);
        int i;
        for (i = 0; i < (int)cnt; i++)
        {
            if (si[i].SessionId == 0)continue;
            if (SetTokenInformation(hToken, TokenSessionId, &si[i].SessionId, sizeof(DWORD)))
            {
                wprintf(_T("Success using system token with set user session id %i"), si[i].SessionId);
                break;
            }
        }
        if (i == cnt)
            wprintf(_T("No success to get user admin token nor system token with set user session id"));
    }
    WTSFreeMemory(si);

    STARTUPINFO startupInfo = {};
    startupInfo.cb = sizeof(STARTUPINFO);

    startupInfo.lpDesktop = _T("winsta0\\default");

    LPVOID pEnv = NULL;
    CreateEnvironmentBlock(&pEnv, hToken, TRUE);

    PROCESS_INFORMATION processInfo = {};
    PROCESS_INFORMATION processInfo32 = {};

    TCHAR szCurModule[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, szCurModule, MAX_PATH);

    BOOL bRes = FALSE;

    std::wstring commandLine;
    commandLine.reserve(1024);

    commandLine += L"\"";
    commandLine += szCurModule;
    commandLine += L"\" \"";
    commandLine += SERVICE_COMMAND_LUNCHER;
    commandLine += L"\"";

    wprintf(_T("launch SG_WinService with CreateProcessAsUser ...  %s"), commandLine.c_str());

    bRes = CreateProcessAsUserW(hToken, NULL, &commandLine[0], NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS |
        CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_DEFAULT_ERROR_MODE, pEnv,
        NULL, &startupInfo, &processInfo);

    if (bRes == FALSE) 
    {
            DWORD   dwLastError = ::GetLastError();
            TCHAR   lpBuffer[256] = _T("?");
            if (dwLastError != 0)    // Don't want to see a "operation done successfully" error ;-)
                ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,                 // It´s a system error
                    NULL,                                      // No string to be formatted needed
                    dwLastError,                               // Hey Windows: Please explain this error!
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),  // Do it in the standard language
                    lpBuffer,              // Put the message here
                    255,                     // Number of bytes to store the message
                    NULL);
            (_T("CreateProcessAsUser(SG_WinService) failed - Error : %s (%i)"), lpBuffer, dwLastError);
    }
    else
    {
        wprintf(_T("CreateProcessAsUser(SG_WinService) success. New process ID: %i"), processInfo.dwProcessId);
    }
}
возвращает следующие выходные данные:

Цитата:
05.06.2019 04:25 5856: попытка сеанса с идентификатором 1 (консоль) пользователя admin маркер

05.06.2019 04:25 5856: Trying session id 2 (RDP-Tcp#27) user admin token

05.06.2019 04:25 5856: Trying session id 65536 (RDP-Tcp) user admin token

05.06.2019 04:25 5856: успешное использование системного токена с установленным идентификатором сеанса пользователя 1

05.06.2019 04:25 5856: запуск SG_WinService с помощью CreateProcessAsUser ... "C:\myservice\SG_WinService.exe" ServiceIsLuncher"

05.06.2019 04:25 5856: CreateProcessAsUser(SG_WinService) успех. Новый идентификатор процесса: 5580

Поэтому вместо запуска дочернего процесса в сеансе 1 он работает как "система".

johannesnestler

Отлично, но, может быть, не надо делать работу так, как она должна быть. Может быть, еще раз прочтите комментарий Дэйва и выполните то, что он вам сказал, не должно быть так уж сложно… В .NET я использую WCF "AdministrationService", размещенный внутри моего windowsservice для IPC - так что мое настольное приложение может подключаться к службе и наоборот. Я делаю это для всех моих windowsservice по умолчанию, если ничего другого я не могу контролировать WindowsService и "смотреть внутрь".

Michael Haephrati

Это технический вопрос. Это не просьба о консультации. Мы предпочитаем избегать использования .NET в настольных и основных серверных приложениях и использует чистый Win32 API. Ответ Дейва-это не ответ, а скорее комментарий. Я обратился к этому замечанию. Чтобы быть более конкретным, когда вы запускаете любой исполняемый файл на Windows Server 2012 и этот исполняемый файл запускает другой исполняемый файл, никакой пользовательский интерфейс не будет показан. Кроме того, следуя рекомендациям Microsoft, используя CreateProcessAsUser, по какой-то причине единственный найденный токен принадлежит специальному "системному" пользователю, который вызывает эту проблему.

[no name]

Почему он не работает с удаленного рабочего стола?

3 Ответов

Рейтинг:
22

Michael Haephrati

Мне удалось найти причину этой проблемы и решить ее.
Исходный код и так был в порядке. Я все сделал правильно, так как просмотрел все сеансы, чтобы использовать токен от первого пользователя, вошедшего в систему и запустившего дочерний процесс с пользовательским интерфейсом в этом сеансе.

По какой-то причине он не работал, потому что я подключился к Windows Server 2012 через удаленный рабочий стол, но когда я запустил его физически на том же сервере, он работал: показывался пользовательский интерфейс и работала горячая клавиша!

Я получил помощь после того, как задал тот же вопрос в Сайте StackOverflow[^] и получил помощь от Гарри Джонстон[^] так что спасибо!

Он посоветовал мне вызвать GetLastError (), что я и сделал. Похоже, что при подключении через удаленный рабочий стол (как администратор) пользователь-администратор не обязательно входит в систему, поэтому единственным сеансом, возможным для запуска дочернего процесса, была "система", что было неправильно, поскольку этот "пользователь" не предназначен для использования для других, кроме системных служб без пользовательского интерфейса.

Мой обновленный код, который работает при физической установке на Windows Server 2012, таков:

void WINAPI Run(DWORD dwTargetSessionId, int desktop, LPTSTR lpszCmdLine)
{
    wprintf(_T("Run client start"));

    if (hPrevAppProcess != NULL)
    {
        TerminateProcess(hPrevAppProcess, 0);
        WaitForSingleObject(hPrevAppProcess, INFINITE);
    }


    HANDLE hToken = 0;
    WTS_SESSION_INFO *si;
    DWORD cnt = 0;
    WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &si, &cnt);
    for (int i = 0; i < (int)cnt; i++)
    {
        if (si[i].SessionId == 0)continue;
        wprintf(_T("Trying session id %i (%s) user admin token"), si[i].SessionId, si[i].pWinStationName);
        HANDLE userToken;
        if (WTSQueryUserToken(si[i].SessionId, &userToken))
        {
            wprintf(_T("WTSQueryUserToken succeced"));
            TOKEN_LINKED_TOKEN admin;
            DWORD len;
            if (GetTokenInformation(userToken, TokenLinkedToken, &admin, sizeof(TOKEN_LINKED_TOKEN), &len))
            {
                wprintf(_T("Success using user admin token"));
                hToken = admin.LinkedToken;
                break;
            }
            else
                wprintf(L"GetTokenInformation() failed");

            CloseHandle(userToken);
        }
        else
        {
            DWORD error = GetLastError();
            if (error)
            {
                LPVOID lpMsgBuf;
                DWORD bufLen = FormatMessage(
                    FORMAT_MESSAGE_ALLOCATE_BUFFER |
                    FORMAT_MESSAGE_FROM_SYSTEM |
                    FORMAT_MESSAGE_IGNORE_INSERTS,
                    NULL,
                    error,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                    (LPTSTR)&lpMsgBuf,
                    0, NULL);
                utils::WriteLogFile(L"Error '%s'\n", lpMsgBuf);
            }
        }

    }
    if (hToken == 0)
    {
        HANDLE systemToken;
        OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &systemToken);
        DuplicateTokenEx(systemToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hToken);
        CloseHandle(systemToken);
        int i;
        for (i = 0; i < (int)cnt; i++)
        {
            if (si[i].SessionId == 0)continue;
            if (SetTokenInformation(hToken, TokenSessionId, &si[i].SessionId, sizeof(DWORD)))
            {
                wprintf(_T("Success using system token with set user session id %i"), si[i].SessionId);
                break;
            }
        }
        if (i == cnt)
            wprintf(_T("No success to get user admin token nor system token with set user session id"));
    }
    WTSFreeMemory(si);


    STARTUPINFO startupInfo = {};
    startupInfo.cb = sizeof(STARTUPINFO);

    startupInfo.lpDesktop = _T("winsta0\\default");

    LPVOID pEnv = NULL;
    CreateEnvironmentBlock(&pEnv, hToken, TRUE);

    PROCESS_INFORMATION processInfo = {};
    PROCESS_INFORMATION processInfo32 = {};

    TCHAR szCurModule[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, szCurModule, MAX_PATH);

    BOOL bRes = FALSE;

    std::wstring commandLine;
    commandLine.reserve(1024);

    commandLine += L"\"";
    commandLine += szCurModule;
    commandLine += L"\" \"";
    commandLine += SERVICE_COMMAND_LUNCHER;
    commandLine += L"\"";

    wprintf(_T("launch SG_WinService with CreateProcessAsUser ...  %s"), commandLine.c_str());

    bRes = CreateProcessAsUserW(hToken, NULL, &commandLine[0], NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS |
        CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_DEFAULT_ERROR_MODE, pEnv,
        NULL, &startupInfo, &processInfo);

    if (bRes == FALSE)
    {
        DWORD   dwLastError = ::GetLastError();
        TCHAR   lpBuffer[256] = _T("?");
        if (dwLastError != 0)    // Don't want to see a "operation done successfully" error ;-)
            ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,                 // It´s a system error
                NULL,                                      // No string to be formatted needed
                dwLastError,                               // Hey Windows: Please explain this error!
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),  // Do it in the standard language
                lpBuffer,              // Put the message here
                255,                     // Number of bytes to store the message
                NULL);
        (_T("CreateProcessAsUser(SG_WinService) failed - Error : %s (%i)"), lpBuffer, dwLastError);
    }
    else
    {
        wprintf(_T("CreateProcessAsUser(SG_WinService) success. New process ID: %i"), processInfo.dwProcessId);
    }
}


Рейтинг:
2

Dave Kreskowiak

Это было в далеком 2006 году. Запуск процесса в качестве пользователя из сервиса теперь не одобряется. Просто не делайте этого, так как с тех пор безопасность была ужесточена, не позволяя службам запускать процессы на рабочем столе пользователя.

Подумайте об этом. Что произойдет, если в то время не было пользователя, вошедшего в систему?

Правильный способ сделать это-иметь небольшое пользовательское приложение, которое запускается, когда пользователь входит в систему, и оно разговаривает со службой через канал IPC, например именованные каналы. Когда служба должна запустить приложение, она просто говорит маленькому приложению сделать это для службы. Запуск приложения оттуда очень прост и всегда будет выполняться как пользователь, который вошел в систему.

Ваша служба даже не должна беспокоиться о том, что пользователь не вошел в систему. Если на другом конце канала IPC нет приложения для прослушивания, то нет и приложения для запуска вашего процесса.


Michael Haephrati

Мой вопрос заключается не в поиске обходного пути для служб, поскольку они теперь изолированы, а в феномене с Windows Server 2012, когда даже дочерняя программа Win32 не показывает никакого пользовательского интерфейса.

Я знаю об изменениях, но мой вопрос возникает после того, как эти изменения были сделаны. Изменение, о котором вы говорите, заключается в следующем: https://www.msigeek.com/1521/appcompat-series-session-0-isolation-and-secure-desktop.
После этого изменения службы Windows не могут иметь никакого взаимодействия с пользователем, и поэтому им необходимо вызывать клиентский процесс для каждого вошедшего пользователя (который отвечает на ваш вопрос о пользователе, не вошедшем в систему), и мой вопрос именно об этом. Он отлично работает на рабочем столе Windows (например, 10), но не на Windows Server 2012.

Вы написали: "правильный способ сделать это-иметь небольшое пользовательское приложение, которое запускается, когда пользователь входит в систему, и оно разговаривает со службой через канал IPC, например именованные каналы. Когда служба должна запустить приложение, она просто говорит маленькому приложению сделать это для службы. Запуск приложения оттуда очень прост и всегда будет выполняться как пользователь, который вошел в систему"., ну, мое пользовательское приложение отлично работает вместе со службой в Windows 10, но не в Windows Server 2012.

Рейтинг:
2

KarstenK

Службы Windows предназначены для отсутствие взаимодействия с пользователем, поэтому использование пользовательского интерфейса-это всегда недостаток дизайна. Запишите в системный журнал какое-нибудь сообщение и укрепите свой код, чтобы он мог справиться с проблемами.

Мой опыт заключается в том, чтобы закрыть полный сервис в верхнем цикле и проверять каждую поездку туда и обратно, будет ли сервис перезапускать свою внутреннюю функциональность. Может быть, вы можете показать значок или лучше отправить какое-нибудь электронное письмо. Иногда дополнительная задержка после ошибок имеет смысл.

Всегда помните: сервер работает нормально без зарегистрированного пользователя, чтобы повысить безопасность.


Michael Haephrati

Это неверный ответ. Никто не утверждал, что службы Windows имеют или должны иметь какой-либо пользовательский интерфейс. Это было бы невозможно, поскольку службы работают в сеансе 0, который также известен как "пользовательская система". Вопрос заключается в рекомендуемом методе вызова настольного процесса путем вызова CreateProcessAsUser (), который завершается неудачей только на сервере 2012 (пожалуйста, не смешивайте "сервис" и "сервер").

Michael Haephrati

Я был бы рад получить доступ к вашему ответу, если вы снова вернетесь к этому вопросу. Посмотрите на исходный код от WTSEnumerateSessions() и вниз. Вопрос заключается в том, что вы не можете найти первого вошедшего пользователя и вместо этого находите только токен "SYSTEM", что приводит к запуску настольного клиента (с пользовательским интерфейсом) как "SYSTEM", что плохо, как вы правильно говорите, но цель состоит в том, чтобы запустить настольное клиентское приложение для вошедшего пользователя, и когда это правильно сделано, пользовательский интерфейс отображается. Мне удалось сделать это в Windows 10.