Skip to content

Instantly share code, notes, and snippets.

@slabarque
Created December 18, 2013 10:48
Show Gist options
  • Save slabarque/8020418 to your computer and use it in GitHub Desktop.
Save slabarque/8020418 to your computer and use it in GitHub Desktop.
a custome AppPoolConfigProvider implementation for creating an apppool during msdeploy deployments
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.XPath;
using Microsoft.Web.Deployment;
namespace MyCompany.CustomMSDeployProviders
{
internal abstract class AppCmdProviderBase : DeploymentObjectProvider
{
internal string ObjectName = "";
internal const string KeyAttributeName = "path";
private static string logFile;
protected AppCmdProviderBase(DeploymentProviderContext providerContext, DeploymentBaseContext baseContext, string objectName):base(providerContext, baseContext)
{
ObjectName = objectName;
WriteLogInfo();
}
public override string Name
{
get { return ObjectName; }
}
///---------------------------------------------------------------
/// <summary>
/// Endows the provider with the attribute "path"
/// </summary>
///---------------------------------------------------------------
public override DeploymentObjectAttributeData CreateKeyAttributeData()
{
Log("CreateKeyAttributeData");
return new DeploymentObjectAttributeData(KeyAttributeName, ProviderContext.Path, DeploymentObjectAttributeKind.CaseInsensitiveCompare);
}
public override void GetAttributes(DeploymentAddAttributeContext addContext)
{
Log("GetAttributes");
GetSettings(addContext);
Log("Adding attribute isDest = {0}" , this.BaseContext.IsDestinationObject);
addContext.Add(new DeploymentObjectAttributeData("isDest", this.BaseContext.IsDestinationObject));
if(BaseContext.IsDestinationObject)
{
if(!ObjectExists(ProviderContext.Path))
{
throw new DeploymentException(String.Format("{0} does not exist, forcing add", ProviderContext.Path));
}
}
}
///---------------------------------------------------------------
/// <summary>
/// Add is called by the destination object when performing a sync
/// to validate the object (after GetAttributes throws an exception)
/// </summary>
///---------------------------------------------------------------
public override void Add(DeploymentObject source, bool whatIf)
{
//TODO: instaed of GetAndUpdateSpecifiedSettings, DeploymentObjectAttributeCollection should be used to pass settings
Log("Adding {0}", ProviderContext.Path);
//LogSyncParams(source.SyncParameters);
LogAttributes(source.Attributes);
//LogOptionSettings(ProviderContext.ProviderOptions.ProviderSettings);
//LogOptionSettings(ProviderContext.ProviderSettings);
if (!whatIf)
{
Dictionary<string, string> settings = GetAndUpdateSpecifiedSettings(ProviderContext.ProviderSettings, source.SyncParameters);
AddObject(ProviderContext.Path, settings);
}
}
///---------------------------------------------------------------
/// <summary>
/// Update is called by the destination object when performing a sync
/// to validate the object (because isDest will be different)
/// </summary>
///---------------------------------------------------------------
public override void Update(DeploymentObject source, bool whatIf)
{
//TODO: instaed of GetAndUpdateSpecifiedSettings, DeploymentObjectAttributeCollection should be used to pass settings
Log("Updating {0}", ProviderContext.Path);
//LogSyncParams(source.SyncParameters);
LogAttributes(source.Attributes);
//LogOptionSettings(ProviderContext.ProviderOptions.ProviderSettings);
//LogOptionSettings(ProviderContext.ProviderSettings);
if (!whatIf)
{
Dictionary<string, string> settings = GetAndUpdateSpecifiedSettings(ProviderContext.ProviderSettings, source.SyncParameters);
SetObject(ProviderContext.Path, settings);
}
}
public override DeploymentObjectKind ObjectKind
{
get
{
return DeploymentObjectKind.None;
}
}
protected abstract void AddObject(string name, Dictionary<string, string> settings);
protected abstract void SetObject(string name, Dictionary<string, string> settings);
protected abstract bool ObjectExists(string name);
protected abstract void GetSettings(DeploymentAddAttributeContext addContext);
private Dictionary<string, string> GetAndUpdateSpecifiedSettings(DeploymentProviderSettingCollection providerSettings, DeploymentSyncParameterCollection syncParameters)
{
Regex regex = new Regex("//(?<providerName>.*)/@(?<settingName>.*)");
var parameters = from sp
in syncParameters
from spe in sp.Entries
where spe.Kind == DeploymentSyncParameterEntryKind.DeploymentObjectAttribute
select new{
Value = sp.Value,
ProviderName = regex.Match(spe.Match).Groups["providerName"].Value,
SettingName = regex.Match(spe.Match).Groups["settingName"].Value
};
Dictionary<string, string> result = new Dictionary<string, string>();
foreach (DeploymentProviderSetting deploymentProviderSetting in providerSettings)
{
if (deploymentProviderSetting.Value != null)
{
var matchingParameter = parameters.Where(p=>p.ProviderName == this.Name && p.SettingName == deploymentProviderSetting.Name).SingleOrDefault();
if (matchingParameter != null)
{
Log("Found a matching parameter for setting {0}, new value is {1}", deploymentProviderSetting.Name, matchingParameter.Value);
result.Add(deploymentProviderSetting.Name.Replace("_", "."), matchingParameter.Value);
}
else
{
Log("Setting {0}, value remains {1}", deploymentProviderSetting.Name, deploymentProviderSetting.Value.ToString());
result.Add(deploymentProviderSetting.Name.Replace("_", "."), deploymentProviderSetting.Value.ToString());
}
}
}
return result;
}
public static void Log(string message, params object[] args)
{
if(String.IsNullOrEmpty(logFile))
{
logFile = String.Format(@"c:\logs\{0}_{1}.log", DateTime.Now.ToString("yyyyMMdd_HH_mm_ss") , "AppcmdProviderBase");
if(!Directory.Exists(@"c:\logs"))
Directory.CreateDirectory(@"c:\logs");
}
if (String.IsNullOrEmpty(message))
message = String.Empty;
File.AppendAllText(logFile, String.Concat(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss : "), String.Format(message, args), Environment.NewLine));
}
private void WriteLogInfo()
{
try
{
Log("RunConfigToolProvider ctor");
Log("DeploymentBaseContext");
Log(BaseContext.WebServerConfiguration.ApplicationHostConfigurationPath);
Log(BaseContext.WebServerConfiguration.ConfigurationDirectory);
Log(BaseContext.WebServerConfiguration.RedirectionConfigurationPath);
Log(BaseContext.WebServerConfiguration.WebServerDirectory);
Log(BaseContext.WebServerConfiguration.WebServerManifest);
Log(BaseContext.WebServerConfiguration.SchemaDirectory);
Log(BaseContext.ComputerName);
Log(BaseContext.IsDestinationObject.ToString());
Log("DeploymentProviderContext");
Log(ProviderContext.Path);
Log(ProviderContext.ProviderOptions.Path);
Log(string.Join(";", ProviderContext.ProviderSettings.Select(s => (s.Name ?? "null") + "=" + (s.Value ?? "null") + "\n").ToArray()));
Log("misc");
Log(AppDomain.CurrentDomain.BaseDirectory);
Log(Environment.CurrentDirectory);
Log(Assembly.GetExecutingAssembly().FullName);
Log(Thread.CurrentPrincipal.Identity.Name);
}
catch (Exception e)
{
Log(e.ToString());
}
}
private void LogOptionSettings(DeploymentProviderSettingCollection providerSettings)
{
Log("LogOptionSettings");
foreach (DeploymentProviderSetting deploymentProviderSetting in providerSettings)
{
Log("{0} = {1}", deploymentProviderSetting.Name, deploymentProviderSetting.Value);
}
}
//TODO: instaed of GetAndUpdateSpecifiedSettings, DeploymentObjectAttributeCollection should be used to pass settings
private void LogAttributes(DeploymentObjectAttributeCollection attributes)
{
Log("LogAttributes");
foreach (DeploymentObjectAttribute deploymentObjectAttribute in attributes)
{
Log("{0} = {1} ({2} {3}", deploymentObjectAttribute.Name, deploymentObjectAttribute.Value.Value, deploymentObjectAttribute.Value.GetType(), deploymentObjectAttribute.Value.Value.GetType());
}
}
private void LogSyncParams(DeploymentSyncParameterCollection syncParameters)
{
Log("LogSyncParams");
foreach (DeploymentSyncParameter deploymentSyncParameter in syncParameters)
{
Log("{0} = {1} (isdirty = {2})", deploymentSyncParameter.Name, deploymentSyncParameter.Value, deploymentSyncParameter.IsDirty);
foreach (DeploymentSyncParameterEntry deploymentSyncParameterEntry in deploymentSyncParameter.Entries)
{
Log("entry {0}", deploymentSyncParameterEntry.Match);
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace MyCompany.CustomMSDeployProviders
{
public class AppPool
{
private string name;
private string managedRunTimeVersion;
private string managedPipelineMode;
private string state;
protected internal AppPool(string name, string managedRunTimeVersion, string managedPipelineMode, string state)
{
this.name = name;
this.managedRunTimeVersion = managedRunTimeVersion;
this.managedPipelineMode = managedPipelineMode;
this.state = state;
}
public string Name
{
get { return name; }
}
public string ManagedRunTimeVersion
{
get { return managedRunTimeVersion; }
}
public string ManagedPipelineMode
{
get { return managedPipelineMode; }
}
public string State
{
get { return state; }
}
public static bool Exists(string name)
{
return List().Count(a => a.name == name) > 0;
}
public static List<AppPool> List()
{
string output = CallAppCmd("list apppool");
return ParseListCommandOutput(output);
}
public static AppPool Set(string name, Dictionary<string, string> settings = null)
{
if(settings == null)
settings = new Dictionary<string, string>();
string output = CallAppCmd(String.Format("set apppool \"{0}\" -apppool.name:\"{0}\" {1}", name, String.Join(" ", settings.Select(s => ConvertSettingToParameter(s, false)).ToArray())));
List<AppPool> appPools = List();
AppPool appPool = appPools.Where(a=>a.name == name).SingleOrDefault();
if(appPool==null)
{
throw new Exception(String.Format("AppPool {0} could not be set", name));
}
return appPool;
}
public static AppPool Add(string name, Dictionary<string, string> settings=null)
{
if (settings == null)
settings = new Dictionary<string, string>();
string output = CallAppCmd(String.Format("add apppool -name:\"{0}\" {1}", name, String.Join(" ", settings.Select(s => ConvertSettingToParameter(s, true)).ToArray())));
List<AppPool> appPools = List();
AppPool appPool = appPools.Where(a => a.name == name).SingleOrDefault();
if (appPool == null)
{
throw new Exception(String.Format("AppPool {0} could not be added", name));
}
return appPool;
}
private static string ConvertSettingToParameter(KeyValuePair<string, string> s, bool adding)
{
return String.Format("-{0}:\"{1}\"", s.Key, s.Value);
}
private static string CallAppCmd(string arguments)
{
string systemroot = Environment.GetEnvironmentVariable("systemroot")??@"C:\Windows";
ConfigAppPoolProvider.Log(Path.Combine(systemroot, @"system32\inetsrv\APPCMD ") + arguments);
ProcessStartInfo batchFileInfo = new ProcessStartInfo(Path.Combine(systemroot, @"system32\inetsrv\APPCMD"));
batchFileInfo.Arguments = arguments;
batchFileInfo.CreateNoWindow = true;
batchFileInfo.UseShellExecute = false;
batchFileInfo.RedirectStandardOutput = true;
batchFileInfo.RedirectStandardError = true;
Process batchProcess = new Process();
batchProcess.StartInfo = batchFileInfo;
batchProcess.Start();
batchProcess.WaitForExit();
string output = batchProcess.StandardOutput.ReadToEnd();
ConfigAppPoolProvider.Log(output);
return output;
}
private static List<AppPool> ParseListCommandOutput(string output)
{
string[] lines = output.Split(new string[] {"APPPOOL"}, StringSplitOptions.RemoveEmptyEntries);
Regex listOutputLine = new Regex(" \"(?<name>.*)\" \\(MgdVersion:(?<version>v\\d*.\\d*),MgdMode:(?<mode>.*),state:(?<state>.*)\\)");
List<AppPool> appPools = new List<AppPool>();
foreach (string line in lines)
{
Match match = listOutputLine.Match(line);
string name = match.Groups["name"].Value;
string version = match.Groups["version"].Value;
string mode = match.Groups["mode"].Value;
string state = match.Groups["state"].Value;
appPools.Add(new AppPool(name, version, mode, state));
}
return appPools;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Web.Deployment;
namespace MyCompany.CustomMSDeployProviders
{
public class ConfigAppPoolObjectResolver: DeploymentObjectResolver
{
public ConfigAppPoolObjectResolver()
: base()
{
AppCmdProviderBase.Log("ConfigAppPoolObjectResolver.ctor");
}
public override string GetAbsolutePath(DeploymentObject deploymentObject)
{
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetAbsolutePath(" + (deploymentObject != null ? deploymentObject.Name : "null") + ")");
string absolutePath = base.GetAbsolutePath(deploymentObject);
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetAbsolutePath: result :" + absolutePath);
return absolutePath;
}
public override string GetAttributeDescription(string deploymentObjectName, string attributeName)
{
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetAttributeDescription");
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetAttributeDescription(" + deploymentObjectName + ", " + attributeName + ")");
string attributeDescription = GetAttributeLocalizedString(deploymentObjectName, attributeName);
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetAttributeDescription: result :" + attributeDescription);
return attributeDescription;
}
public override string GetAttributeFriendlyName(string deploymentObjectName, string attributeName)
{
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetAttributeFriendlyName");
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetAttributeFriendlyName(" + deploymentObjectName + ", " + attributeName + ")");
string attributeFriendlyName = GetAttributeLocalizedString(deploymentObjectName, attributeName);
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetAttributeFriendlyName: result :" + attributeFriendlyName);
return attributeFriendlyName;
}
public override void GetFactoryInfo(DeploymentObject deploymentObject, out string factoryName, out string factoryPath)
{
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetFactoryInfo");
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetFactoryInfo(" + (deploymentObject != null ? deploymentObject.Name : "null") + ")");
base.GetFactoryInfo(deploymentObject, out factoryName, out factoryPath);
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetFactoryInfo( factoryName=" + factoryName + ", factoryPath = " + factoryPath + ")");
}
public override string GetStreamRelativeFilePath(DeploymentObject deploymentObject)
{
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetStreamRelativeFilePath");
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetStreamRelativeFilePath(" + (deploymentObject != null ? deploymentObject.Name : "null") + ")");
string streamRelativeFilePath = base.GetStreamRelativeFilePath(deploymentObject);
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetStreamRelativeFilePath: result :" + streamRelativeFilePath);
return streamRelativeFilePath;
}
public override string GetSummary(DeploymentObject deploymentObject)
{
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetSummary");
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetSummary(" + (deploymentObject!=null?deploymentObject.Name:"null") + ")");
string summary = base.GetSummary(deploymentObject);
AppCmdProviderBase.Log( "ConfigAppPoolObjectResolver.GetSummary: result :" + summary + " " + (deploymentObject != null ? deploymentObject.Name : "null"));
return summary;
}
/// <summary>
/// These are the descriptions visible in the GUI of IIS when importing a package
/// </summary>
/// <param name="deploymentObjectName"></param>
/// <param name="attributeName"></param>
/// <returns></returns>
private static string GetAttributeLocalizedString(string deploymentObjectName, string attributeName)
{
switch (attributeName)
{
case "isDest":
return String.Empty;
case "path":
return "Apppool name";
case "managedRuntimeVersion":
return "Required .NET Runtime Version";
case "managedPipelineMode":
return "Required Managed Pipeline Mode";
case "processModel_identityType":
return "Identity type for Process Model";
case "processModel_userName":
return "Identity for Process Model";
case "processModel_password":
return "Password for Process Model Identity";
case "enable32BitAppOnWin64":
return "Always run in 32-bit mode";
default:
return attributeName;
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Web.Deployment;
namespace MyCompany.CustomMSDeployProviders
{
internal class ConfigAppPoolProvider : AppCmdProviderBase
{
public ConfigAppPoolProvider(DeploymentProviderContext providerContext, DeploymentBaseContext baseContext)
: base(providerContext, baseContext, "configAppPool")
{
}
public override DeploymentObjectResolver GetObjectResolver()
{
return new ConfigAppPoolObjectResolver();
}
protected override void AddObject(string name, Dictionary<string, string> settings)
{
AppPool.Add(name, settings);
}
protected override void SetObject(string name, Dictionary<string, string> settings)
{
AppPool.Set(name, settings);
}
protected override bool ObjectExists(string name)
{
return AppPool.Exists(name);
}
protected override void GetSettings(DeploymentAddAttributeContext addContext)
{
foreach (DeploymentProviderSettingInfo deploymentProviderSettingInfo in ConfigAppPoolProviderSettingInfo.GetSettings())
{
if (ProviderContext.ProviderSettings[deploymentProviderSettingInfo.Name].Value != null)
{
Log("Adding attribute {0} = {1}" , deploymentProviderSettingInfo.Name , ProviderContext.ProviderSettings[deploymentProviderSettingInfo.Name].Value.ToString());
addContext.Add(new DeploymentObjectAttributeData(deploymentProviderSettingInfo.Name,
ProviderContext.ProviderSettings[deploymentProviderSettingInfo.Name].Value.ToString(),
DeploymentObjectAttributeKind.CaseInsensitiveCompare));
}
}
}
}
}
using System.Linq;
using Microsoft.Web.Deployment;
namespace MyCompany.CustomMSDeployProviders
{
[DeploymentProviderFactory]
public class ConfigAppPoolProviderFactory : DeploymentProviderFactory
{
public static string NAME = "configAppPool";
protected override DeploymentObjectProvider Create(DeploymentProviderContext providerContext,
DeploymentBaseContext baseContext)
{
return new ConfigAppPoolProvider(providerContext, baseContext);
}
public override DeploymentProviderSettingInfo[] GetSupportedSettings()
{
return ConfigAppPoolProviderSettingInfo.GetSettings().ToArray();
}
public override string Description
{
get { return @"Custom provider to create or update an application pool"; }
}
public override string ExamplePath
{
get { return @"myapppool"; }
}
public override string FriendlyName
{
get { return NAME; }
}
public override string Name
{
get { return NAME; }
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Web.Deployment;
namespace MyCompany.CustomMSDeployProviders
{
public class ConfigAppPoolProviderSettingInfo : DeploymentProviderSettingInfo
{
#region SupportedSettings
public static string[] supportedSettings = new string[46]
{
"queueLength",
"autoStart",
"enable32BitAppOnWin64",
"managedRuntimeVersion",
"managedRuntimeLoader",
"enableConfigurationOverride",
"managedPipelineMode",
"CLRConfigFile",
"passAnonymousToken",
"startMode",
"processModel_identityType",
"processModel_userName",
"processModel_password",
"processModel_loadUserProfile",
"processModel_logonType",
"processModel_manualGroupMembership",
"processModel_idleTimeout",
"processModel_maxProcesses",
"processModel_shutdownTimeLimit",
"processModel_startupTimeLimit",
"processModel_pingingEnabled",
"processModel_pingInterval",
"processModel_pingResponseTime",
"recycling_disallowOverlappingRotation",
"recycling_disallowRotationOnConfigChange",
"recycling_logEventOnRecycle",
"recycling_periodicRestart_memory",
"recycling_periodicRestart_privateMemory",
"recycling_periodicRestart_requests",
"recycling_periodicRestart_time",
"recycling_periodicRestart_schedule_[value='timespan']_value",
"failure_loadBalancerCapabilities",
"failure_orphanWorkerProcess",
"failure_orphanActionExe",
"failure_orphanActionParams",
"failure_rapidFailProtection",
"failure_rapidFailProtectionInterval",
"failure_rapidFailProtectionMaxCrashes",
"failure_autoShutdownExe",
"failure_autoShutdownParams",
"cpu_limit",
"cpu_action",
"cpu_resetInterval",
"cpu_smpAffinitized",
"cpu_smpProcessorAffinityMask",
"cpu_smpProcessorAffinityMask2"
};
#endregion
public override Type Type
{
get { return typeof (String); }
}
public override string Description
{
get { return this.Name; }
}
public override string FriendlyName
{
get { return this.Name; }
}
static ConfigAppPoolProviderSettingInfo()
{
}
private ConfigAppPoolProviderSettingInfo(string settingName)
: base(settingName, ConfigAppPoolProviderFactory.NAME)
{
}
public static IEnumerable<DeploymentProviderSettingInfo> GetSettings()
{
foreach (string supportedSetting in supportedSettings)
{
yield return new ConfigAppPoolProviderSettingInfo(supportedSetting);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment