Запуск Outlook Express из сервиса (Win2000).
От: Вячеслав Мишуров Россия  
Дата: 04.02.02 05:59
Оценка:
Натолкните пожалуйста на мысль.

Есть работающий сервис.
В некоторый момент он выводит окно, одна из кнопок на котором
запускает Outlook Express. Но запускается он для LocalAccount,
а надо для залогиненого юзера. Как поступить ?
Re: Запуск Outlook Express из сервиса (Win2000).
От: Alex Fedotov США  
Дата: 04.02.02 06:48
Оценка: 2 (1)
Здравствуйте Вячеслав Мишуров, Вы писали:

ВМ>Есть работающий сервис.

ВМ>В некоторый момент он выводит окно, одна из кнопок на котором
ВМ>запускает Outlook Express. Но запускается он для LocalAccount,
ВМ>а надо для залогиненого юзера. Как поступить ?

Надо каким-то образом заполучить токен текущего пользователя. Это можно сделать:

1) перечислив процессы и взяв токен, скажем, еxplorer.exe (более точно — процесса оболочки, зарегистрированного в системе), либо

2) иметь процесс, запускаемый в контексте пользователя (например, с помощью ключа реестра Run), который соединяется с сервисом посредством named pipe, COM или другого механизма, поддерживающего имперсонацию. В этот момент сервис может имперсонировать пользователя и получить его токен. Вспомогательный процесс может благополучно завершиться после этого.

Второй способ лучше, так как корректно работает в случае нескольких интерактивных сессий в Terminal services и XP Fast User Switching.

После того как токен получен, процесс OE создается функцией CreateProcessAsUser, причем в структуре STARTUPINFO необходимо не забыть указать lpDesktop как "winsta0\\default".

Для полной иллюзии создания интерактивного процесса, в CreateProcessAsUser нужно передать новый environment block и заполнить его переменными, исходя из списка переменных окружения пользователя:

HKEY_USERS\<user-sid>\Environment
HKEY_USERS\<user-sid>\Volatile Environment

Впрочем, я думаю, что для OE это не обязательно.
-- Alex Fedotov
Re[2]: Запуск Outlook Express из сервиса (Win2000).
От: Аноним  
Дата: 05.02.02 04:23
Оценка:
Здравствуйте Alex Fedotov, Вы писали:

AF>Надо каким-то образом заполучить токен текущего пользователя. Это можно сделать:

AF>1) перечислив процессы и взяв токен, скажем, еxplorer.exe (более точно — процесса оболочки, зарегистрированного в системе), либо

Я решил попробовать первый способ так как кажется он проще (пробовал в Delphi):

procedure TForm1.Button1Click(Sender: TObject);
var
    ProcessId: Integer;
    hWindow, hProcess, TokenHandle: THandle;
    si: Tstartupinfo;
    p: Tprocessinformation;
begin
    hWindow := FindWindow('Progman', 'Program Manager');
    GetWindowThreadProcessID(hWindow, @ProcessID);
    hProcess := OpenProcess (PROCESS_ALL_ACCESS, FALSE, ProcessID);
    OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, TokenHandle) then begin

    FillChar(si,SizeOf(si),0);
    with Si do begin
        cb := SizeOf( Si);
        dwFlags := startf_UseShowWindow;
        wShowWindow := SW_NORMAL;
        lpDesktop := PChar ('winsta0\default');
    end;

    CreateProcessAsUser(TokenHandle, nil,
        'C:\Program Files\Outlook Express\msimn.exe',
        nil, nil, false, Create_default_error_mode, nil, nil, si, p);
end;


Вроде окно находит, дескриптор процесса получает, OpenProcessToken тоже без ошибок.
А при выполнении CreateProcessAsUser возникает ошибка 1314 (отказано в доступе ?).
Что я забыл сделать ?


P.S. :shuffle: Заранее изивиняюсь, если вопрос детский, я раньше с WinApi почти не сталкивался.
Re[3]: Запуск Outlook Express из сервиса (Win2000).
От: Alex Fedotov США  
Дата: 05.02.02 04:38
Оценка: 2 (1)
Здравствуйте Аноним, Вы писали:

AF>>Надо каким-то образом заполучить токен текущего пользователя. Это можно сделать:

AF>>1) перечислив процессы и взяв токен, скажем, еxplorer.exe (более точно — процесса оболочки, зарегистрированного в системе), либо

А>Я решил попробовать первый способ так как кажется он проще (пробовал в Delphi):


А>[...]


А>Вроде окно находит, дескриптор процесса получает, OpenProcessToken тоже без ошибок.

А>А при выполнении CreateProcessAsUser возникает ошибка 1314 (отказано в доступе ?).
А>Что я забыл сделать ?

Вроде раньше шла речь о сервисе, работающем в LocalSystem?

