diff --git a/IISIntegration.sln b/IISIntegration.sln
index 6548ea89c5..4ac18b81bd 100644
--- a/IISIntegration.sln
+++ b/IISIntegration.sln
@@ -30,6 +30,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.IISIntegration.Tests", "test\Microsoft.AspNetCore.Server.IISIntegration.Tests\Microsoft.AspNetCore.Server.IISIntegration.Tests.xproj", "{4106DB10-E09F-480E-9CE6-B39235512EE6}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests", "test\Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests\Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests.xproj", "{D0FA003D-DE4C-480E-B4A4-BD38691B36AD}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.IISIntegration.Tools", "src\Microsoft.AspNetCore.Server.IISIntegration.Tools\Microsoft.AspNetCore.Server.IISIntegration.Tools.xproj", "{B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +72,14 @@ Global
{4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D0FA003D-DE4C-480E-B4A4-BD38691B36AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D0FA003D-DE4C-480E-B4A4-BD38691B36AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0FA003D-DE4C-480E-B4A4-BD38691B36AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D0FA003D-DE4C-480E-B4A4-BD38691B36AD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -81,5 +93,7 @@ Global
{8B3446E8-E6A8-4591-AA63-A95837C6E97C} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD}
{7F2F50C7-610F-4B69-B945-CA283511A587} = {EF30B533-D715-421A-92B7-92FEF460AC9C}
{4106DB10-E09F-480E-9CE6-B39235512EE6} = {EF30B533-D715-421A-92B7-92FEF460AC9C}
+ {D0FA003D-DE4C-480E-B4A4-BD38691B36AD} = {EF30B533-D715-421A-92B7-92FEF460AC9C}
+ {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD}
EndGlobalSection
EndGlobal
diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json
index 22c9f65529..1b75677d04 100644
--- a/NuGetPackageVerifier.json
+++ b/NuGetPackageVerifier.json
@@ -6,7 +6,8 @@
"packages": {
"dotnet-publish-iis": { },
"Microsoft.AspNetCore.IISPlatformHandler": { },
- "Microsoft.AspNetCore.Server.IISIntegration": { }
+ "Microsoft.AspNetCore.Server.IISIntegration": { },
+ "Microsoft.AspNetCore.Server.IISIntegration.Tools": { }
}
},
"Default": { // Rules to run for packages not listed in any other set.
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Microsoft.AspNetCore.Server.IISIntegration.Tools.xproj b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Microsoft.AspNetCore.Server.IISIntegration.Tools.xproj
new file mode 100644
index 0000000000..7c005d8d4a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Microsoft.AspNetCore.Server.IISIntegration.Tools.xproj
@@ -0,0 +1,19 @@
+
+
+
+ 14.0.24720
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ b1bc61b7-ba1d-4100-a2e8-49d00ce2771d
+ Microsoft.AspNetCore.Server.IISIntegration.Tools
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\$(MSBuildProjectName)\
+
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs
new file mode 100644
index 0000000000..c279b3bb7b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs
@@ -0,0 +1,58 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.Extensions.CommandLineUtils;
+
+namespace Microsoft.AspNetCore.Tools.PublishIIS
+{
+ public class Microsoft.AspNetCore.Server.IISIntegration.Tools
+ {
+ public static int Main(string[] args)
+ {
+ var app = new CommandLineApplication
+ {
+ Name = "dotnet publish-iis",
+ FullName = "Asp.Net IIS Publisher",
+ Description = "IIS Publisher for the Asp.Net web applications",
+ };
+ app.HelpOption("-h|--help");
+
+ var publishFolderOption = app.Option("--publish-folder|-p", "The path to the publish output folder", CommandOptionType.SingleValue);
+ var webRootOption = app.Option("--webroot|-w", "The name of webroot folder", CommandOptionType.SingleValue);
+ var projectPath = app.Argument("", "The path to the project (project folder or project.json) being published. If empty the current directory is used.");
+
+ app.OnExecute(() =>
+ {
+ var publishFolder = publishFolderOption.Value();
+
+ if (publishFolder == null)
+ {
+ app.ShowHelp();
+ return 2;
+ }
+
+ Reporter.Output.WriteLine($"Configuring the following project for use with IIS: '{publishFolder}'");
+
+ var exitCode = new PublishIISCommand(publishFolder, projectPath.Value, webRootOption.Value()).Run();
+
+ Reporter.Output.WriteLine("Configuring project completed successfully");
+
+ return exitCode;
+ });
+
+ try
+ {
+ return app.Execute(args);
+ }
+ catch (Exception e)
+ {
+ Reporter.Error.WriteLine(e.Message.Red());
+ Reporter.Verbose.WriteLine(e.ToString().Yellow());
+ }
+
+ return 1;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..76feceeff0
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Reflection;
+using System.Resources;
+
+[assembly: AssemblyMetadata("Serviceable", "True")]
+[assembly: NeutralResourcesLanguage("en-us")]
+[assembly: AssemblyCompany("Microsoft Corporation.")]
+[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
+[assembly: AssemblyProduct("Microsoft ASP.NET Core")]
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs
new file mode 100644
index 0000000000..eea405c817
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs
@@ -0,0 +1,119 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.ProjectModel;
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.AspNetCore.Server.IISIntegration.Tools
+{
+ public class PublishIISCommand
+ {
+ private readonly string _publishFolder;
+ private readonly string _projectPath;
+ private readonly string _webRoot;
+
+ public PublishIISCommand(string publishFolder, string projectPath, string webRoot)
+ {
+ _publishFolder = publishFolder;
+ _projectPath = projectPath;
+ _webRoot = webRoot;
+ }
+
+ public int Run()
+ {
+ var applicationBasePath = GetApplicationBasePath();
+ var webRoot = GetWebRoot(applicationBasePath);
+
+ XDocument webConfigXml = null;
+ var webRootDirectory = Path.Combine(_publishFolder, webRoot);
+ var webConfigPath = Path.Combine(webRootDirectory, "web.config");
+ if (File.Exists(webConfigPath))
+ {
+ Reporter.Output.WriteLine($"Updating web.config at '{webConfigPath}'");
+
+ try
+ {
+ webConfigXml = XDocument.Load(webConfigPath);
+ }
+ catch (XmlException) { }
+ }
+ else
+ {
+ if (!Directory.Exists(webRootDirectory))
+ {
+ Reporter.Output.WriteLine($"No webroot directory found. Creating '{webRootDirectory}'");
+ Directory.CreateDirectory(webRootDirectory);
+ }
+
+ Reporter.Output.WriteLine($"No web.config found. Creating '{webConfigPath}'");
+ }
+
+ var applicationName = GetApplicationName(applicationBasePath) + ".exe";
+ var transformedConfig = WebConfigTransform.Transform(webConfigXml, applicationName, ConfigureForAzure());
+
+ using (var f = new FileStream(webConfigPath, FileMode.Create))
+ {
+ transformedConfig.Save(f);
+ }
+
+ return 0;
+ }
+
+ private string GetApplicationBasePath()
+ {
+ if (!string.IsNullOrEmpty(_projectPath))
+ {
+ var fullProjectPath = Path.GetFullPath(_projectPath);
+
+ return Path.GetFileName(fullProjectPath) == "project.json"
+ ? Path.GetDirectoryName(fullProjectPath)
+ : fullProjectPath;
+ }
+
+ return Directory.GetCurrentDirectory();
+ }
+
+ private string GetApplicationName(string applicationBasePath)
+ {
+ return ProjectReader.GetProject(Path.Combine(applicationBasePath, "project.json")).Name;
+ }
+
+ private string GetWebRoot(string applicationBasePath)
+ {
+ if (!string.IsNullOrEmpty(_webRoot))
+ {
+ return _webRoot;
+ }
+
+ var builder = new ConfigurationBuilder()
+ .AddJsonFile(Path.Combine(applicationBasePath, "hosting.json"), optional: true);
+
+ var webroot = builder.Build()["webroot"];
+
+ if (!string.IsNullOrEmpty(webroot))
+ {
+ return webroot;
+ }
+
+ if (Directory.Exists(Path.Combine(applicationBasePath, "wwwroot")))
+ {
+ return "wwwroot";
+ }
+
+ return string.Empty;
+ }
+
+ private static bool ConfigureForAzure()
+ {
+ var configureForAzureValue = Environment.GetEnvironmentVariable("DOTNET_CONFIGURE_AZURE");
+ return string.Equals(configureForAzureValue, "true", StringComparison.Ordinal) ||
+ string.Equals(configureForAzureValue, "1", StringComparison.Ordinal) ||
+ !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME"));
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs
new file mode 100644
index 0000000000..2d6a0994f3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs
@@ -0,0 +1,104 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace Microsoft.AspNetCore.Server.IISIntegration.Tools
+{
+ public static class WebConfigTransform
+ {
+ public static XDocument Transform(XDocument webConfig, string appName, bool configureForAzure)
+ {
+ const string HandlersElementName = "handlers";
+ const string httpPlatformElementName = "httpPlatform";
+
+ webConfig = webConfig == null || webConfig.Root.Name.LocalName != "configuration"
+ ? XDocument.Parse("")
+ : webConfig;
+
+ var webServerSection = GetOrCreateChild(webConfig.Root, "system.webServer");
+
+ TransformHandlers(GetOrCreateChild(webServerSection, HandlersElementName));
+ TransformHttpPlatform(GetOrCreateChild(webServerSection, httpPlatformElementName), appName, configureForAzure);
+
+ // make sure that the httpPlatform element is after handlers element
+ var httpPlatformElement = webServerSection.Element(HandlersElementName)
+ .ElementsBeforeSelf(httpPlatformElementName).SingleOrDefault();
+ if (httpPlatformElement != null)
+ {
+ httpPlatformElement.Remove();
+ webServerSection.Element(HandlersElementName).AddAfterSelf(httpPlatformElement);
+ }
+
+ return webConfig;
+ }
+
+ private static void TransformHandlers(XElement handlersElement)
+ {
+ var platformHandlerElement =
+ handlersElement.Elements("add")
+ .FirstOrDefault(e => string.Equals((string)e.Attribute("name"), "httpplatformhandler", StringComparison.OrdinalIgnoreCase));
+
+ if (platformHandlerElement == null)
+ {
+ platformHandlerElement = new XElement("add");
+ handlersElement.Add(platformHandlerElement);
+ }
+
+ platformHandlerElement.SetAttributeValue("name", "httpPlatformHandler");
+ SetAttributeValueIfEmpty(platformHandlerElement, "path", "*");
+ SetAttributeValueIfEmpty(platformHandlerElement, "verb", "*");
+ SetAttributeValueIfEmpty(platformHandlerElement, "modules", "httpPlatformHandler");
+ SetAttributeValueIfEmpty(platformHandlerElement, "resourceType", "Unspecified");
+ }
+
+ private static void TransformHttpPlatform(XElement httpPlatformElement, string appName, bool configureForAzure)
+ {
+ var appPath = Path.Combine(configureForAzure ? @"%home%\site" : "..", appName);
+ var logPath = Path.Combine(configureForAzure ? @"\\?\%home%\LogFiles" : @"..\logs", "stdout.log");
+
+ httpPlatformElement.SetAttributeValue("processPath", appPath);
+ SetAttributeValueIfEmpty(httpPlatformElement, "stdoutLogEnabled", "false");
+ SetAttributeValueIfEmpty(httpPlatformElement, "stdoutLogFile", logPath);
+ SetAttributeValueIfEmpty(httpPlatformElement, "startupTimeLimit", "3600");
+
+ AddApplicationBase(httpPlatformElement);
+ }
+
+ private static void AddApplicationBase(XElement httpPlatformElement)
+ {
+ const string appBaseKeyName = "ASPNET_APPLICATIONBASE";
+
+ var envVariables = GetOrCreateChild(httpPlatformElement, "environmentVariables");
+ var appBaseElement = envVariables.Elements("environmentVariable").SingleOrDefault(e =>
+ string.Equals((string)e.Attribute("name"), appBaseKeyName, StringComparison.CurrentCultureIgnoreCase));
+
+ if (appBaseElement == null)
+ {
+ appBaseElement = new XElement("environmentVariable", new XAttribute("name", appBaseKeyName));
+ envVariables.AddFirst(appBaseElement);
+ }
+
+ appBaseElement.SetAttributeValue("value", ".");
+ }
+
+ private static XElement GetOrCreateChild(XElement parent, string childName)
+ {
+ var childElement = parent.Element(childName);
+ if (childElement == null)
+ {
+ childElement = new XElement(childName);
+ parent.Add(childElement);
+ }
+ return childElement;
+ }
+
+ private static void SetAttributeValueIfEmpty(XElement element, string attributeName, string value)
+ {
+ element.SetAttributeValue(attributeName, (string)element.Attribute(attributeName) ?? value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/project.json b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/project.json
new file mode 100644
index 0000000000..130896ffb0
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/project.json
@@ -0,0 +1,23 @@
+{
+ "version": "1.0.0-*",
+ "compilationOptions": {
+ "emitEntryPoint": true,
+ "warningsAsErrors": true,
+ "keyFile": "../../tools/Key.snk",
+ "nowarn": [ "CS1591" ],
+ "xmlDoc": true
+ },
+
+ "dependencies": {
+ "NETStandard.Library": "1.0.0-*",
+ "Microsoft.Extensions.CommandLineUtils": "1.0.0-*",
+ "Microsoft.Extensions.Configuration.Json": "1.0.0-*",
+ "Microsoft.DotNet.ProjectModel": "1.0.0-*",
+ "Microsoft.DotNet.Cli.Utils": "1.0.0-*"
+ },
+
+ "frameworks": {
+ "dnxcore50": {
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests.xproj b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests.xproj
new file mode 100644
index 0000000000..e3882f78d1
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests.xproj
@@ -0,0 +1,19 @@
+
+
+
+ 14.0.24720
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ d0fa003d-de4c-480e-b4a4-bd38691b36ad
+ Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\$(MSBuildProjectName)\
+
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs
new file mode 100644
index 0000000000..5c00ae007d
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs
@@ -0,0 +1,153 @@
+using Xunit;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using System;
+
+namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests
+{
+ public class PublishIISCommandFacts
+ {
+ private class Folders
+ {
+ public string TestRoot;
+ public string PublishOutput;
+ public string ProjectPath;
+ }
+
+ [Fact]
+ public void PublishIIS_uses_default_values_if_options_not_specified()
+ {
+ var webRoot = "wwwroot";
+ var folders = CreateTestDir("{}", webRoot);
+
+ new PublishIISCommand(folders.PublishOutput, folders.ProjectPath, null).Run();
+
+ var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot)
+ .Descendants("httpPlatform").Attributes("processPath").Single();
+
+ Assert.Equal($@"..\projectDir.exe", processPath);
+
+ Directory.Delete(folders.TestRoot, recursive: true);
+ }
+
+ [Theory]
+ [InlineData("awesomeApp")]
+ [InlineData("awesome.App")]
+ public void PublishIIS_reads_application_name_from_project_json_if_exists(string projectName)
+ {
+ var webRoot = "wwwroot";
+ var folders = CreateTestDir($@"{{ ""name"": ""{projectName}"" }}", webRoot);
+
+ new PublishIISCommand(folders.PublishOutput, folders.ProjectPath, null).Run();
+
+ var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot)
+ .Descendants("httpPlatform").Attributes("processPath").Single();
+
+ Assert.Equal($@"..\{projectName}.exe", processPath);
+
+ Directory.Delete(folders.TestRoot, recursive: true);
+ }
+
+ [Fact]
+ public void PublishIIS_uses_webroot_from_hosting_json()
+ {
+ var webRoot = "mywebroot";
+ var folders = CreateTestDir("{}", webRoot);
+ File.WriteAllText(Path.Combine(folders.ProjectPath, "hosting.json"), $"{{ \"webroot\": \"{webRoot}\"}}");
+
+ new PublishIISCommand(folders.PublishOutput, folders.ProjectPath, null).Run();
+
+ var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot)
+ .Descendants("httpPlatform").Attributes("processPath").Single();
+
+ Assert.Equal(@"..\projectDir.exe", processPath);
+
+ Directory.Delete(folders.TestRoot, recursive: true);
+ }
+
+ [Fact]
+ public void PublishIIS_webroot_switch_takes_precedence_over_hosting_json()
+ {
+ var webRoot = "mywebroot";
+ var folders = CreateTestDir("{}", webRoot);
+ File.WriteAllText(Path.Combine(folders.ProjectPath, "hosting.json"), $"{{ \"webroot\": \"wwwroot\"}}");
+
+ new PublishIISCommand(folders.PublishOutput, folders.ProjectPath, webRoot).Run();
+
+ var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot)
+ .Descendants("httpPlatform").Attributes("processPath").Single();
+
+ Assert.Equal(@"..\projectDir.exe", processPath);
+
+ Directory.Delete(folders.TestRoot, recursive: true);
+ }
+
+ [Theory]
+ [InlineData("projectDir")]
+ [InlineData("project.Dir")]
+ public void PublishIIS_accepts_path_to_project_json_as_project_path(string projectDir)
+ {
+ var webRoot = "wwwroot";
+ var folders = CreateTestDir("{}", webRoot, projectDir);
+
+ new PublishIISCommand(folders.PublishOutput, Path.Combine(folders.ProjectPath, "project.json"), null).Run();
+
+ var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot)
+ .Descendants("httpPlatform").Attributes("processPath").Single();
+
+ Assert.Equal($@"..\{projectDir}.exe", processPath);
+
+ Directory.Delete(folders.TestRoot, recursive: true);
+ }
+
+ [Fact]
+ public void PublishIIS_modifies_existing_web_config()
+ {
+ var webRoot = "wwwroot";
+ var folders = CreateTestDir("{}", webRoot);
+
+ File.WriteAllText(Path.Combine(folders.PublishOutput, webRoot, "web.config"),
+@"
+
+
+
+
+
+
+");
+
+ new PublishIISCommand(folders.PublishOutput, Path.Combine(folders.ProjectPath, "project.json"), null).Run();
+
+ var httpPlatformElement = GetPublishedWebConfig(folders.PublishOutput, webRoot)
+ .Descendants("httpPlatform").Single();
+
+ Assert.Equal(@"..\projectDir.exe", (string)httpPlatformElement.Attribute("processPath"));
+ Assert.Equal(@"1234", (string)httpPlatformElement.Attribute("startupTimeLimit"));
+
+ Directory.Delete(folders.TestRoot, recursive: true);
+ }
+
+ private XDocument GetPublishedWebConfig(string publishOut, string webRoot)
+ {
+ return XDocument.Load(Path.Combine(publishOut, webRoot, "web.config"));
+ }
+
+ private Folders CreateTestDir(string projectJson, string webRoot, string projectDir = "projectDir")
+ {
+ var testRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+ Directory.CreateDirectory(testRoot);
+
+ var projectPath = Path.Combine(testRoot, projectDir);
+ Directory.CreateDirectory(projectPath);
+ Directory.CreateDirectory(Path.Combine(projectPath, webRoot));
+ File.WriteAllText(Path.Combine(projectPath, "project.json"), projectJson);
+
+ var publishOut = Path.Combine(testRoot, "publishOut");
+ Directory.CreateDirectory(publishOut);
+ Directory.CreateDirectory(Path.Combine(publishOut, webRoot));
+
+ return new Folders { TestRoot = testRoot, ProjectPath = projectPath, PublishOutput = publishOut };
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs
new file mode 100644
index 0000000000..8c5cdee79c
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs
@@ -0,0 +1,202 @@
+using Xunit;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests
+{
+ public class WebConfigTransformFacts
+ {
+ private XDocument WebConfigTemplate => XDocument.Parse(
+@"
+
+
+
+
+
+
+
+
+
+
+");
+
+ [Fact]
+ public void WebConfigTransform_creates_new_config_if_one_does_not_exist()
+ {
+ Assert.True(XNode.DeepEquals(WebConfigTemplate,
+ WebConfigTransform.Transform(null, "test.exe", configureForAzure: false)));
+ }
+
+ [Fact]
+ public void WebConfigTransform_creates_new_config_if_one_has_unexpected_format()
+ {
+ Assert.True(XNode.DeepEquals(WebConfigTemplate,
+ WebConfigTransform.Transform(XDocument.Parse(""), "test.exe", configureForAzure: false)));
+ }
+
+ [Theory]
+ [InlineData(new object[] { new[] { "system.webServer" } })]
+ [InlineData(new object[] { new[] { "add" } })]
+ [InlineData(new object[] { new[] { "handlers" } })]
+ [InlineData(new object[] { new[] { "httpPlatform" } })]
+ [InlineData(new object[] { new[] { "environmentVariables" } })]
+ [InlineData(new object[] { new[] { "environmentVariable" } })]
+ [InlineData(new object[] { new[] { "handlers", "httpPlatform", "environmentVariables" } })]
+ public void WebConfigTransform_adds_missing_elements(string[] elementNames)
+ {
+ var input = WebConfigTemplate;
+ foreach (var elementName in elementNames)
+ {
+ input.Descendants(elementName).Remove();
+ }
+
+ Assert.True(XNode.DeepEquals(WebConfigTemplate,
+ WebConfigTransform.Transform(input, "test.exe", configureForAzure: false)));
+ }
+
+ [Theory]
+ [InlineData("add", "path", "test")]
+ [InlineData("add", "verb", "test")]
+ [InlineData("add", "modules", "mods")]
+ [InlineData("add", "resourceType", "Either")]
+ [InlineData("httpPlatform", "stdoutLogEnabled", "true")]
+ [InlineData("httpPlatform", "startupTimeLimit", "1200")]
+ [InlineData("httpPlatform", "arguments", "arg1")]
+ [InlineData("httpPlatform", "stdoutLogFile", "logfile.log")]
+ public void WebConfigTransform_wont_override_custom_values(string elementName, string attributeName, string attributeValue)
+ {
+ var input = WebConfigTemplate;
+ input.Descendants(elementName).Single().SetAttributeValue(attributeName, attributeValue);
+
+ var output = WebConfigTransform.Transform(input, "test.exe", configureForAzure: false);
+ Assert.Equal(attributeValue, (string)output.Descendants(elementName).Single().Attribute(attributeName));
+ }
+
+ [Fact]
+ public void WebConfigTransform_overwrites_processPath()
+ {
+ var newProcessPath =
+ (string)WebConfigTransform.Transform(WebConfigTemplate, "app.exe", configureForAzure: false)
+ .Descendants("httpPlatform").Single().Attribute("processPath");
+
+ Assert.Equal(@"..\app.exe", newProcessPath);
+ }
+
+ [Fact]
+ public void WebConfigTransform_fixes_httpPlatformHandler_casing()
+ {
+ var input = WebConfigTemplate;
+ input.Descendants("add").Single().SetAttributeValue("name", "httpplatformhandler");
+
+ Assert.True(XNode.DeepEquals(WebConfigTemplate,
+ WebConfigTransform.Transform(input, "test.exe", configureForAzure: false)));
+ }
+
+ [Fact]
+ public void WebConfigTransform_does_not_remove_children_of_httpPlatform_element()
+ {
+ var envVarElement =
+ new XElement("environmentVariable", new XAttribute("name", "ENVVAR"), new XAttribute("value", "123"));
+
+ var input = WebConfigTemplate;
+ input.Descendants("environmentVariable").Single().Add(envVarElement);
+
+ Assert.True(XNode.DeepEquals(envVarElement,
+ WebConfigTransform.Transform(input, "app.exe", configureForAzure: false)
+ .Descendants("environmentVariable").SingleOrDefault(e => (string)e.Attribute("name") == "ENVVAR")));
+ }
+
+ [Fact]
+ public void WebConfigTransform_adds_stdoutLogEnabled_if_attribute_is_missing()
+ {
+ var input = WebConfigTemplate;
+ input.Descendants("httpPlatform").Attributes("stdoutLogEnabled").Remove();
+
+ Assert.Equal(
+ "false",
+ (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false)
+ .Descendants().Attributes("stdoutLogEnabled").Single());
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("false")]
+ [InlineData("true")]
+ public void WebConfigTransform_adds_stdoutLogFile_if_attribute_is_missing(string stdoutLogFile)
+ {
+ var input = WebConfigTemplate;
+
+ var httpPlatformElement = input.Descendants("httpPlatform").Single();
+ httpPlatformElement.Attribute("stdoutLogEnabled").Remove();
+ if (stdoutLogFile != null)
+ {
+ httpPlatformElement.SetAttributeValue("stdoutLogEnabled", stdoutLogFile);
+ }
+
+ Assert.Equal(
+ @"..\logs\stdout.log",
+ (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false)
+ .Descendants().Attributes("stdoutLogFile").Single());
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("true")]
+ [InlineData("false")]
+ public void WebConfigTransform_does_not_change_existing_stdoutLogEnabled(string stdoutLogEnabledValue)
+ {
+ var input = WebConfigTemplate;
+ var httpPlatformElement = input.Descendants("httpPlatform").Single();
+
+ httpPlatformElement.SetAttributeValue("stdoutLogFile", "mylog.txt");
+ httpPlatformElement.Attributes("stdoutLogEnabled").Remove();
+ if (stdoutLogEnabledValue != null)
+ {
+ input.Descendants("httpPlatform").Single().SetAttributeValue("stdoutLogEnabled", stdoutLogEnabledValue);
+ }
+
+ Assert.Equal(
+ "mylog.txt",
+ (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false)
+ .Descendants().Attributes("stdoutLogFile").Single());
+ }
+
+ [Fact]
+ public void WebConfigTransform_correctly_configures_for_Azure()
+ {
+ var input = WebConfigTemplate;
+ input.Descendants("httpPlatform").Attributes().Remove();
+
+ var httPlatformElement = WebConfigTransform.Transform(input, "test.exe", configureForAzure: true)
+ .Descendants("httpPlatform").Single();
+ httPlatformElement.Elements().Remove();
+
+ Assert.True(XNode.DeepEquals(
+ XDocument.Parse(@"").Root,
+ httPlatformElement));
+ }
+
+ [Fact]
+ public void WebConfigTransform_overrites_value_for_ASPNET_APPLICATIONBASE()
+ {
+ var input = WebConfigTemplate;
+ input.Descendants("environmentVariable").Single().SetAttributeValue("value", "abc");
+
+ Assert.True(XNode.DeepEquals(WebConfigTemplate,
+ WebConfigTransform.Transform(input, "test.exe", configureForAzure: false)));
+ }
+
+ private bool VerifyMissingElementCreated(params string[] elementNames)
+ {
+ var input = WebConfigTemplate;
+ foreach (var elementName in elementNames)
+ {
+ input.Descendants(elementName).Remove();
+ }
+
+ return XNode.DeepEquals(WebConfigTemplate,
+ WebConfigTransform.Transform(input, "test.exe", configureForAzure: false));
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/project.json b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/project.json
new file mode 100644
index 0000000000..4a3f14c43f
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/project.json
@@ -0,0 +1,16 @@
+{
+ "dependencies": {
+ "xunit": "2.1.0",
+ "dotnet-publish-iis": "1.0.0-*",
+ "Microsoft.NETCore.Platforms": "1.0.1-*"
+ },
+ "frameworks": {
+ "dnxcore50": {
+ "imports": "portable-net451+win8",
+ "dependencies": {
+ "dotnet-test-xunit": "1.0.0-dev-*"
+ }
+ }
+ },
+ "testRunner": "xunit"
+}