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