PowerShellでVisualStudioを操作する

Table of Content

PowerShellでVisualStudioを操作する

VisualStudioをスクリプトで操作したい場合がたまにあります。たとえば、ソースコード中の関数の一覧を列挙したり、コードを自動生成する場合などです。

現VisualStudioのマクロの制限

以下の記事で紹介している復活したVisualStudioのマクロはJScriptからVisualStudio.DTEを利用して、VisualStudioを操作しています。

復活のVisualStudioのマクロ
https://qiita.com/mima_ita/items/c56d6e2657cc836af7ac

じつは旧マクロで実行できたことが、新しいマクロでは実行できないケースがあります。たとえばCodeElementで取得した関数のパラメータの列挙はかってのVisualStudioのマクロでは簡単におこなえましたが、新VisualStudioのマクロでは実行できません。

これはJScriptではインターフェイスの明示がおこなえないためと考えられます。

C#からのVisualStudioの操作

C#からのVisualStudioの操作は容易に行えます。
これを行うには参照の追加からアセンブリ→拡張をえらびEnvDTEXXXを追加する必要があります。

EnvDTEのバージョンはわかりにくいですが、EnvDTE80はEnvDTEに足りない機能を拡張したもの、EnvDTE90はEvnDTE+EnvDTE80に足りないものを拡張したものとなっています。つまり、EnvDTEを参照したあとに必要に応じて拡張されたものを参照することになります。

以下のコードは起動しているVisualStudio2019のソリューションから関数の一覧を表示するサンプルです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace VSTest
{
    class Program
    {
        static void EnumCodeElements(EnvDTE.CodeElements elems)
        {
            foreach (EnvDTE.CodeElement elem in elems)
            {
                if (elem.Kind == EnvDTE.vsCMElement.vsCMElementFunction)
                {
                    EnvDTE.CodeFunction func = elem as EnvDTE.CodeFunction;
                    Console.WriteLine("{0} {1}", func.Type.AsFullName, func.FullName);
                    foreach(EnvDTE.CodeParameter param in func.Parameters)
                    {
                        Console.WriteLine("  {0} {1}", param.Type.AsFullName, param.FullName);
                    }
                }
                EnumCodeElements(elem.Children);
            }

        }

        static void EnumProjectItems(EnvDTE.ProjectItems items)
        {
            foreach (EnvDTE.ProjectItem item in items)
            {
                Console.WriteLine(item.Name);
                EnumProjectItems(item.ProjectItems);
                if (item.FileCodeModel != null)
                {
                    EnumCodeElements(item.FileCodeModel.CodeElements);
                }
            }

        }

        static void Main(string[] args)
        {
            EnvDTE.DTE dte = (EnvDTE.DTE)Marshal.GetActiveObject("VisualStudio.DTE.16.0");
            foreach (EnvDTE.Project prj in dte.Solution.Projects)
            {
                Console.WriteLine(prj.Name);
                EnumProjectItems(prj.ProjectItems);
            }

        }

    }
}

注意すべき点として、dynamicなどを使用してインターフェイスを明示しない場合、正常に動作しない箇所があるということに注意してください。

PowerShellからのVisualStudioの操作

さてPowerShellから同様の操作した場合、インターフェイスを明示するところで悩むことになります。
これはCOM経由でDTEを操作しているため、PowerShell上ではSystem.__ComObject型として扱われるため、適切なDTEのインターフェイスにキャストできないためです。

ではどうしたらPowerShellからVisualStudioを操作できるのでしょうか。

パッケージマネージャーコンソールでの操作

実はVisualStudioについているパッケージマネージャーコンソールからは簡単にVisualStudioの自動操作が行えます。
これはNugetがPowershellを使用してプロジェクト情報の更新を行っているためと考えられます。

パッケージマネージャーコンソールでは変数$dteにVisualStudioを操作するためのVisualStudio.DTE.16.0がすでに読み込まれており、また、Get-Interfaceコマンドレットを使用してインターフェイスを明示することが可能になっています。

