Power Automate Desktopがなんとなく使える講座 #6 Excelでキーの送信を使う

　第4回で作成したエクセル表ですが、このままだと見栄えが悪いので、元の表から書式を引き継ぎしたいですよね。書式だけのコピーは、Power Automate Desktopのアクションでは定義されていませんので、Excel上でキー操作を行って実現したいと思います。

# 項目名の書式と値のコピーと貼り付け
　Power Automate DesktopのExcelアクションの中で「Excelワークシートからセルをコピー」と「Excelワークシートにセルを貼り付け」を使用すると、クリップボード経由でデータと書式をコピーすることができます。前回のフローのステップ8とステップ11を削除して「Excelワークシートからセルをコピー」と「Excelワークシートにセルを貼り付け」を登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/2d9fa2b8-2d9a-96c3-1410-6124d24bec87.png)

# データ部分の書式の貼り付け
###ウィンドウにフォーカスする
　データ部分は、仕分け完了後に書式だけを元の表からコピーしていきます。
　ただし、Excel上でキー操作を行うには、Excelをアクティブにしてやる必要があります。Power Automate Desktopは、そのままだと自分がアクティブな状態で、Excelは背後で動作しています。アクションペインから「UIオートメーション」の「Windows」を開いて**「ウインドウにフォーカスする」**を選択します。
　ウィンドウの検索モードは、「ウィンドウのインスタンス／ハンドルごと」を選択します。インスタンスという言葉、以前ありましたよね。Excelの起動で作成された変数 **ExcelInstance** の事です。第2ステップで、この変数を使用するときは、マウス操作で選択できたのですが、ここではそのような操作ができないので、直接以下の文字を入力します。

```
%ExcelInstance%
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/7cdd49c6-26c3-ede2-a826-1294401abb3a.png)

　この後、シートごとに書式を貼り付けていくのですが、それぞれのシートを選択するには、最初に使った血液型のリスト **ColumnAsList** を **For each** でループさせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/38abb719-ab73-6d7d-a1b5-1274036925aa.png)

### 元の表のデータ部分をコピーし、張り付ける先を選択
　ループの中で、コピー元のシートをアクティブにし、書式の元になる2行目をコピーし、コピー先のシートをアクティブにして、書式を貼り付ける範囲を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/1017fe3b-f051-444f-6739-bcb3c46f49f5.png)
　エクセル上では、以下のように、データ部分が選択された状態になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/ebb6964a-e42a-55a1-e594-f422675b92d1.png)

### キーの送信
　Power Automate Desktopに限らず、RPAでアプリを操作する処理を書くときにとても大切なことがあります。**目的の操作がショートカットキーに割り当てられていないか確認しましょう**。メニューを操作していくより、遥かに早く、確実に処理を行うことができます。[**「Microsoftサポート Excelのショートカットキー」**](https://support.microsoft.com/ja-jp/topic/excel-%E3%81%AE%E3%82%B7%E3%83%A7%E3%83%BC%E3%83%88%E3%82%AB%E3%83%83%E3%83%88-%E3%82%AD%E3%83%BC-1798d9d5-842a-42b8-9c99-9b7213f0040f)を眺めてみてください。普段使っているものから、今まで使ったことなかったものまで多数のショートカットキーがあります。
　動画で見たい方は、Ｙoutubeチャンネルで「[Excel Office HARU　【Excel講座】★脱マウス★よく使うショートカットキー総集編 ~2021年も生産性爆上がり！~](https://www.youtube.com/watch?v=Xk-iM9huPWE&list=WL&index=25)」がお勧めです。
　書式の貼り付けは、コントロールキーとAltキーと「V」を押して、形式を指定して貼り付けのダイアログを表示させ、張り付け方の右に書かれたアルファベットを入力してエンターキーを押すと、早く処理できます。
　Power Automate Desktopでは、以下のような書き方をします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/54b4c64e-0541-811a-a549-62b6feec77f8.png)
　通常のテキスト入力であれば、送信するテキストの項目にテキストをそのまま書き込めばよいのですが、ショートカットキーを送信したい場合は枠の下にある「特殊キーの挿入」や「修飾キーの挿入」を開いて選択します。同時に押す場合は **{Control}({V})** のような書き方をしますが、今回はAltキーも同時に押す必要があります。その場合は、()内に**{ALT}()** と入れ子にしていきます。ControlとAltとVキーを同時に押す場合は以下になります。

```
{Control}({Alt}({V}))
```
　このキー送信で以下のダイアログが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/ee8ecc1b-78b6-e211-f735-39bc6fc0c092.png)
　Tキーを押して貼り付ける形式で「書式」を選択しエンターキーでOKを押したことにするので、以下のようになります。

```
{Control}({Alt}({V}))T{Return}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/e125be41-6bac-c4d7-f0c1-3bac532cba15.png)
　書式の貼り付けだけでは列の幅は標準のままなので、もう一度「列幅(W)」を選んで貼り付けをしてやります。

