UiPathサンプル - デスクトップアプリケーションからのデータ抽出を自動化する

公式サイトのKnowledge Baseサンプルを詳しく解説します。
今回は**"Automate Data Extraction from Desktop Application"**です。
https://www.uipath.com/kb-articles/automate-data-extraction-from-desktop-application

#シナリオ
経費精算のためのWindowsアプリケーション"ExpenseIt"を起動し、Emailアドレス、従業員番号等を入力してログイン、そして画面にテーブル状に表示された経費リストを自動的に読み取り、CSVファイルに保存します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/202992/904a784e-76d4-6f4a-4cfb-8aa1578cfe80.png" width=60%>


#このサンプルで使用する機能
- UiPath Studio
    - **Desktop Recording** - Windowsアプリケーションへのログインを記録します。
    - **Screen Scraping** - 画面に表示された経費リストを読み取ります。  
<br>
- UiPathアクティビティ
    - **Open Application** - 経費精算アプリケーションを起動します
    - **Type into** - 経費精算アプリケーションに情報を入力します
    - **Click** - ボタンをクリックします
    - **Build Data Table** - CSVデータを保持するDataTableを作成します
    - **Write Csv File** - DataTableをCSVファイルで保存します  
    ※Type into, ClickはRecording機能で自動的に作成されます。  
<br>
- Windows Workflow
    - **While**コントロール
    - **Assign**コントロール  
<br>
- .Net関連
    - System.data.**DataTable** - 経費データを格納するデータモデルです

#準備
1. サンプルZIPファイルをダウンロードします。
 https://drive.google.com/open?id=0BxVAKI3l9Pc5UWY5Ujd2eElZVUE

    ZIPファイルには以下のファイルが含まれています。これらのファイルを任意の場所に展開します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/202992/a26e5ebf-b103-d183-8739-42ccba081df3.png">

        - Expenses.csv - 出力されるCSVファイル
        - Main.xaml - ワークフローのXAML実装
        - project.json - UiPathプロジェクトファイル
        - WPF_30_ANYCPU_ExpenseIt.exe - 経費精算アプリケーション実行ファイル
<br> 
2. UiPathを起動します。Main.xamlを選択し、UiPathで開きます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/202992/66d768e3-8343-e5a0-268c-16e927e9e40f.png" width=50%>
<br>
3. Flowchartが表示されます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/202992/9f1a7d7d-fb86-c032-edb9-9a53e1f06265.png">

