ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

VisualStudio2015を外部から操作する

前回からの流れですね。
インプロセスで動くCOMを使っているアプリであれば、Friendlyを使ってまるでアウトプロセスCOMのように簡単に外から操作できるという話でした。で、VisualStudioには外部仕様として公開されているCOMがあるのです。

あ、この内容はめとべや大阪#31 - めとべや大阪 | DoorkeeperでLTしてきました。

DTE2

VisualStudioは拡張機能を作ることができます。そして拡張機能はもちろんVisualStudioを操作できる必要があるので、そのためにDTE2というCOMオブジェクトが提供されているのです。これを使いましょう。

テストプロジェクト作成

テストプロジェクトである必要はないのですが、お手軽なので。作成したら、参照にEnvDTEとEnvDTE80を設定します。
f:id:ishikawa-tatsuya:20150726091948p:plain
で、NuGetからFriendkly.WindowsとFriendly.PinInterfaceを取得します。
f:id:ishikawa-tatsuya:20150726091951p:plain

コード

こんな感じです。Friendly.PinInterfaceのおかげでインテリセンスが効きます。それから、このコードはVS2013にもそのまま使えます。まだ2015持ってない人は途中のpathを2013用に書き換えてくださいね。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Codeer.Friendly;
using Codeer.Friendly.Dynamic;
using Codeer.Friendly.Windows;
using EnvDTE;
using VSHTC.Friendly.PinInterface;
using EnvDTE80;
using System.IO;
using Codeer.Friendly.DotNetExecutor;

namespace VsDte
{
    [TestClass]
    public class Demo
    {
        string SolutionDir = Path.GetFullPath("TestDir");
        System.Diagnostics.Process vsProcess;

        [TestInitialize]
        public void TestInitialize()
        {
            //ソリューション作成用のディレクトリを作成
            while (Directory.Exists(SolutionDir))
            {
                try
                {
                    Directory.Delete(SolutionDir, true);
                    break;
                }
                catch { }
                System.Threading.Thread.Sleep(10);
            }
            Directory.CreateDirectory(SolutionDir);

            //VS起動
            //パスを書き換えると2013も操作できるよ。
            var path = @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe";
            vsProcess = System.Diagnostics.Process.Start(path);
            while (0 == vsProcess.MainWindowTitle.Length) 
            {
                System.Threading.Thread.Sleep(10);
                vsProcess = System.Diagnostics.Process.GetProcessById(vsProcess.Id);
            }
        }

        [TestCleanup]
        public void TestCleanup() 
        {
            vsProcess.Kill();
        }

        static Type DTEType { get { return typeof(_DTE); } }

        [TestMethod]
        public void Test()
        {
            //アタッチ
            WindowsAppFriend app = new WindowsAppFriend(vsProcess);

            //DLLインジェクション
            WindowsAppExpander.LoadAssembly(app, GetType().Assembly);

            //Microsoft.VisualStudio.Shell.Package.GetGlobalServiceの呼び出し
            var dteType = app.Type(GetType()).DTEType;
            AppVar obj = app.Type().Microsoft.VisualStudio.Shell.Package.GetGlobalService(dteType);

            //注目!
            //インターフェイスでプロキシが作成できる!
            var dte = obj.Pin<DTE2>();
            var solution = dte.Solution;

            //ソリューション作成
            solution.Create(SolutionDir, "Test.sln");
            string solutionPath = Path.Combine(SolutionDir, "Test.sln");
            solution.SaveAs(solutionPath);

            //プロジェクト追加
            solution.AddFromTemplate(
                @"C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ProjectTemplates\CSharp\Windows\1041\WPFApplication\csWPFApplication.vstemplate",
                Path.Combine(SolutionDir, "WPF"), "WPF", true);

            //保存
            solution.SaveAs(solutionPath);

            //閉じる
            solution.Close();

            //再度開く
            solution.Open(solutionPath);

            //クリーン→ビルド
            solution.SolutionBuild.Clean(true);
            solution.SolutionBuild.Build(true);

            //デバッグ
            solution.SolutionBuild.Debug();

            //デバッグ対象プロセスを操作
            var debProcess = System.Diagnostics.Process.GetProcessById(dte.Debugger.DebuggedProcesses.Item(1).ProcessID);
            using (var debApp = new WindowsAppFriend(debProcess)) 
            {
                debApp.Type().System.Windows.Application.Current.MainWindow.Close(new Async());
            }

            //編集モードに戻るまで待つ
            while (dte.Debugger.CurrentMode != dbgDebugMode.dbgDesignMode) 
            {
                System.Threading.Thread.Sleep(10);
            }
        }
    }
}

このソースコードはこちらからダウンロードできます。

Ishikawa-Tatsuya/Sample_VS_DTE · GitHub

COM操作アイデア募集中

COMとFriendly.PinInterfaceの相性の良さは異常w
と言うことで、他に「このアプリも内部にCOM持ってるから簡単に操作できるよ」みたいなアイデアがある人はブログとか書いてくれたら嬉しいなーw。