仮想環境(Guest側OS)のWindowsUpdateの自動化検討 その1
仮想環境のGuestOSのWindowsUpdateの完全自動化を見据えて色々調べてみた。
まずは仮想環境は含めずにCUI環境から直接Updateする方法を探す。
(Powershellを使った方法はwin7以降のみ対応となる。)
- PowerShellでWindows Updateが有効かどうかと最終インストール日時を確認する - Qiita
- Microsoft.Update.AutoUpdateのCOMオブジェクトをPowerShellで。
- フルオートでWindows Updateする
- PowerShellを使って、新規作成直後の再起動の繰り返しに対応。
- WSUS の手動操作のメモ at SE の雑記
- Windows Update Services (WSUS)をコマンドラインから利用
- 山市良のえぬなんとかわーるど: Hyper-V & Windows Update 自動化スクリプトのまとめ (+α)
- 仮想環境(GuestOS)のWindowsUpdateについてまとめ。ただし、Hyper-V。
- 【メモ】PSWindowsUpdate -コマンドラインでWindows Update- (Windows Server 2012 R2 Essentials):shirokichi's hobby life:So-netブログ
- PowerShellのPSWindowsUpdateモジュールを使用したやり方。
(「正規のGUIで行うアップデートとのあいだで不整合が発生する」というのが気にかかるが……)
- PowerShellのPSWindowsUpdateモジュールを使用したやり方。
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(); } } } }