Fragrammer’s Blog

破片プログラマーから脱する

仮想環境(Guest側OS)のWindowsUpdateの自動化検討 その1

仮想環境のGuestOSのWindowsUpdateの完全自動化を見据えて色々調べてみた。
まずは仮想環境は含めずにCUI環境から直接Updateする方法を探す。
Powershellを使った方法はwin7以降のみ対応となる。)

HostOS側からUI AutomationもしくはUWSCでGuestOSを起動
→GuestOSにログイン
→GuestOS起動時に上のいずれかのUpdate用スクリプトを起動
で自動化できそう。

ただ、ログイン部分が今のところ一番の難点。
リモートでログインすると、WindowsUpdateが実行出来ない場合もあるようなので、
この辺り要調査。


一応Vagrant、Jenkinsも調べたものの、
Vagrantは環境を作ったり(コピーしたり)壊したりが得意なことは解った。
osdn.jp
tech.nitoyon.com
GuestOSのログイン部分の制御に利用できる可能性があるので、要調査。(プラグイン等ありそう)
JenkinsはGuestOSをスレーブに設定すれば起動部分含めて制御できる可能性あり。
shunirr.hatenablog.jp
JenkinsのVirtualBoxプラグインや、Vagrantプラグインがある模様。

現在のところ、ログイン部分の対処としては、
1.JenkinsやVagrantを利用(まだ未確定)
2.UWSCの画像認識で力技で処理
の2つが候補。

業務レポートのTask部分のみ自動生成(VBA)

作業時間を記録しているExcelから、業務レポートの一部を自動生成。
・コメント欄
・他の項目もテンプレ部分も出力させる
辺りの改良の余地あり。

※作業時間を記録してるExcelは以下にアップロード
業務レポート用.xlsm - Google ドライブ

Option Base 1   ' 配列を1から
Const MaxTaskNum As Long = 42

Sub CreateTask()
    Dim i As Long
    Dim TaskListCell As Variant
    Dim TempFileNamePath As String
    Dim TaskList(MaxTaskNum, 3) As String
    
    Dim Str1, Str2, Str3 As String
    
    ' 出力ファイルパス
    TempFileNamePath = "C:\work\VBA\業務レポート作成\test.txt"
    Sheets("タスク種類").Activate
    ' タスク一覧取得
    TaskListCell = Range("A2").Resize(MaxTaskNum, 2)
    ' 出力ファイルを作成
    Set OutPutFileObj = CreateObject("Scripting.FileSystemObject"). _
                        CreateTextFile(TempFileNamePath)
    ' 出力ループ
    For i = 1 To MaxTaskNum
        ' Task名が無い場合は飛ばす
        If TaskListCell(i, 2) = "" Then GoTo Next_i
        ' Task名がある場合はTaskのセット作成
        Str1 = "・タスク名:" & TaskListCell(i, 2)
        Str2 = " 作業時間:" & TaskListCell(i, 1)
        Str3 = " コメント:" ' 未実装
        OutPutFileObj.WriteLine (Str1)
        OutPutFileObj.WriteLine (Str2)
        OutPutFileObj.WriteLine (Str3)
Next_i:
    Next i
    ' 後始末
    OutPutFileObj.Close
End Sub

出力結果

・タスク名:朝会
 作業時間:0.25
 コメント:
・タスク名:残業申請
 作業時間:0.25
 コメント:
・タスク名:TaskA
 作業時間:2.75
 コメント:
・タスク名:TaskB
 作業時間:2
 コメント:
・タスク名:TaskC
 作業時間:0.5
 コメント:

詰まった点としては、
出力結果が一部「0.249999999999999」のようになった。

Excel側でROUNDUPを使って小数点第3位まで丸めて解決。

string.Format()の"$"

string.Format()の"$"(文字列挿入)でちょっとハマったのでメモ。

// 1.とする
MessageBox.Show(string.Format("name: {0}, class: {1}", info.Name, info.ClassName));
// 2.とする
MessageBox.Show($"name: {info.Name}, class: {info.ClassName}");

1.の書き換え版が2.となるが、2.はC♯6.0(VS2015)以降対応。

メモ帳開いて勝手に保存(UI Automation)

コメント欄でアドバイスして下さったid:u338stevenさんのおかげで、
一気に保存まで達成。ありがとうございました。

前のUWSCでメモ帳保存のUI Automation版
1.メモ帳起動
2.「ああああああ」を書き込む
3.「テスト.txt」でカレントパスに保存

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Automation;
using System.Windows.Forms;

namespace UIAutoPractice
{
    class Program
    {
        private static readonly string PROCESS_NAME = @"notepad";
        private static readonly int DEFAULT_WAIT_TIME = 1000;

        private static AutomationElement mainForm;
        static void Main(string[] args)
        {
            Process process = Process.Start(PROCESS_NAME);
            try
            {
                // メモ帳を起動します
                Thread.Sleep(DEFAULT_WAIT_TIME);
                mainForm = AutomationElement.FromHandle(process.MainWindowHandle);
                // ああああああ
                string InputText = "ああああああ";
                // 入力準備
                object valuePattern = null;
                if (!mainForm.TryGetCurrentPattern(ValuePattern.Pattern, out valuePattern))
                {
                    mainForm.SetFocus();
                    Thread.Sleep(100);
                    SendKeys.SendWait(InputText);
                }
                else
                {
                    mainForm.SetFocus();
                    ((ValuePattern)valuePattern).SetValue(InputText);
                }
                // 閉じる
                if (mainForm.TryGetCurrentPattern(WindowPattern.Pattern, out valuePattern))
                {
                    mainForm.SetFocus();
                    ((WindowPattern)valuePattern).Close();
                }
                // フォーカス当たるまで待機
                Thread.Sleep(1000);
                // 保存するかどうか聞かれた時のダイアログを取得
                AutomationElement subForm = AutomationElement.FocusedElement;
                // 保存ボタン取得
                var btn = subForm.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
                // 保存実行
                btn.Invoke();
                // 再度保存するかどうか聞かれる場合があるため、若干待機させる
                Thread.Sleep(500);
                // 保存するかどうか聞かれた時のダイアログを取得
                var SaveTextBoxElem = AutomationElement.FocusedElement as AutomationElement;
                SaveTextBoxElem.SetFocus();
                // Test.txtでカレントパスに保存
                SendKeys.SendWait("Test");
                // Enterキー押下
                SendKeys.SendWait("{ENTER}");       
            }
            finally
            {
                process.CloseMainWindow();
            }
        }
    }
}

