Skip to content

Commit

Permalink
ApplicationPoolConfig: Start or stop an IIS application pool
Browse files Browse the repository at this point in the history
  • Loading branch information
nirbar committed Sep 2, 2024
1 parent fc5ecd3 commit ad4104e
Show file tree
Hide file tree
Showing 16 changed files with 358 additions and 10 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/github-actions-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
psw_wix_version:
description: 'Custom WiX version'
required: true
default: '3.15.0-a62'
default: '3.15.0-a63'
type: string

jobs:
Expand All @@ -37,7 +37,7 @@ jobs:
Add-Content -Path ${{ github.env }} -Value "CUSTOM_WIX_VERSION=${{ env.DEFAULT_CUSTOM_WIX_VERSION }}"
}
env:
DEFAULT_CUSTOM_WIX_VERSION: '3.15.0-a46'
DEFAULT_CUSTOM_WIX_VERSION: '3.15.0-a63'

- name: Prepare for build
run: |
Expand Down
1 change: 1 addition & 0 deletions CaCommon/ErrorPrompter.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum PSW_MSI_MESSAGES
PSW_MSI_MESSAGES_ZIP_ARCHIVE_ERROR = 27014,
PSW_MSI_MESSAGES_UNZIP_ARCHIVE_ERROR = 27015,
PSW_MSI_MESSAGES_UNZIP_FILE_ERROR = 27016,
PSW_MSI_MESSAGES_APPPOOL_CONFIG_ERROR = 27017,
};

class CErrorPrompter
Expand Down
10 changes: 10 additions & 0 deletions PanelSwWixExtension/Data/tables.xml
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,16 @@
<columnDefinition name="Order" type="number" length="4" minValue="0" maxValue="2147483647" />
</tableDefinition>

<tableDefinition name="PSW_ApplicationPoolConfig">
<columnDefinition name="Id" type="string" length="72" primaryKey="yes" modularize="column" category="identifier" />
<columnDefinition name="Component_" type="string" length="72" modularize="column" keyTable="Component" keyColumn="1" category="identifier"/>
<columnDefinition name="ApplicationPool" type="string" length="0" modularize="property" category="formatted" />
<columnDefinition name="Stop" type="number" length="2" minValue="0" maxValue="1" />
<columnDefinition name="Start" type="number" length="2" minValue="0" maxValue="1" />
<columnDefinition name="ErrorHandling" type="number" minValue="0" maxValue="2" length="2" />
<columnDefinition name="Order" type="number" length="4" minValue="0" maxValue="2147483647" />
</tableDefinition>

<tableDefinition name="PSW_VersionCompare">
<columnDefinition name="Id" type="string" length="72" primaryKey="yes" modularize="column" category="identifier" />
<columnDefinition name="Property_" type="string" length="0" modularize="property" category="identifier" />
Expand Down
87 changes: 87 additions & 0 deletions PanelSwWixExtension/PanelSwWixCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ public override void ParseElement(SourceLineNumberCollection sourceLineNumbers,
ParseWebsiteConfigElement(element, componentId);
break;

case "ApplicationPoolConfig":
ParseApplicationPoolConfigElement(element, componentId);
break;

case "XslTransform":
ParseXslTransform(element, componentId, null);
break;
Expand Down Expand Up @@ -847,6 +851,10 @@ private void ParseCustomPatchRefElement(XmlElement element)
customActions.Add("PSW_WebsiteConfigSched");
customActions.Add("PSW_WebsiteConfigExec");
break;
case "PSW_ApplicationPoolConfig":
customActions.Add("PSW_ApplicationPoolConfigSched");
customActions.Add("PSW_ApplicationPoolConfigExec");
break;
case "PSW_VersionCompare":
customActions.Add("PSW_VersionCompare");
break;
Expand Down Expand Up @@ -1308,6 +1316,85 @@ private void ParseWebsiteConfigElement(XmlElement element, string component)
row[7] = order;
}

private void ParseApplicationPoolConfigElement(XmlElement element, string component)
{
SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
string id = "wap" + Guid.NewGuid().ToString("N");
string appPool = null;
bool stop = false;
bool start = false;
ErrorHandling promptOnError = ErrorHandling.fail;
int order = 1000000000 + GetLineNumber(sourceLineNumbers);

foreach (XmlAttribute attrib in element.Attributes)
{
if ((0 != attrib.NamespaceURI.Length) && (attrib.NamespaceURI != schema.TargetNamespace))
{
continue;
}

switch (attrib.LocalName)
{
case "ApplicationPool":
appPool = Core.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "Stop":
stop = (Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.Yes);
break;
case "Start":
start = (Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.Yes);
break;
case "ErrorHandling":
{
string a = Core.GetAttributeValue(sourceLineNumbers, attrib);
try
{
promptOnError = (ErrorHandling)Enum.Parse(typeof(ErrorHandling), a);
}
catch
{
Core.UnexpectedAttribute(sourceLineNumbers, attrib);
}
}
break;
case "Order":
order = Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, -1000000000, 1000000000);
if (order < 0)
{
order += int.MaxValue;
}
break;
default:
Core.UnexpectedAttribute(sourceLineNumbers, attrib);
break;
}
}