```
{Control}({Alt}({V}))W{Return}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/b956f2f4-2035-4146-879a-9445115999b7.png)
　これで、エクセルシートは以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/4b58f05b-ba53-ed5b-735c-a7e25bbfd137.png)

　きちんと書式を含めてコピーされました．．．！！！
　生年月日がおかしいのに気が付いてしまいましたね。もとの表は、きちんと 1981/12/16 のような形になっているのに、コピーされたデータは 29936 のような数字になってしまっています。ここも日付形式にしてやればよいのですが、先ほどの作業では書式を持ってくることができなかったようです。
　こういう場合は、その列だけ書式の変更をしてやります。面倒そうですが、シリアル値から日付に書式変換するショートカットキーがあります。ControlとShiftと3の3つのキーを押してやれば日付に変わります。前と同様に送信するテキストで {Control}({Shift}({3})) としてやればいいかというと、うまくいきません。送信するテキストの書き方は、ちょっと癖があります。[**Microsoft Docs キーの送信**](https://docs.microsoft.com/ja-jp/power-automate/desktop-flows/actions-reference/mouseandkeyboard#sendkeys)に説明があります。ちょっと見ではわかりにくい表ですが、数字キーは **D3** を使います。今回送信するテキストは、以下のようになります。

```
{Control}({Shift}({D3}))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/f974d8fb-31e1-7852-e366-d0ff36e89a14.png)

　ショートカットで設定できる書式は、以下のようになっています。

|書式|ショートカット|記述方法|
|---|---|---|
|太字|[Ctrl] + 2|{Control}({D2})|
|斜体|[Ctrl] + 3|{Control}({D3})|
|下線|[Ctrl] + 4|{Control}({D4})|
|取り消し線|[Ctrl] + 5|{Control}({D5})|
|カンマ区切り|[Ctrl] + [Shift] + 1 |{Control}({Shift}({D1}))|
|日付|[Ctrl] + [Shift] + 3|{Control}({Shift}({D3}))|
|円マーク|[Ctrl] + [Shift] + 4|{Control}({Shift}({D4}))|
|パーセント|[Ctrl] + [Shift] + 5 |{Control}({Shift}({D5}))|
|外枠|[Ctrl] + [Shift] + 6|{Control}({Shift}({D6}))|
|標準書式|[Ctrl] + [Shift] + ^|{Control}({Shift}({OemQuotes}))|

# RPAのキモ
### 操作内容、環境状況によるタイミング調整が必要
　アプリのUI操作やWeb関連の操作、キーの送信などでは、人間が操作することを想定してテストされているため、RPAで処理を行うと、操作速度が速すぎて不具合が発生する場合があります。場合によっては、サーバダウンなどの大問題が発生することさえあります。画面の展開や入力操作の間隔など、PCによってRPAの動作する速度は若干変わりますし、インターネットを使用する作業の場合は時間帯によって通信速度が変わったりします。上手に遅延条件を挟んでやる必要があります。
　今回のExcelの処理では、私の環境ではキー送信を若干遅くしてやらないと度々エラーが発生してしまいました。そのため、キー送信の**「キー入力の間隔の遅延」**項目が標準で10ミリ秒になっているところを**100ミリ秒**に変更しました。PCの処理速度によっては、それ以上の数値にする必要があるかもしれません。
### フロー実行中はPC操作しない
　フローの中で、今回のようにアプリ画面をフォーカスしてキーの送信を行うような処理が入っている場合、実行中に別のアプリを選択してしまうと、そちらのアプリに対してキー送信が行われてしまい、問題が発生します。
　この講義で＃１から＃５までで作成したフローは、そのような処理が入っていないため、別のアプリを操作したり、別のフローを並行して実行しても問題はありません。できればそのような作り方をするのが望ましいのですが、並行処理化可能なフローなのか、他の作業をしてはいけないフローなのか明確にしておくか、全てのフロー実行時にはPC操作はしないということを決めておくかしておいた方がよいでしょう。

# 実働
![motion.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/2bfeaa81-ec2e-48a3-078f-7e7a37e565b7.gif)
　下記の通り、前回のエラー処理も含めたフローにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/d1275889-2e9f-0597-f5ec-4b40d363f577.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/424064cd-b7b4-42f9-82b7-bc1c95b40101.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/0073f96b-6137-5ef2-003b-f5727f4ebdbe.png)
コードは、以下のようになってます。