前回の続き

前回の問題点。
subFormが取れてない可能性があるため、
・Thread.Sleep(1000);で取得直前に待ってみた
 →×
・「AutomationElement」オブジェクトの存在有無を調べるメソッドを軽く調べた
 →見当たらない……。今回は15分程度しか探せないが、もう少し探せば見つかるかもしれない。
・AutomationElementクラスを調べていると、子要素を渡すCachedChildrenプロパティが見つかった
 mainFormに適用してみる事を考える
・出ていた例外のメッセージは"ハンドルされていない例外: System.InvalidOperationException: シーケンスに要素が含まれていません"

途中経過

メモ帳での保存時のダイアログの扱いで苦戦中。
AutomationElement.FocusedElementでフォーカスされたダイアログのAutomationElement を取得しようとするも、例外発生して終了。
・AutomationElementの取得有無未確認
・ダイアログや子Windowの扱いについて、まだ未整理

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Automation;
using System.Windows.Forms;

namespace UIAutoPractice
{
    class Program
    {
        private static readonly string PROCESS_NAME = @"notepad";
        private static readonly int DEFAULT_WAIT_TIME = 1000;

        private static AutomationElement mainForm;
        static void Main(string[] args)
        {

            Process process = Process.Start(PROCESS_NAME);
            try
            {
                // メモ帳を起動します
                Thread.Sleep(DEFAULT_WAIT_TIME);
                mainForm = AutomationElement.FromHandle(process.MainWindowHandle);
                // あああああ
                string InputText = "あああああ";
                // 入力準備
                object valuePattern = null;
                if (!mainForm.TryGetCurrentPattern(ValuePattern.Pattern, out valuePattern))
                {
                    mainForm.SetFocus();
                    Thread.Sleep(100);
                    SendKeys.SendWait(InputText);
                }
                else
                {
                    mainForm.SetFocus();
                    ((ValuePattern)valuePattern).SetValue(InputText);
                }
                // 閉じる
                if (!mainForm.TryGetCurrentPattern(WindowPattern.Pattern, out valuePattern))
                {
                    //mainForm.SetFocus();
                    //Thread.Sleep(100);
                    //SendKeys.SendWait(InputText);
                }
                else
                {
                    mainForm.SetFocus();
                    ((WindowPattern)valuePattern).Close();
                }

                // 保存するかどうか聞かれた時のダイアログを取得
                AutomationElement subForm = AutomationElement.FocusedElement;
                // 保存ボタン取得
                var btn = FindButtonsByName(subForm, "キャンセル").First().GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
                // 保存実行
                btn.Invoke();
            
            }
            finally
            {
                process.CloseMainWindow();
            }
        }

        // 指定したID属性に一致するAutomationElementを返します
        private static AutomationElement FindElementById(AutomationElement rootElement, string automationId)
        {
            return rootElement.FindFirst(
                TreeScope.Element | TreeScope.Descendants,
                new PropertyCondition(AutomationElement.AutomationIdProperty, automationId));
        }

        // 指定したName属性に一致するAutomationElementをすべて返します
        private static IEnumerable<AutomationElement> FindElementsByName(AutomationElement rootElement, string name)
        {
            return rootElement.FindAll(
                TreeScope.Element | TreeScope.Descendants,
                new PropertyCondition(AutomationElement.NameProperty, name))
                .Cast<AutomationElement>();
        }

        // 指定したName属性に一致するボタン要素をすべて返します
        private static IEnumerable<AutomationElement> FindButtonsByName(AutomationElement rootElement, string name)
        {
            const string BUTTON_CLASS_NAME = "Button";
            return from x in FindElementsByName(rootElement, name)
                   where x.Current.ClassName == BUTTON_CLASS_NAME
                   select x;
        }

    }
}

メモ帳起動して文字入力(UI Automation)

UI Automation使ってC#でメモ帳起動~文字入力まで。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Automation;
using System.Windows.Forms;

namespace UIAutoPractice
{
    class Program
    {
        private static readonly string PROCESS_NAME = @"notepad";
        private static readonly int DEFAULT_WAIT_TIME = 1000;

        private static AutomationElement mainForm;
        static void Main(string[] args)
        {

            Process process = Process.Start(PROCESS_NAME);
            try
            {
                // メモ帳を起動します
                Thread.Sleep(DEFAULT_WAIT_TIME);
                mainForm = AutomationElement.FromHandle(process.MainWindowHandle);
                // あああああ
                string InputText = "あああああ";
                // 入力準備
                object valuePattern = null;
                if (!mainForm.TryGetCurrentPattern(ValuePattern.Pattern, out valuePattern))
                {
                    mainForm.SetFocus();
                    Thread.Sleep(100);
                    SendKeys.SendWait(InputText);
                }
                else
                {
                    mainForm.SetFocus();
                    ((ValuePattern)valuePattern).SetValue(InputText);
                }
            
            }
            finally
            {
                process.CloseMainWindow();
            }
        }

    }
}