if (string.IsNullOrEmpty(component))
{
Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, "Component", "Id"));
}
if (string.IsNullOrEmpty(appPool))
{
Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, element.LocalName, "ApplicationPool"));
}

if (Core.EncounteredError)
{
return;
}

Core.CreateWixSimpleReferenceRow(sourceLineNumbers, "CustomAction", "PSW_ApplicationPoolConfigSched");
Row row = Core.CreateRow(sourceLineNumbers, "PSW_ApplicationPoolConfig");
row[0] = id;
row[1] = component;
row[2] = appPool;
row[3] = stop ? 1 : 0;
row[4] = start ? 1 : 0;
row[5] = (int)promptOnError;
row[6] = order;
}

private void ParseIsWindowsVersionOrGreater(XmlNode node)
{
SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
Expand Down
40 changes: 40 additions & 0 deletions PanelSwWixExtension/Xsd/PanelSwWixExtension.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,46 @@ Note that build-time resolution may be harmed by this process. For example, MsiA
</xs:complexType>
</xs:element>

<xs:element name="ApplicationPoolConfig">
<xs:annotation>
<xs:appinfo>
<xse:parent namespace="http://schemas.microsoft.com/wix/2006/wi" ref="Component" />
</xs:appinfo>
<xs:documentation><![CDATA[Configure an existing IIS application pool. Note that it may be useful to schedule this action after WiX iis extension's ConfigureIIs action.]]></xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="ApplicationPool" type="xs:string" use="required">
<xs:annotation>
<xs:documentation><![CDATA[Target application pool name. This field is formatted]]></xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Stop" type="wix:YesNoType" use="optional" default="no">
<xs:annotation>
<xs:documentation><![CDATA[Whether or not to stop the application pool. If both 'Stop' and 'Start' are set to 'yes' then the application pool will restart]]></xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Start" type="wix:YesNoType" use="optional" default="no">
<xs:annotation>
<xs:documentation><![CDATA[Whether or not to start the application pool. If both 'Stop' and 'Start' are set to 'yes' then the application pool will restart]]></xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ErrorHandling" use="optional" type="ErrorHandlingType" default="fail">
<xs:annotation>
<xs:documentation><![CDATA[On error fail, ignore, or prompt user to abort, retry or ignore. Defaults to 'fail']]></xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Order" use="optional" type="xs:int" default="0">
<xs:annotation>
<xs:documentation><![CDATA[Order of execution, defaults to line number. Expressions with explicit order field will be executed before expressions with implicit order. Negative Order values will be executed last in increasing order. For example, Order="-1" will be executed last, after Order="-2"]]></xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>

<xs:element name="ZipFile">
<xs:annotation>
<xs:appinfo>
Expand Down
182 changes: 182 additions & 0 deletions PswManagedCA/ApplicationPoolConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Web.Administration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
using PswManagedCA.Util;

namespace PswManagedCA
{
public class ApplicationPoolConfig
{
[Serializable]
public class ApplicationPoolConfigCatalog
{
public string Component { get; set; }
public string ApplicationPool { get; set; }
public bool Stop { get; set; } = false;
public bool Start { get; set; } = false;
public ErrorHandling ErrorHandling { get; set; } = ErrorHandling.fail;
}

[CustomAction]
public static ActionResult ApplicationPoolConfigSched(Session session)
{
AssemblyName me = typeof(ApplicationPoolConfig).Assembly.GetName();
session.Log($"Initialized from {me.Name} v{me.Version}");

List<ApplicationPoolConfigCatalog> catalogs = new List<ApplicationPoolConfigCatalog>();
using (View view = session.Database.OpenView("SELECT `Component_`, `ApplicationPool`, `Stop`, `Start`, `ErrorHandling` FROM `PSW_ApplicationPoolConfig` ORDER BY `Order`"))
{
view.Execute(null);

foreach (Record rec in view)
{
ApplicationPoolConfigCatalog cfg = new ApplicationPoolConfigCatalog();
using (rec)
{
cfg.Component = rec.GetString("Component_");
cfg.ApplicationPool = session.Format(rec.GetString("ApplicationPool"));
cfg.Stop = (rec.GetInteger("Stop") != 0);
cfg.Start = (rec.GetInteger("Start") != 0);
cfg.ErrorHandling = (ErrorHandling)rec.GetInteger("ErrorHandling");
}

ComponentInfo ci = session.Components[cfg.Component];
if (ci == null)
{
session.Log($"Component '{cfg.Component}' not present in package");
return ActionResult.Failure;
}
switch (ci.RequestState)
{
case InstallState.Default:
case InstallState.Local:
case InstallState.Source:
break;

default:
session.Log($"Component '{ci.Name}' action isn't install, or repair. Skipping ApplicationPoolConfig for '{cfg.ApplicationPool}'");
continue;
}

if (string.IsNullOrEmpty(cfg.ApplicationPool))
{
session.Log($"ApplicationPool name is empty for component '{ci.Name}'");
return ActionResult.Failure;
}
session.Log($"Will configure application pool '{cfg.ApplicationPool}'");
catalogs.Add(cfg);
}
}

if (catalogs.Count > 0)
{
XmlSerializer srlz = new XmlSerializer(catalogs.GetType());
using (StringWriter sw = new StringWriter())
{
srlz.Serialize(sw, catalogs);
session["PSW_ApplicationPoolConfigExec"] = sw.ToString();
session.DoAction("PSW_ApplicationPoolConfigExec");
}
}

return ActionResult.Success;
}

[CustomAction]
public static ActionResult ApplicationPoolConfigExec(Session session)
{
AssemblyName me = typeof(ApplicationPoolConfig).Assembly.GetName();
session.Log($"Initialized from {me.Name} v{me.Version}");

List<ApplicationPoolConfigCatalog> actions = new List<ApplicationPoolConfigCatalog>();
XmlSerializer srlz = new XmlSerializer(actions.GetType());
string cad = session["CustomActionData"];
using (StringReader sr = new StringReader(cad))
{
IEnumerable<ApplicationPoolConfigCatalog> ctlgs = srlz.Deserialize(sr) as IEnumerable<ApplicationPoolConfigCatalog>;
if (ctlgs == null)
{
return ActionResult.Success;
}
actions.AddRange(ctlgs);
}

foreach (ApplicationPoolConfigCatalog ctlg in actions)
{
LRetry:
try
{
ApplicationPoolConfigExec(session, ctlg);
}
catch (Exception ex)
{
switch (session.HandleError(ctlg.ErrorHandling, (int)PswErrorMessages.ApplicationPoolConfigFailure, ctlg.ApplicationPool, ex.Message))
{
default: // Silent / fail
session.Log($"User aborted on failure to configure application pool {ctlg.ApplicationPool}. {ex.Message}");
return ActionResult.Failure;

case MessageResult.Ignore:
session.Log($"User ignored failure to configure application pool {ctlg.ApplicationPool}. {ex.Message}");
continue;

case MessageResult.Retry:
session.Log($"User retry on failure to configure application pool {ctlg.ApplicationPool}. {ex.Message}");
goto LRetry;
}
}
}

return ActionResult.Success;
}

private static void ApplicationPoolConfigExec(Session session, ApplicationPoolConfigCatalog cfg)
{
using (ServerManager manager = new ServerManager())
{
ApplicationPool appPool = manager.ApplicationPools[cfg.ApplicationPool];
if (appPool == null)
{
throw new Exception($"Could not find '{cfg.ApplicationPool}' application pool");
}

if (cfg.Stop)
{
session.Log($"Stopping application pool '{cfg.ApplicationPool}'");
appPool.Stop();
manager.CommitChanges();

switch (appPool.State)
{
case ObjectState.Stopped:
case ObjectState.Stopping:
break;

default:
throw new Exception("Failed stopping application pool");
}
}
if (cfg.Start)
{
session.Log($"Starting application pool '{cfg.ApplicationPool}'");
appPool.Start();
manager.CommitChanges();

switch (appPool.State)
{
case ObjectState.Started:
case ObjectState.Starting:
break;

default:
throw new Exception("Failed starting application pool");
}
}
}
}
}
}
1 change: 1 addition & 0 deletions PswManagedCA/PswManagedCA.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ApplicationPoolConfig.cs" />
<Compile Include="InstallUtil.cs" />
<Compile Include="JsonJPath.cs" />
<Compile Include="Util\SessionEx.cs" />
Expand Down
1 change: 1 addition & 0 deletions PswManagedCA/Util/SessionEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public enum PswErrorMessages
ZipArchiveError = 27014,
UnzipArchiveError = 27015,
UnzipFileeError = 27016,
ApplicationPoolConfigFailure = 27017,
}

static class SessionEx
Expand Down
Loading

0 comments on commit ad4104e

Please sign in to comment.