Microsoft Edge が出すパスワード入力画面に自動入力する

# まえがき

以前、[Windows10でBASIC認証画面にIDとパスワードを自動入力するソフト](https://qiita.com/kob58im/items/f373df61e9ea0be7befa)を作って長らく職場で愛用していたのですが、つい最近職場で導入された Microsoft Edge が、いつもと違う画面を出しやがるわけです。[^1]

<b>これ</b>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/0c873b6a-fe20-3142-17fb-8399362358a7.png)


# というわけで下調べ

こいつのハンドルをつかんで自動入力してやろうと思い立ったわけで、まずは、[以前作成したツール](https://qiita.com/kob58im/items/3587d8e595e655e9391d)で、手掛かりを探します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/10341e20-350a-14d0-6599-f19761122c19.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/b4463aae-d955-cd50-935a-3c73b226d981.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/855ceb4d-72c2-abc4-dad5-c3ac18263fe6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/b8c23384-2f30-2f8c-db5f-c8cc94e82ba8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/af4765eb-3866-8666-c978-86581bf4f55a.png)
AutomationIdがふられておらず、ちょっとめんどくさそう。

# とりあえず作る

[Windows10でBASIC認証画面にIDとパスワードを自動入力するソフト](https://qiita.com/kob58im/items/f373df61e9ea0be7befa)をベースにテキトウにいじってみた。


```csharp:AutoInputToEdge.cs

using System;
using System.Collections.Generic;
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 FindEdgeCredentialForm()
    {
        var wnds = FindEdgeWindows();
        //Console.WriteLine(wnds.Count);
        foreach ( IntPtr hWnd in wnds ) {
            //Console.WriteLine("--");
            AutomationElement aeWindow = AutomationElement.FromHandle(hWnd);
            AutomationElement a = aeWindow;
            a = FindFirstChildElementByClassName(a, "BrowserRootView");
            if ( a == null ) {/*Console.WriteLine("-1-");*/continue;}
            a = FindFirstChildElementByClassName(a, "RootView");
            if ( a == null ) {/*Console.WriteLine("-2-");*/continue;}
            a = FindFirstChildElementByClassName(a, "NonClientView");
            if ( a == null ) {/*Console.WriteLine("-3-");*/continue;}
            a = FindFirstChildElementByClassName(a, "DialogClientView");
            if ( a == null ) {/*Console.WriteLine("-4-");*/continue;}
            var aLoginView = FindFirstChildElementByClassName(a, "LoginView");
            if ( aLoginView == null ) {/*Console.WriteLine("-5-");*/continue;}
            var aView = FindFirstChildElementByClassName(a, "View");
            if ( aView == null ) {/*Console.WriteLine("-6-");*/continue;}

            return a;
        }
        return null;
    }
    static List<IntPtr> FindEdgeWindows()
    {
        var ret = new List<IntPtr>();
        IntPtr hWnd = IntPtr.Zero;

        while ( true ) {
            hWnd = NativeMethods.FindWindowEx(IntPtr.Zero, hWnd, "Chrome_WidgetWin_1", null);
            if ( hWnd == IntPtr.Zero ) {
                break;
            }
            else {
                ret.Add(hWnd);
            }
        }
        return ret;
    }

    static string FindLabelMatches(AutomationElement aeLoginView, Regex r)
    {
        var elems = FindChildElementsByClassName(aeLoginView, "Label");
        foreach ( AutomationElement elem in elems ) {
            var elemInfo = elem.Current;
            if ( r.IsMatch(elemInfo.Name) ) {
                return elemInfo.Name;
            }
        }
        return null;
    }

    static void InputToEdgeCredentialForm(AutomationElement aeLoginView, AutomationElement aeView, string userId, string password)
    {
        var textElems = FindChildElementsByClassName(aeLoginView, "Textfield");
        if (textElems.Count!=2) {
            return;
        }
        AutomationElement aeUserId   = textElems[0];
        AutomationElement aePassword = textElems[1];
        AutomationElement aeSignInButton = FindFirstChildElementByClassName(aeView, "MdTextButton");

        if ( aeUserId != null && aePassword != null && aeSignInButton != 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)aeSignInButton.GetCurrentPattern(InvokePattern.Pattern);
            ipOkButton.Invoke();
        }
    }

    static AutomationElement FindFirstChildElementByClassName(AutomationElement rootElement, string className)
    {
        var cond = new PropertyCondition(AutomationElement.ClassNameProperty, className);
        return rootElement.FindFirst(TreeScope.Children, cond);
    }
    static AutomationElementCollection FindChildElementsByClassName(AutomationElement rootElement, string className)
    {
        var cond = new PropertyCondition(AutomationElement.ClassNameProperty, className);
        return rootElement.FindAll(TreeScope.Children, cond);
    }

    [STAThread]
    static void Main()
    {
        AutomationElement aeForm = FindEdgeCredentialForm();
        if ( aeForm != null ) {
            var aLoginView = FindFirstChildElementByClassName(aeForm, "LoginView");
            var aView = FindFirstChildElementByClassName(aeForm, "View");

            if (aLoginView==null || aView==null){
                Console.WriteLine("Unexpected error.");
            }

            // 対象サイト(?)のメッセージに応じて適宜変更. 改行とか合わせるのが面倒なので正規表現で探すようにした
            string text = FindLabelMatches(aLoginView, new Regex(@"^http(s?)://")); // 適宜変更してください

            if ( text != null ) {
                Console.WriteLine(text);

                // 下記は ID, PASSWORD に応じて適宜変更。機密管理に注意すること。
                InputToEdgeCredentialForm(aLoginView, aView, "userid", "password");
            }
            else {
                Console.WriteLine("Not found Credential window with the description.");
            }
        }
        else {
            Console.WriteLine("Not found Credential window.");
        }
    }
}
```

# コンパイル方法

`compile.bat AutoInputToEdge.cs`

```compile.bat

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 ^
 %*

```

[^1]: 素直にパスワード保存機能が使えればいいのですが、社内システムはIEでしか使えず、どちらも同じ串なのと、定期的にパスワード変更させられるので、パスワードを保存するのはイマイチ。。どちらにも使えるように共通化したいところ。（というかクソインフラをどうにかしてほしい・・・）