```
Excel.LaunchAndOpen Path: $'''D:\\User\\Downloads\\personal_information.xlsx''' Visible: True ReadOnly: False LoadAddInsAndMacros: False Instance=> ExcelInstance
Excel.Advanced.GetAllWorksheets Instance: ExcelInstance Worksheets=> SheetNames
Excel.ActivateWorksheetByName Instance: ExcelInstance Name: SheetNames[0]
Excel.GetFirstFreeColumnRow Instance: ExcelInstance FirstFreeColumn=> FirstFreeColumn FirstFreeRow=> FirstFreeRow
Excel.ReadCells Instance: ExcelInstance StartColumn: 1 StartRow: 1 EndColumn: FirstFreeColumn - 1 EndRow: FirstFreeRow - 1 ReadAsText: False FirstLineIsHeader: True RangeValue=> ExcelData
Variables.RetrieveDataTableColumnIntoList DataTable: ExcelData ColumnNameOrIndex: $'''血液型''' ColumnAsList=> ColumnAsList
Variables.RemoveDuplicateItemsFromList List: ColumnAsList IgnoreCase: False NewList=> ColumnAsList
Excel.Advanced.CopyCells Instance: ExcelInstance StartColumn: 1 StartRow: 1 EndColumn: FirstFreeColumn - 1 EndRow: 1
LOOP FOREACH CurrentItem IN ColumnAsList
    Excel.AddWorksheet Instance: ExcelInstance Name: CurrentItem WorksheetPosition: Excel.WorksheetPosition.Last
    Excel.Advanced.PasteAt Instance: ExcelInstance Column: 1 Row: 1
    Excel.Advanced.ActivateCell Instance: ExcelInstance Column: 1 Row: 2
END
UIAutomation.Windows.SetVisibilityByInstanceOrHandle WindowInstance: ExcelInstance Visibility: UIAutomation.Visibility.Hidden
SET ErrorName TO $'''null'''
LOOP FOREACH CurrentItem2 IN ExcelData
    Excel.ActivateWorksheetByName Instance: ExcelInstance Name: CurrentItem2['血液型']
    ON ERROR WorksheetNotFoundError
        SET ErrorName TO $'''ワークシートが見つかりません'''
        GOTO ShowExcelbook
    ON ERROR ActivateWorksheetError
        SET ErrorName TO $'''ワークシートをアクティブ化できませんでした'''
        GOTO ShowExcelbook
    END
    Excel.Write Instance: ExcelInstance Value: CurrentItem2
    ON ERROR WriteToExcelError
        SET ErrorName TO $'''Excelに値を書き込めませんでした'''
        GOTO ShowExcelbook
    END
    Excel.Advanced.ActivateCellRelativeTo Instance: ExcelInstance Direction: Excel.TwoDimensionalDirection.Below OffsetFromActiveCell: 1
    ON ERROR ActivateExcelCellError
        SET ErrorName TO $'''セルをアクティブ化できませんでした'''
        GOTO ShowExcelbook
    END
END
LABEL ShowExcelbook
UIAutomation.Windows.SetVisibilityByInstanceOrHandle WindowInstance: ExcelInstance Visibility: UIAutomation.Visibility.Visible
IF ErrorName <> $'''null''' THEN
    EXIT Code: 0 ErrorMessage: ErrorName
END
UIAutomation.Windows.FocusByInstanceOrHandle WindowInstance: ExcelInstance
LOOP FOREACH CurrentItem3 IN ColumnAsList
    Excel.ActivateWorksheetByName Instance: ExcelInstance Name: SheetNames[0]
    Excel.Advanced.CopyCells Instance: ExcelInstance StartColumn: 1 StartRow: 2 EndColumn: FirstFreeColumn - 1 EndRow: 2
    Excel.ActivateWorksheetByName Instance: ExcelInstance Name: CurrentItem3
    Excel.GetFirstFreeColumnRow Instance: ExcelInstance FirstFreeColumn=> FirstFreeColumn2 FirstFreeRow=> FirstFreeRow2
    Excel.Advanced.SelectCells Instance: ExcelInstance StartColumn: 1 StartRow: 2 EndColumn: FirstFreeColumn2 - 1 EndRow: FirstFreeRow2 - 1
    MouseAndKeyboard.SendKeys TextToSend: $'''{Control}({Alt}({V}))T{Return}''' DelayBetweenKeystrokes: 100 SendTextAsHardwareKeys: False
    MouseAndKeyboard.SendKeys TextToSend: $'''{Control}({Alt}({V}))W{Return}''' DelayBetweenKeystrokes: 100 SendTextAsHardwareKeys: False
    Excel.Advanced.SelectCells Instance: ExcelInstance StartColumn: 7 StartRow: 2 EndColumn: 7 EndRow: FirstFreeRow2 - 1
    MouseAndKeyboard.SendKeys TextToSend: $'''{Control}({Shift}({D3}))''' DelayBetweenKeystrokes: 100 SendTextAsHardwareKeys: False
END
```