以前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回ずつ呼び出されるように、接続している共有リソースをプロセス全体で適切に管理する必要もあります。