Get-Interfaceの使用例

PM>  Get-Interface $dte.ActiveDocument.ProjectItem.FileCodeModel ([ENVDTE80.FileCodeModel2])

DTE          : EnvDTE.DTEClass
Parent       : System.__ComObject
Language     : {B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}
CodeElements : {System.__ComObject, System.__ComObject, System.__ComObject, System.__ComObject...}
ParseStatus  : vsCMParseStatusComplete
IsBatchOpen  : False

Can't access FileCodeModel from Powershell console in Visual Studio 2017 DTE
https://stackoverflow.com/questions/45237281/cant-access-filecodemodel-from-powershell-console-in-visual-studio-2017-dte

このGet-Interfaceはパッケージマネージャーコンソール内だけで定義されているコマンドであり、その内容は以下の通りです。

PM> (Get-Command Get-Interface).Definition 

    Param(
        $Object,
        [type]$InterfaceType
    )

    [NuGetConsole.Host.PowerShell.Implementation.PSTypeWrapper]::GetInterface($Object, $InterfaceType)

NuGetConsoleはNuGetConsole.Host.PowerShell.dll内で定義されているため、これを利用することにより通常のPowerShellからもDTEのインターフェイスを明示することが可能になります。

PowerShellからVisualStudioを操作する例

では、通常のPowerShellからVisualStudioを操作する方法を考えてみます。

下記の例ではNuGetConsole.Host.PowerShell.dllを読み込んだあとDTEを利用してVisualStudioのプロジェクトから関数を取得しています。また、必要におうじて、GetInterfaceを用いてインターフェイスを明示しています。

function EnumCodeElements($elems, $level) {
    $space = " " * $level
    foreach($elem in $elems) {
        If ($elem.Kind -eq 2) { # vsCMElement.vsCMElementFunction
            # Access:
            # https://docs.microsoft.com/en-us/dotnet/api/envdte.vscmaccess?view=visualstudiosdk-2017
            Write-Host " " $space  $elem.Access $elem.Type.AsFullName   $elem.FullName
            foreach($param in $elem.Parameters) {
                Write-Host "    " $space  $param.Type.AsFullName $param.Name
            }
        }
        If ($elem.Children) {
            EnumCodeElements $elem.Children ($level+1)
        }
    }
}

function EnumProjectItem($items, $level) {
    foreach($item in $items) {
        $item = [NuGetConsole.Host.PowerShell.Implementation.PSTypeWrapper]::GetInterface($item, [ENVDTE.ProjectItem]) 
        $space = " " * $level
        Write-Host $space $item.Name
        if ($item.FileCodeModel) {
            $fileCodeModel = [NuGetConsole.Host.PowerShell.Implementation.PSTypeWrapper]::GetInterface($item.FileCodeModel, [ENVDTE.FileCodeModel])
            $fileCodeModel.Language
            EnumCodeElements $fileCodeModel.CodeElements $level
        }
        If ($item.ProjectItems) {
            EnumProjectItem $item.ProjectItems  ($level+1)
        }
    }
}

[Reflection.Assembly]::LoadFile("C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\Common7\IDE\CommonExtensions\Microsoft\NuGet\NuGetConsole.Host.PowerShell.dll") > $null

If (-not $dte) {
    $dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.16.0")
}
foreach($project in $dte.Solution.Projects) {
  Write-Host "Project:" $project.Name
  EnumProjectItem $project.ProjectItems  0
}

まとめ

今回はVisualStudioをPowerShellで操作する方法を検証しました。
これを利用することでクラスの情報を取得したり、クラスを自動作成したりすることができると考えられます。
ただし、VisualStudioのDTEでFileCodeModelとして認識できるファイルはC#,VB.NET,C++とかぎられており、JavaScriptなどは認識できません。

CodeModelLanguageConstants Class
https://docs.microsoft.com/en-us/dotnet/api/envdte.codemodellanguageconstants?view=visualstudiosdk-2017