Windows10でBASIC認証画面にIDとパスワードを自動入力するソフトを作った

この記事の内容は、ライブラリやソフトのダウンロードなしで使えます。

# やれること
下のようなWindowがでているときに本記事のプログラムを実行すると、指定のIDとパスワードを自動で入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/1e7bc0f6-831c-53cf-c13d-5fc0553ae7ba.png)


# C#のコンパイル
このあたりが参考になるかと。
https://qiita.com/yuki451/items/c9c6a2b79ce96ae0d252

今回のソースに関しては、下記のようにアセンブリのパスを指定する（環境に依存するかも）。

```

csc /r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\UIAutomationClient\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationClient.dll ^
/r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\UIAutomationTypes\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationTypes.dll ^
/r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll ^
Test.cs

```

# ソースコード

```csharp:Test.cs

using System;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Windows.Automation;


public static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow);

    public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public extern static bool EnumWindows(EnumWindowsDelegate lpEnumFunc,   IntPtr lparam);
}

public class Class1
{
    static AutomationElement FindCredentialWindow()
    {
        IntPtr hWnd = NativeMethods.FindWindowEx(IntPtr.Zero, IntPtr.Zero, "Credential Dialog Xaml Host", "Windows セキュリティ");
        
        if ( hWnd == IntPtr.Zero ) {
            return null;
        }

        return AutomationElement.FromHandle(hWnd);
    }

    static string FindTextBlockMatches(AutomationElement aeForm, Regex r)
    {
        var elems = FindElementsByClassName(aeForm, "TextBlock");
        foreach ( AutomationElement elem in elems ) {
            var elemInfo = elem.Current;
            if ( r.IsMatch(elemInfo.Name) ) {
                return elemInfo.Name;
            }
        }
        return null;
    }

    static void InputToCredentialWindow(AutomationElement aeForm, string userId, string password)
    {
        AutomationElement aeUserId   = FindElementById(aeForm, "EditField_0");
        AutomationElement aePassword = FindElementById(aeForm, "PasswordField_1");
        AutomationElement aeOkButton = FindElementById(aeForm, "OkButton");

        if ( aeUserId != null && aePassword != null && aeOkButton != null ) {
            var vpUserId   = (ValuePattern)aeUserId.GetCurrentPattern(ValuePattern.Pattern);
            var vpPassword = (ValuePattern)aePassword.GetCurrentPattern(ValuePattern.Pattern);
            vpUserId.SetValue(userId);
            vpPassword.SetValue(password);
            
//  OKも自動で押す場合はこれ
//            var ipOkButton = (InvokePattern)aeOkButton.GetCurrentPattern(InvokePattern.Pattern);
//            ipOkButton.Invoke();
        }
    }

    static AutomationElement FindElementById(AutomationElement rootElement, string automationId)
    {
        var cond = new PropertyCondition(AutomationElement.AutomationIdProperty, automationId);
        return rootElement.FindFirst(TreeScope.Descendants, cond);
    }

    static AutomationElementCollection FindElementsByClassName(AutomationElement rootElement, string className)
    {
        var cond = new PropertyCondition(AutomationElement.ClassNameProperty, className);
        return rootElement.FindAll(TreeScope.Descendants, cond);
    }

    [STAThread]
    static void Main()
    {
        AutomationElement aeForm = FindCredentialWindow();
        if ( aeForm != null ) {
            
            // 対象サイト(?)のメッセージに応じて適宜変更. 改行とか合わせるのが面倒なので正規表現で探すようにした
            string text = FindTextBlockMatches(aeForm, new Regex("^サーバー(.*)が"));
            
            if ( text != null ) {
                Console.WriteLine(text);

                // 下記は ID, PASSWORD に応じて適宜変更。機密管理に注意すること。
                //  やらかし例：ソースにパスワード埋め込んだままGitHubなどでうっかり公開したりとか
                InputToCredentialWindow(aeForm, "userid", "password");
            }
            else {
                Console.WriteLine("Not found Credential window with the description.");
            }
        }
        else {
            Console.WriteLine("Not found Credential window.");
        }
    }
}

```

# 事前調査
BASIC認証の画面の構成要素を下記のツールで調べた。
https://qiita.com/kob58im/items/3587d8e595e655e9391d



# 参考サイト
https://www.atmarkit.co.jp/fdotnet/special/uiautomation/uiautomation_03.html

# その後
`EditField_0`、`PasswordField_1`の数字が違う認証画面が出るケースに遭遇した。
Windows仕様が分からん…

# さらにその後 - Edge対応
 - [Microsoft Edge が出すパスワード入力画面に自動入力する - Qiita](https://qiita.com/kob58im/items/571b834830c0199337c0)