はじめに
Windowsのログオン画面にログオン方法を追加するCredential Providerと、ログオン方法を使えなくするCredential Provider Filterの話。
Credential Provider
サンプル
まずは何はともあれサンプル。
https://docs.microsoft.com/en-us/samples/microsoft/windows-classic-samples/credential-provider/
にある。
Browse codeのボタンから、GitHubの該当ページに飛ぶので、まとめてダウンロードしたければ windows-classic-samples のところをクリック、codeからDownload-ZIPを選択してダウンロードする。
ZIPを展開し、CredentialProvider\cpp にある SampleV2CredentialProvider.sln を VisualStudio で開けば良い。
※ここでは VisualStudio 2019 を使う
VisualStudio 2019の場合、プロジェクトの変換が入るが、多分問題ない。
サンプルのビルド
64bit Windowsで使う場合は x64 でビルドする必要があるので注意。SysWOW64 に置けばいいかもしれないけど未確認。
ランタイムが入っていないと動かないので、プロジェクトのプロパティ⇨C++⇨コード生成 で ランタイムライブラリ を マルチスレッド(MT) にしておく。これでスタティックリンクされる。
(もちろん、ランタイムを入れるでも良い)
また、全般で Windows SDK バージョンの設定を変えないとエラーが出るかもしれない。
これでビルドできるはず。ビルドするとSampleV2CredentialProvider.dllができる。
動かしてみる
Hyper-Vとかで仮想環境のWindowsを作っておくと、なんか間違ってログオンできなくなっても復帰が楽でオススメ。
まずはビルドしたSampleV2CredentialProvider.dllを Windows\System32 にコピー、そして、ソースツリー内にある register.reg をダブルクリックしてレジストリ書き換え。
これで登録される。
再起動すると、ログオン画面に サインインオプション のリンクが表示され、SampleV2CredentialProvider が選べるようになっているはず。
ダメな時は x64 でビルドされているか、ランタイムをスタティックリンクしているかを確認。
サンプルの説明をちょっとだけ
処理の中心は CSampleCredential。
また、表示される各コントロール(TextEdit, ComboBoxなど)の定義はcommon.hにある。
大体はCSampleCredentialの各メソッドが Windows Logon のシステム(LogonUI)から呼び出される形で実行される。
最初の各コントロール初期化は CSampleProvider にある GetFieldDescriptorCount と GetFieldDescriptorAt が呼び出されて行われる。
その後、パスワードが入力されると CSampleCredential::SetFieldString が呼び出され、パスワードのテキストボックスに●が出たりする。
実際のログオン処理は CSampleCredential::GetSerialization で、最後に ReportResult が呼び出される。
裏でなんかして表示を更新とか
他のサービスなんかと通信してログオンするような仕組みを作るため、別スレッドを動かしてログオン画面に何かすることを考える。
CSampleCredential::Advise が画面表示開始時に呼ばれるようなので、ここでスレッドを開始。
CSampleCredential::UnAdvise でスレッドを止めると良さそう。
何か画面に変化を起こすときは、CSampleCredential::CommandLinkClicked が参考になる。
ICredentialProviderCredentialEvents::BeginFieldUpdates で始めて、EndFieldUpdates で終わる。
表示している文字列を書き換えるときは、ICredentialProviderCredentialEvents::SetFieldString で、表示・非表示の切り替えなどは SetFieldState という感じ。
気を付けないといけないのはリモートデスクトップの対応。2つCredential Providerが起動して(ローカルとリモート)それぞれ別プロセスとして起動するらしい。なので、排他制御とかはプロセス間でやらないといけない。
通信してログオンする場合も、どちらか一方だけでログオンしないと基本的にはリモート側が切断される。(ローカルはリモートのログオンが成功した直後にログオン画面に戻ってログオンしてしまう)
Credential Provider Filter
https://social.msdn.microsoft.com/Forums/ja-JP/90e4a887-6da0-4961-96c5-2d2d5581695a/credential-provider-filter-?forum=windowssdksupportteamja
この記事がとてもよくできていて、そのままやれば作れる。
ただ、プロジェクトテンプレートでATLプロジェクトを使うとなんか余計なファイルがいっぱい出てくるので、いやならMFCアプリケーションでDLLを指定してプロジェクトを作ると良い。
そこにシンプルATLオブジェクトを追加する。
ただし、VS2015だと、MFCでDLLを作るやり方だとビルド時にエラーになる。上のリンク先で使っているVS2010なら大丈夫。
VS2015をつかう場合はATLプロジェクトで作ること。
この場合、管理者権限でVisualStudioを起動しないとビルド時にエラーになる。これはregsvr32が失敗するため。
エラーが出ていてもdll自体のビルドは終わっているので、気にしなくても大丈夫。
また、こちらも64bit環境で使用する場合はx64でビルドする必要がある。32bitビルドのdllはregsvr32 で失敗する。(SysWOW64に置けばいいのだと思うが未確認)
Filterメソッドの実装のところで、たとえばパスワードログインを消したい場合は
if( rgclsidProviders[ i ] がフィルタ対象の GUID) {
のところを
if (IsEqualGUID(rgclidProviders[i], CLSID_PasswordCredentialProvider)) {
とする。
これでパスワードログインはできなくなるが、ファイルサーバへの接続などでのパスワード入力もできなくなってしまう。
そこで、Filterメソッド内で CREDENTIAL_PROVIDER_USAGE_SCENARIO の引数をチェックし、CPUS_LOGONかCPUS_UNLOCK_WORKSTATION以外なら何もしないようにするとうまくいく。