#実行と結果
1. Runボタン（またはF5キー)で実行します。  
<img src="https://qiita-image-store.s3.amazonaws.com/0/202992/a29fc5a4-1308-a733-44de-9ea7664c7bf6.png">  
<br>
2. ExpenseItが起動し、自動的に必要項目が入力されます。
3. Expenses.csvが出力されます。
    （zipファイルに元々含まれていましたが更新日時が変わっています）  
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/68fd63ad-0094-3950-1530-d094528073ba.png">  
    ファイルを開けると画面で表示された経費リストが出力されています。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/9bcb6ee3-c487-43b9-f3b5-98898097bb12.png">

    ビデオで見るとこのような感じになります。
    [![Video](http://img.youtube.com/vi/m8Irit9Q-qU/0.jpg)](http://www.youtube.com/watch?v=m8Irit9Q-qU)

#ワークフロー作成ビデオ
1から自分でworkflowを作るとこのようになります。
[![Video](http://img.youtube.com/vi/rU594RBfs8s/0.jpg)](http://www.youtube.com/watch?v=rU594RBfs8s)


#解説
1. "Open application 'WPF_30_ANYCPU_ExpenseIt...'"アクティビティ
<br>
 - OpenApplicationアクティビティ  
    UiPath.Core.Activitis.OpenApplication が使われています。  
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/b3aa04da-f08d-a12f-39c2-2045bf6e3423.png">

    ダブルクリックすると以下のようになっています。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/0102c11f-b699-afb4-8c51-2ace9677829e.png">  
    ①ここでは操作対象となるウィンドウが表示されています。
    Recording機能でウィンドウを選択した際、アプリケーション実行ファイルのパス(FileName)とウィンドウのSelectorが自動的に記録されます。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/86b97c54-81ab-a81f-20e6-35f7a328a574.png">

    FileNameには経費精算アプリケーションの実行ファイルのパスが記録されています。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/62091180-f14d-58e0-1dea-3c6bdd1edd01.png" width=50%>

    Selectorを確認してみましょう。Selectorとは、UiPathが操作対象のユーザ・インターフェース要素を特定するために使う情報で、Recording機能で自動的に設定されます。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/d5e2e11a-554c-a9fe-a1bc-ca78fb4f772c.png" width=50%>

    ここではアプリケーション・ウィンドウを特定するためのSelectorとして以下の３つが使用されています。

        "app" - 実行ファイル名
        "title" - ウィンドウタイトル
        "cls" - ウィンドウクラス名

    "app"は経費精算アプリケーションの実行ファイル名である"wpf_30_anycpu_expenseIt.exe"が、"title"はアプリケーションのウィンドウタイトルである"ExpenseIt Standalone"が、"cls"はウィンドウクラス名である"HwndWrapper*"がそれぞれ指定されています（アスタリスク*はワイルドカードで、HwndWrapperで始まる名前を指定しています）。  
    >**ウィンドウクラスについて**
実行ファイル名、ウィンドウタイトルについては自明ですが、ウィンドウクラスが何かを知るにはWindowsにおけるアプリケーション開発についての知識が必要です。Windowsアプリケーション開発でウィンドウを作成する際、[CreateWindow](https://msdn.microsoft.com/ja-jp/library/cc410713.aspx?f=255&MSPPError=-2147217396)というWindows APIを使い、引数として``lpClassName``としてウィンドウクラス名を渡し、どのようなデザイン・機能を持つウィンドウにしたいか指定します。これが"cls"で指定するウィンドウクラス名です。なお、指定するウィンドウクラスはあらかじめ[RegisterClass](https://msdn.microsoft.com/ja-jp/library/cc410996.aspx)を使い、[WNDCLASS構造体](https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms633576(v=vs.85).aspx)としてWindowsシステムに登録しなければいけません。  
参考１：[C++によるWindowsプログラミングの学習－ウィンドウを作成する](https://msdn.microsoft.com/ja-jp/library/windows/desktop/ff381397(v=vs.85).aspx)  
参考２："WinLister"というツールで筆者のPCでウィンドウクラス名の一覧を表示すると以下のようになります。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/ca7129b4-3e5e-c8b8-59c1-bdaeef98e12c.png">


    ②Window Scopeが表示されています。
    Recording機能を使う際、"Basic Recording"を使うとWindow Scopeはアクティビティとして出てきません。"Desktop Recording"を使うと出てきます。同じアプリケーション、同じウィンドウタイトルのウィンドウが複数並ぶ場合はDesktop Recordingを使い、明示的にWindowを指定する必要があります。  
    ③Type intoアクティビティでEmailアドレスを入力します。
    ④Type intoアクティビティで従業員番号(Employee number)を入力します。
    ⑤"Create Expense Report"ボタンをクリックします  
<br>

2. "Screen scraping"アクティビティ
<br>
 - "Screen scraping"アクティビティはRecording機能により"Window Scope"アクティビティ、Sequenceアクティビティ、"Get Full Text"アクティビティの組み合わせとして自動で生成されます。  
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/27c1b879-7cb0-d13c-3334-a6d80cb32957.png">

    ダブルクリックすると以下のようになっています。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/b3a0ce38-55c9-39dd-6089-7fa2e13e6b5d.png">

    ①"Screen scraping"の実体はSequenceアクティビティです。
    ②"Attach Window 'CreateExp Hwndwrapp'"はWindow Scopeアクティビティです。プロパティ"Selector"の詳細を見てみましょう。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/2175d894-5933-539b-558e-b7b571b237bf.png">

    Selector詳細
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/b2a7ca87-778f-755a-16c7-f18cbf7ba9d7.png">

    Open ApplicationアクティビティでもSelectorを使用していますがtitleのみ"Create Expense Report"に変わっています。これは、実際にウィンドウタイトルが親画面と子画面で異なっているためです。（Open Applicationアクティビティでは"ExpenseIt Standalone"でした）
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/435e882a-0c4f-a45b-cdba-78a55d44658e.png" width=70%>


    ③Get Full Textアクティビティで画面上のテキスト文字を読み取ります。
    実際には、以下の文字列が読み込まれます。

        システム
        元のサイズに戻す(R)
        移動(M)
        サイズ変更(S)
        最小化(N)
        最大化(X)
        閉じる(C)	Alt+F4
        Email Alias:
        Email _Alias:
        Someone@example.com
        Employee Number:
        Employee _Number:
        57304
        Cost Center:
        _Cost Center:
        4032
        Add Expense
        Add _Expense
        View Chart
        Expense Type
        Description
        Amount
        Meal
        Mexican Lunch
        12
        Meal
        Italian Dinner
        45
        Education
        Developer Conference
        90
        Travel
        Taxi
        70
        Travel
        Hotel
        60
        Total Expenses ($):
        277
        OK
        _OK
        Cancel
        _Cancel

    読み込まれた文字列はString変数"scrappedText"に格納されます。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/05c12796-852a-9fc2-45df-6a2f3e6b004c.png">


    **IgnoreHidden**オプションは今回チェックされていませんが、チェックした場合は画面上表示されていない「システム」から「閉じる(C) Alt+F4」などは読み込まれません。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/59559e3b-7d61-98c0-13d7-91235eec266f.png">
<br>

3. AssignでString型の配列変数"lines"に読み取った結果を代入します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/202992/5a24fb41-5de8-ba9e-1bfe-464410a76aca.png">
    ``lines = scrappedText.Split(separators,StringSplitOptions.RemoveEmptyEntries)``  
    第一引数の"separators"は下のように変数定義され、改行コードが現れたらlinesの次の配列要素へと文字列を入れていきます。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/1ae3fa5b-ba4a-1a2a-efd2-9a102a40c847.png">

    StringSplitOptions.RemoveEmptyEntries は"空の文字列を格納している配列要素を含まない"ようにするためのオプションです。
<br>

4. Build data table
    DataTable "dtExpenses"を作成します。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/e98a9b4d-0159-33b6-f3d6-3b66ef5051c4.png">

    "DataTable..."をクリックするとデータ構造が定義されています。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/91b502e2-3493-8bcb-15ba-b18231356be0.png">
<br>

5. Whileで必要な経費データが現れるまでループを回します。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/61f9027f-d2bf-8fcb-d15a-238c9152c410.png">

    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/b21e465a-480d-98e3-5ed5-46336b0e855e.png">

    もう一度読み取ったテキストデータを見てみましょう。
    「システム」から始まり「Amount（下から４つ目）」までいったところでループが止まり、その位置の変数iが保持されます。ほしいデータは"Meal"以降です。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/4be92d73-0c4a-d964-1ef8-ee91879fe871.png">
<br>

6. "Amount"の次の行から読み取るため変数iをインクリメントします。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/eb2e35e9-eef8-822a-900d-735706ca1827.png">  
<br>

7. WhileでCSVデータを格納するDataTableを構築します。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/b62959b5-a56c-3ee7-e8fd-83b8bc44b396.png">

    ダブルクリックすると以下のようになっています。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/702d753e-3118-1354-9b1a-bace0e256f3c.png">

    1つの経費アイテムはExpense Type, Description, Amountの3項目で構成されているので"Add Data Row"アクティビティで、
    ``{lines(i), lines(i+1), lines(i+2)}``
    をDataTable型のdtExpenses変数に追加していきます。1回目のループ、2回目のループではそれぞれ、

        Meal, Mexican Lunch, 12
        Meal, Italian Dinner, 45
        ...

    がDataTableに追加されます。

    ループ処理は"Total Expenses ($):"が出てくるまで3行単位で回ります。
<br>

8. "Write CSV"アクティビティででCSVファイルを出力します。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/ea8c8beb-6d13-8b63-5d83-fe39495a17d3.png">

    FilePathとして"Expenses.csv"が、入力DataTableとしてdtExpensesが指定されています。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/202992/937c97c1-bb91-262b-02c2-69503898e674.png">