1314 = ERROR_PRIVILEGE_NOT_HELD, означает что у вызывающего пользователя нет необходимых привилегий для выполнения этой функции. И действительно, если внимательно посмотреть в документацию CreateProcessAsUser, то можно обнаружить, что она требует привилегию SE_ASSIGNPRIMARYTOKEN_NAME, которая по умолчанию есть только у LocalSystem.

То есть надо запускать этот код из сервиса, там должно работать.

P.S. В исходном коде стоит заменить PROCESS_ALL_ACCESS и TOKEN_ALL_ACCECSS на PROCESS_QUERY_INFORMATION и TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY соответственно.
-- Alex Fedotov
Re[2]: Запуск Outlook Express из сервиса (Win2000).
От: George_Seryakov Россия  
Дата: 05.02.02 04:59
Оценка:
Здравствуйте Alex Fedotov, Вы писали:

ВМ>>Есть работающий сервис.

ВМ>>В некоторый момент он выводит окно, одна из кнопок на котором
ВМ>>запускает Outlook Express. Но запускается он для LocalAccount,
ВМ>>а надо для залогиненого юзера. Как поступить ?

AF>2) иметь процесс, запускаемый в контексте пользователя (например, с помощью ключа реестра Run), который соединяется с сервисом посредством named pipe, COM или другого механизма, поддерживающего имперсонацию. В этот момент сервис может имперсонировать пользователя и получить его токен. Вспомогательный процесс может благополучно завершиться после этого.


Вот пусть этот процесс и запускает OE.

А вообще — не соответвует ли OE некий AppId? И не следует ли прописать ему, чтоб он исполнялся из-под интерактивного юзера? И нет какого ClsId, создание которого стартует OE? И если нет, то нужно создать такое приложение и мы возвращаемся к ситуации предыдущего абзаца.
GS
Re[3]: Запуск Outlook Express из сервиса (Win2000).
От: Alex Fedotov США  
Дата: 05.02.02 05:53
Оценка:
Здравствуйте George_Seryakov, Вы писали:

ВМ>>>Есть работающий сервис.

ВМ>>>В некоторый момент он выводит окно, одна из кнопок на котором
ВМ>>>запускает Outlook Express. Но запускается он для LocalAccount,
ВМ>>>а надо для залогиненого юзера. Как поступить ?

AF>>2) иметь процесс, запускаемый в контексте пользователя (например, с помощью ключа реестра Run), который соединяется с сервисом посредством named pipe, COM или другого механизма, поддерживающего имперсонацию. В этот момент сервис может имперсонировать пользователя и получить его токен. Вспомогательный процесс может благополучно завершиться после этого.


GS> Вот пусть этот процесс и запускает OE.


Это было бы вообще идеально, только многие боятся, что пользователь пришибет этот процесс. Я уже давно заметил, не доверяют российские программисты пользователям.

GS> А вообще — не соответвует ли OE некий AppId? И не следует ли прописать ему, чтоб он исполнялся из-под интерактивного юзера? И нет какого ClsId, создание которого стартует OE? И если нет, то нужно создать такое приложение и мы возвращаемся к ситуации предыдущего абзаца.


А вот тут я не уверен. Если y COM-сервера стоит Identity=Interactive User в какой сессии он будет запускаться в случае Terminal Services? Я думаю, что в нулевой, а вот есть OE запускается той программкой, тогда можно в любой запускать.

И вообще, всем идти читать
Q308403 HOWTO: Design a Service to Interact with Multiple User Sessions
-- Alex Fedotov
Re[4]: Запуск Outlook Express из сервиса (Win2000).
От: Вячеслав Мишуров Россия  
Дата: 05.02.02 08:46
Оценка:
Здравствуйте Alex Fedotov, Вы писали:

AF>То есть надо запускать этот код из сервиса, там должно работать.


Да, действительно как сервис работает. Огромное спасибо.

Правда одна проблема осталась: OE запускается с настройками и адресной книгой
нужного пользователя, а вот каталог с папками берет для default user.

Нужно получить SID залогиненого пользователя?
(GetTokenInformation для ранее полученного токена и еще что-то? LookupAccountSid?)
А после получения SID ?
Re[5]: Запуск Outlook Express из сервиса (Win2000).
От: Alex Fedotov США  
Дата: 05.02.02 17:28
Оценка: 13 (2)
Здравствуйте Вячеслав Мишуров, Вы писали:

ВМ>Правда одна проблема осталась: OE запускается с настройками и адресной книгой

ВМ>нужного пользователя, а вот каталог с папками берет для default user.

Environment? Насколько я понимаю, запущенный тобой OE сейчас отличается от обычного только переменными окружения.

ВМ>Нужно получить SID залогиненого пользователя?

