以前WNetAddConnection2を使って他のPCのネットワーク共有にアクセスする方法について書きましたが、これをサービスから実行するとWNetAddConnection2が時々エラー1312(ERROR_NO_SUCH_LOGON_SESSION)になる、という現象が発生します。調べてみたところ、サービスの場合はSystemではなくNetworkServiceで動作していないとこのような状態になるようです(常にエラーになるわけではなく、成功したりエラーになったりという感じ)。もちろんサービスをNetworkServiceで実行すればネットワークアクセスでエラーになることはないのですが、ローカルコンピュータに対するアクセスがUsersグループ相当に制限されてしまいます(サービスで使用する(Local)System/LocalService/NetworkServiceアカウントについてはWindowsテクニカルドキュメント(旧MSDN)のService User Accounts、LocalService Account、NetworkService Account、LocalSystem Accountや@ITのWindowsのサービスで使用される「System」「Local Service」「Network Service」アカウントとは?:Tech TIPSを参照)。
そこでSystemアカウントで動作するサービスから実行することを前提に、ネットワークリソースにアクセスするときだけNetworkServiceアカウントに偽装するようにしてみます。
まずLogonUserでNetworkServiceとしてログインし、返されたトークンハンドルでImpersonateLoggedOnUserを呼び出すことでユーザを偽装します。
const
LOGON32_LOGON_NEW_CREDENTIALS = 9;
{$EXTERNALSYM LOGON32_LOGON_NEW_CREDENTIALS}
var
LogonUserUser: String;
LogonUserDomain: String;
LogonUserLogonType: DWORD;
LogonUserLogonProvider: DWORD;
hToken: THandle;
begin
{ Logon as 'NetworkService' user }
LogonUserUser := 'NetworkService';
LogonUserDomain := 'NT AUTHORITY';
LogonUserLogonType := LOGON32_LOGON_NEW_CREDENTIALS;
LogonUserLogonProvider := LOGON32_PROVIDER_DEFAULT;
if LogonUser(PChar(LogonUserUser),PChar(LogonUserDomain),nil,
LogonUserLogonType,LogonUserLogonProvider,hToken) = False then
begin
RaiseLastOSError;
end;
{ Impersonate }
if ImpersonateLoggedOnUser(hToken) = False then
begin
RaiseLastOSError;
end;
これでこの後WNetAddConnection2で接続を試みるときにSystemアカウントではなくNetworkServiceアカウントであるかのように扱われます。切断するときはまずWNetCancelConnection2を呼び出した後で、RevertToSelfで偽装を解除し、CloseHandleでトークンハンドルをクローズすることでログオフします。
{ Revert impersonation }
if RevertToSelf() = False then
begin
RaiseLastOSError;
end;
{ Loggoff }
CloseHandle(hToken);
hToken := INVALID_HANDLE_VALUE;
なおプロセス上で複数のスレッドが動作している場合(特にサービス)など、同一の共有リソース('\\<computername>\<sharename>')に対してWNetAddConnection2をネストして呼び出すとERROR_ALREADY_ASSIGNEDでエラーになります。これを防ぐには同一の共有リソースに対してWNetAddConnection2/WNetCancelConnection2が1回ずつ呼び出されるように、接続している共有リソースをプロセス全体で適切に管理する必要もあります。