展開用のカスタムMSBuildタスクの作成(WMIを含む)

こんにちは ある晴れた日、MSBuild展開プロジェクトは新しい環境で動作したくないことがわかりました。サイトとプールを作成および管理するために、MSBuild.ExtensionPackを使用しました。 DCOMの利用不能に関連するエラーが減少しました。 環境を変更することは不可能だったため、MSBuildの独自のタスクmsdn.microsoft.com/en-us/library/t9883dzc.aspxを作成する機会があったため、WMIを介して機能する独自のタスクを作成することになりました(環境で利用可能)何が起こった、猫の下でお願いします。



MSBuildとWMIを選ぶ理由



ポートを開いて必要に応じて設定する権限がない環境があります。 ただし、この環境では、WMIがネットワーク全体で機能するようにすべてが既に構成されているため、WMIを使用する決定は最も簡単でした。

MSBuildは最初から単純なサイトを展開するために使用されたため、展開全体をNantに書き換えず、既存のスクリプトを使用して、機能しないタスクのみを置き換えることが選択されました。



MSBuildのカスタムタスクを作成する方法



Microsoft.Build.Framework、Microsoft.Build.Tasks.v4.0、およびMicrosoft.Build.Utilities.v4.0をアセンブリプロジェクトに接続します。 現在、2つの選択肢があります。

1- ITaskインターフェイスを継承してから、 一連のメソッドとプロパティ自体を再定義します。

2-抽象クラスTaskを継承し、Executeメソッドのみを再定義します。

ご想像のとおり、2番目の方法が選択されました。

独自のタスクのためのHelloWorld:

using System; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace MyTasks { public class SimpleTask : Task { public override bool Execute() { Log.LogMessage("Hello Habrahabr"); return true; } } }
      
      





Executeメソッドは、タスクが正常に完了した場合はtrueを返し、そうでない場合はfalseを返します。 Taskクラスで使用できる便利なプロパティのうち、ユーザーの操作を維持できるLogプロパティに注目する価値があります。

パラメータも簡単に渡されます。このクラスでオープンプロパティを定義するだけで十分です(オープンゲッターとセッターを使用)。

 using System; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace MyTasks { public class SimpleTask : Task { public string AppPoolName { get; set; } [Output] public bool Exists { get; set; } public override bool Execute() { Log.LogMessage("Hello Habrahabr"); return true; } } }
      
      





タスクが何かを返すためには、プロパティに[Output]属性を追加する必要があります。

したがって、記述しやすさもこのソリューションのプラスであったと言えます。 WMIを使用してIISを管理する方法については説明しませんが、Windowsコンポーネント「IIS Management Scripts and Tools」と共にインストールされる名前空間WebAdministrationを使用することに注意してください。

ネタバレの下、基本タスクのリスト。WMI接続ロジックと基本タスクパラメーターは次のようにカプセル化されます。

  1. マシン-リモートマシンまたはローカルホストの名前
  2. UserName-WMIに接続するユーザー名
  3. パスワード-WMIに接続するためのユーザーパスワード
  4. TaskAction-アクション自体の名前(作成、停止、開始、CheckExists)