ВМ>(GetTokenInformation для ранее полученного токена и еще что-то? LookupAccountSid?)
ВМ>А после получения SID ?

Чтобы корректно установить environment (не знаю, насколько это поможет проблеме, но в голову ничего больше не приходит) нужно перечислить переменные вот в этих ключах

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
HKEY_USERS\<user-sid>\Environment
HKEY_USERS\<user-sid>\Volatile Environment

и объединить их. Это достаточно кропотливая задача, потому что нужно аккуратно склеить переменные PATH, а также развернуть все переменные REG_EXPAND_SZ.

Чтобы получить SID в двоичной форме, достаточно GetTokenInformation(hToken, TokenUser, ...). Чтобы преобразовать его в строку можно воспользоваться функцией ConvertSidToStringSid, но она есть только на Win2K и старше. На NT 4.0 несложно написать заменитель:

//---------------------------------------------------------------------------
// ConvertSidToTextSid
//
//  Converts a binary SID to the string representation. On Win2K and later
//  you can use the ConverSidToStringSid function for that. This function 
//  works on all platforms.
//
//  Parameters:
//      pSid   - pointer to the SID
//      pszSid - pointer to a buffer that receives the text SID
//      cchSid - size of the buffer in characters
//
//  Returns:
//      TRUE, if successful, FALSE - otherwise.
//
EXTERN_C
BOOL
APIENTRY
ConvertSidToTextSid(
    IN PSID pSid,
    OUT PTSTR pszSid,
    IN ULONG cchSid
    )
{
    _ASSERTE(pSid != NULL);
    _ASSERTE(IsValidSid(pSid));
    _ASSERTE(pszSid != NULL);

    *pszSid = 0;

    PSID_IDENTIFIER_AUTHORITY pSia;
    ULONG cAuthorities;
    ULONG cch;

    pSia = GetSidIdentifierAuthority(pSid);
    cAuthorities = *GetSidSubAuthorityCount(pSid);

    cch = (15 + 12 + (12 * cAuthorities) + 1) * sizeof(TCHAR);
    if (cch >= cchSid)
        return SetLastError(ERROR_INSUFFICIENT_BUFFER), FALSE;

    pszSid += wsprintf(pszSid, _T("S-%lu-"), ((SID *)pSid)->Revision);

    if (pSia->Value[0] != 0 || pSia->Value[1] != 0)
    {
        pszSid += wsprintf(pszSid, 
                           _T("0x%02hx%02hx%02hx%02hx%02hx%02hx"),
                           (USHORT)pSia->Value[0], (USHORT)pSia->Value[1],
                           (USHORT)pSia->Value[2], (USHORT)pSia->Value[3],
                           (USHORT)pSia->Value[4], (USHORT)pSia->Value[5]);
    }
    else
    {
        pszSid += wsprintf(pszSid, _T("%lu"),
                           (ULONG)(pSia->Value[5])
                           | (ULONG)(pSia->Value[4] <<  8)
                           | (ULONG)(pSia->Value[3] << 16)
                           | (ULONG)(pSia->Value[2] << 24));
    }

    for (ULONG i = 0; i < cAuthorities; i++)
    {
        pszSid += wsprintf(pszSid, _T("-%lu"), 
                           *GetSidSubAuthority(pSid, i));
    }    

    return TRUE;
}
-- Alex Fedotov
Re[6]: Запуск Outlook Express из сервиса (Win2000).
От: mvg Россия  
Дата: 06.02.02 10:58
Оценка:
Здравствуйте Alex Fedotov, Вы писали:

AF>Чтобы корректно установить environment (не знаю, насколько это поможет проблеме, но в голову ничего больше не приходит) нужно перечислить переменные вот в этих ключах


Все заработало. Получилось примерно вот так:

1. GetTokenInformation.
2. ConvertSidToStringSidA.
3. Читаем HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT CurrentVersion\ProfileList\<полученный в п.2 SID>\ProfileImagePath
4. SetEnvironmentVariable ('userprofile', <значение из п.3>);
5. CreateProcessAsUser

Еще раз спасибо за помощь.
Re[7]: Запуск Outlook Express из сервиса (Win2000).
От: Alex Fedotov США  
Дата: 06.02.02 17:00
Оценка:
Здравствуйте mvg, Вы писали:

mvg>Все заработало. Получилось примерно вот так:


mvg>1. GetTokenInformation.

mvg>2. ConvertSidToStringSidA.
mvg>3. Читаем HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NTmvg> CurrentVersion\ProfileList\<полученный в п.2 SID>\ProfileImagePath
mvg>4. SetEnvironmentVariable ('userprofile', <значение из п.3>);
mvg>5. CreateProcessAsUser

Вместо п.3 лучше использовать GetUserProfileDirectory.
-- Alex Fedotov
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.