しゅう
 using Microsoft.Build.Utilities; using System; using System.Collections.Generic; using System.Linq; using System.Management; using System.Text; using System.Threading; namespace MSBuild.WMI { /// <summary> /// This class will be used as a base class for all WMI MSBuild tasks. /// Contains logic for basic WMI operations as well as some basic properties (connection information, actual task action). /// </summary> public abstract class BaseWMITask : Task { #region Private Fields private ManagementScope _scope; #endregion #region Public Properties (Task Parameters) /// <summary> /// IP or host name of remote machine or "localhost" /// If not set - treated as "localhost" /// </summary> public string Machine { get; set; } /// <summary> /// Username for connecting to remote machine /// </summary> public string UserName { get; set; } /// <summary> /// Password for connecting to remote machine /// </summary> public string Password { get; set; } /// <summary> /// Specific action to be executed (Start, Stop, etc.) /// </summary> public string TaskAction { get; set; } #endregion #region Protected Members /// <summary> /// Gets WMI ManagementScope object /// </summary> protected ManagementScope WMIScope { get { if (_scope != null) return _scope; var wmiScopePath = string.Format(@"\\{0}\root\WebAdministration", Machine); //we should pass user as HOST\\USER var wmiUserName = UserName; if (wmiUserName != null && !wmiUserName.Contains("\\")) wmiUserName = string.Concat(Machine, "\\", UserName); var wmiConnectionOptions = new ConnectionOptions() { Username = wmiUserName, Password = Password, Impersonation = ImpersonationLevel.Impersonate, Authentication = AuthenticationLevel.PacketPrivacy, EnablePrivileges = true }; //use current user if this is a local machine if (Helpers.IsLocalHost(Machine)) { wmiConnectionOptions.Username = null; wmiConnectionOptions.Password = null; } _scope = new ManagementScope(wmiScopePath, wmiConnectionOptions); _scope.Connect(); return _scope; } } /// <summary> /// Gets task action /// </summary> protected TaskAction Action { get { return (WMI.TaskAction)Enum.Parse(typeof(WMI.TaskAction), TaskAction, true); } } /// <summary> /// Gets ManagementObject by query /// </summary> /// <param name="queryString">String WQL query</param> /// <returns>ManagementObject or null if it was not found</returns> protected ManagementObject GetObjectByQuery(string queryString) { var query = new ObjectQuery(queryString); using (var mos = new ManagementObjectSearcher(WMIScope, query)) { return mos.Get().Cast<ManagementObject>().FirstOrDefault(); } } /// <summary> /// Wait till the condition returns True /// </summary> /// <param name="condition">Condition to be checked</param> protected void WaitTill(Func<bool> condition) { while (!condition()) { Thread.Sleep(250); } } #endregion } }
      
      







Apppool
 using Microsoft.Build.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Management; using System.Text; using System.Threading; namespace MSBuild.WMI { /// <summary> /// This class is used for operations with IIS ApplicationPool. /// Possible actions: /// "CheckExists" - check if the pool with the name specified in "AppPoolName" exists, result is accessible through field "Exists" /// "Create" - create an application pool with the name specified in "AppPoolName" /// "Start" = starts Application Pool /// "Stop" - stops Application Pool /// </summary> public class AppPool : BaseWMITask { #region Public Properties /// <summary> /// Application pool name /// </summary> public string AppPoolName { get; set; } /// <summary> /// Used as outpur for CheckExists command - True, if application pool with the specified name exists /// </summary> [Output] public bool Exists { get; set; } #endregion #region Public Methods /// <summary> /// Executes the task /// </summary> /// <returns>True, is task has been executed successfully; False - otherwise</returns> public override bool Execute() { try { Log.LogMessage("AppPool task, action = {0}", Action); switch (Action) { case WMI.TaskAction.CheckExists: Exists = GetAppPool() != null; break; case WMI.TaskAction.Create: CreateAppPool(); break; case WMI.TaskAction.Start: StartAppPool(); break; case WMI.TaskAction.Stop: StopAppPool(); break; } } catch (Exception ex) { Log.LogErrorFromException(ex); return false; } //WMI tasks are execute asynchronously, wait to completing Thread.Sleep(1000); return true; } #endregion #region Private Methods /// <summary> /// Gets ApplicationPool with name AppPoolName /// </summary> /// <returns>ManagementObject representing ApplicationPool or null</returns> private ManagementObject GetAppPool() { return GetObjectByQuery(string.Format("select * from ApplicationPool where Name = '{0}'", AppPoolName)); } /// <summary> /// Creates ApplicationPool with name AppPoolName, Integrated pipeline mode and ApplicationPoolIdentity (default) /// Calling code (MSBuild script) must first call CheckExists, in this method there's no checks /// </summary> private void CreateAppPool() { var path = new ManagementPath(@"ApplicationPool"); var mgmtClass = new ManagementClass(WMIScope, path, null); //obtain in-parameters for the method var inParams = mgmtClass.GetMethodParameters("Create"); //add the input parameters. inParams["AutoStart"] = true; inParams["Name"] = AppPoolName; //execute the method and obtain the return values. mgmtClass.InvokeMethod("Create", inParams, null); //wait till pool is created WaitTill(() => GetAppPool() != null); var appPool = GetAppPool(); //set pipeline mode (default is Classic) appPool["ManagedPipelineMode"] = (int)ManagedPipelineMode.Integrated; appPool.Put(); } /// <summary> /// Starts Application Pool /// </summary> private void StartAppPool() { GetAppPool().InvokeMethod("Start", null); } /// <summary> /// Stops Application Pool /// </summary> private void StopAppPool() { GetAppPool().InvokeMethod("Stop", null); } #endregion } }
      
      







ウェブサイト
 using Microsoft.Build.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Management; using System.Text; using System.Threading; using System.Threading.Tasks; namespace MSBuild.WMI { /// <summary> /// /// </summary> public class WebSite : BaseWMITask { #region Public Properties /// <summary> /// Web Site name /// </summary> public string SiteName { get; set; } /// <summary> /// Web Site physical path (not a UNC path) /// </summary> public string PhysicalPath { get; set; } /// <summary> /// Port (it's better if it's custom) /// </summary> public string Port { get; set; } /// <summary> /// Name of the Application Pool that will be used for this Web Site /// </summary> public string AppPoolName { get; set; } [Output] public bool Exists { get; set; } #endregion #region Public Methods /// <summary> /// Executes the task /// </summary> /// <returns>True, is task has been executed successfully; False - otherwise</returns> public override bool Execute() { try { Log.LogMessage("WebSite task, action = {0}", Action); switch (Action) { case WMI.TaskAction.CheckExists: Exists = GetWebSite() != null; break; case WMI.TaskAction.Create: CreateWebSite(); break; case WMI.TaskAction.Start: StartWebSite(); break; case WMI.TaskAction.Stop: StopWebSite(); break; } } catch (Exception ex) { Log.LogErrorFromException(ex); return false; } //WMI tasks are execute asynchronously, wait to completing Thread.Sleep(1000); return true; } #endregion #region Private Methods /// <summary> /// Creates web site with the specified name and port. Bindings must be confgiured after manually. /// </summary> private void CreateWebSite() { var path = new ManagementPath(@"BindingElement"); var mgmtClass = new ManagementClass(WMIScope, path, null); var binding = mgmtClass.CreateInstance(); binding["BindingInformation"] = ":" + Port + ":"; binding["Protocol"] = "http"; path = new ManagementPath(@"Site"); mgmtClass = new ManagementClass(WMIScope, path, null); // Obtain in-parameters for the method var inParams = mgmtClass.GetMethodParameters("Create"); // Add the input parameters. inParams["Bindings"] = new ManagementBaseObject[] { binding }; inParams["Name"] = SiteName; inParams["PhysicalPath"] = PhysicalPath; inParams["ServerAutoStart"] = true; // Execute the method and obtain the return values. mgmtClass.InvokeMethod("Create", inParams, null); WaitTill(() => GetApp("/") != null); var rootApp = GetApp("/"); rootApp["ApplicationPool"] = AppPoolName; rootApp.Put(); } /// <summary> /// Gets Web Site by name /// </summary> /// <returns>ManagementObject representing Web Site or null</returns> private ManagementObject GetWebSite() { return GetObjectByQuery(string.Format("select * from Site where Name = '{0}'", SiteName)); } /// <summary> /// Get Virtual Application by path /// </summary> /// <param name="path">Path of virtual application (if path == "/" - gets root application)</param> /// <returns>ManagementObject representing Virtual Application or null</returns> private ManagementObject GetApp(string path) { return GetObjectByQuery(string.Format("select * from Application where SiteName = '{0}' and Path='{1}'", SiteName, path)); } /// <summary> /// Stop Web Site /// </summary> private void StopWebSite() { GetWebSite().InvokeMethod("Stop", null); } /// <summary> /// Start Web Site /// </summary> private void StartWebSite() { GetWebSite().InvokeMethod("Start", null); } #endregion } }
      
      







スクリプトビルドから独自のタスクを呼び出します



今では、ビルドスクリプトからこれらのタスクを呼び出す方法を学ぶだけです。 これを行うには、まず、MSBuildにアセンブリの場所とそこから使用するタスクを指示します。

 <UsingTask TaskName="MSBuild.WMI.AppPool" AssemblyFile="MSBuild.WMI\bin\Debug\MSBuild.WMI.dll"/>
      
      





これで、最も一般的なMSBuildコマンドと同じ方法でMSBuild.WMI.AppPoolタスクを使用できます。

 <MSBuild.WMI.AppPool TaskAction="CheckExists" AppPoolName="$(AppPoolName)" Machine="$(Machine)" UserName="$(User)" Password="$(Password)"> <Output TaskParameter="Exists" PropertyName="AppPoolExists"/> </MSBuild.WMI.AppPool>
      
      





スポイラーの下には、プールとサイト(存在しない場合)を作成し、展開前にそれらを停止してから再起動できるdeploy.projファイルの例があります。

deploy.proj
 <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- common variables --> <PropertyGroup> <Machine Condition="'$(AppPoolName)' == ''">localhost</Machine> <User Condition="'$(User)' == ''"></User> <Password Condition="'$(User)' == ''"></Password> <AppPoolName Condition="'$(AppPoolName)' == ''">TestAppPool</AppPoolName> <WebSiteName Condition="'$(WebSiteName)' == ''">TestSite</WebSiteName> <WebSitePort Condition="'$(WebSitePort)' == ''">8088</WebSitePort> <WebSitePhysicalPath Condition="'$(WebSitePhysicalPath)' == ''">D:\Inetpub\TestSite</WebSitePhysicalPath> <AppPoolExists>False</AppPoolExists> </PropertyGroup> <UsingTask TaskName="MSBuild.WMI.AppPool" AssemblyFile="MSBuild.WMI\bin\Debug\MSBuild.WMI.dll"/> <UsingTask TaskName="MSBuild.WMI.WebSite" AssemblyFile="MSBuild.WMI\bin\Debug\MSBuild.WMI.dll"/> <!-- set up variables --> <Target Name="_Setup"> <MSBuild.WMI.AppPool TaskAction="CheckExists" AppPoolName="$(AppPoolName)" Machine="$(Machine)" UserName="$(User)" Password="$(Password)"> <Output TaskParameter="Exists" PropertyName="AppPoolExists"/> </MSBuild.WMI.AppPool> <MSBuild.WMI.WebSite TaskAction="CheckExists" SiteName="$(WebSiteName)" Machine="$(Machine)" UserName="$(User)" Password="$(Password)"> <Output TaskParameter="Exists" PropertyName="WebSiteExists"/> </MSBuild.WMI.WebSite> </Target> <!-- stop web site --> <Target Name="_StopSite"> <MSBuild.WMI.WebSite TaskAction="Stop" SiteName="$(WebSiteName)" Machine="$(Machine)" UserName="$(User)" Password="$(Password)" Condition="'$(WebSiteExists)'=='True'" /> <MSBuild.WMI.AppPool TaskAction="Stop" AppPoolName="$(AppPoolName)" Machine="$(Machine)" UserName="$(User)" Password="$(Password)" Condition="'$(AppPoolExists)'=='True'" /> </Target> <!-- stop and deploy web site --> <Target Name="_StopAndDeployWebSite"> <!-- stop (if it exists) --> <CallTarget Targets="_StopSite" /> <!-- create AppPool (if does not exist) --> <MSBuild.WMI.AppPool TaskAction="Create" AppPoolName="$(AppPoolName)" Machine="$(Machine)" UserName="$(User)" Password="$(Password)" Condition="'$(AppPoolExists)'=='False'" /> <!-- create web site (if does not exist)--> <MSBuild.WMI.WebSite TaskAction="Create" SiteName="$(WebSiteName)" Port="$(WebSitePort)" AppPoolName="$(AppPoolName)" Machine="$(Machine)" UserName="$(User)" Password="$(Password)" PhysicalPath="$(WebSitePhysicalPath)" Condition="'$(WebSiteExists)'=='False'" /> </Target> <!-- start all application parts --> <Target Name="_StartAll"> <MSBuild.WMI.AppPool TaskAction="Start" AppPoolName="$(AppPoolName)" Machine="$(Machine)" UserName="$(User)" Password="$(Password)" /> <MSBuild.WMI.WebSite TaskAction="Start" SiteName="$(WebSiteName)" Machine="$(Machine)" UserName="$(User)" Password="$(Password)" /> </Target> <!-- deployment implementation --> <Target Name="_DeployAll"> <CallTarget Targets="_StopAndDeployWebSite" /> <CallTarget Targets="_StartAll" /> </Target> <!-- deploy application --> <Target Name="Deploy" DependsOnTargets="_Setup"> <CallTarget Targets="_DeployAll" /> </Target> <!-- stop application --> <Target Name="StopApplication" DependsOnTargets="_Setup"> <CallTarget Targets="_StopWebSite" /> </Target> <!-- start application --> <Target Name="StartApplication" DependsOnTargets="_Setup"> <CallTarget Targets="_StartAll" /> </Target> </Project>
      
      







展開を呼び出すには、次のmsbuild.exeファイルを転送します。

 "C:\Program Files (x86)\MSBuild\12.0\Bin\msbuild.exe" deploy.proj
      
      







結論と参考文献



タスクを作成してMSBuildに組み込むことは、決して難しいことではないと言えます。 このようなタスクを実行できるアクションの範囲も非常に広いため、msbuild.exe以外の何も必要とせずに、最も簡単な展開操作でもMSBuildを使用できます。 このプロジェクトは、 github.com / StanislavUshakov / MSBuild.WMIのサンプルビルドファイルと共にgithubに投稿されました。新しいタスクを展開および追加できます。



All Articles