// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.IO; using System.Threading; using Microsoft.Extensions.PlatformAbstractions; using System.Security.Principal; using System.Security.AccessControl; namespace AspNetCoreModule.Test.Framework { public static class TestFlags { public const string SkipTest = "SkipTest"; public const string UsePrivateANCM = "UsePrivateANCM"; public const string UseIISExpress = "UseIISExpress"; public const string UseFullIIS = "UseFullIIS"; public const string RunAsAdministrator = "RunAsAdministrator"; public const string MakeCertExeAvailable = "MakeCertExeAvailable"; public const string X86Platform = "X86Platform"; public const string Wow64BitMode = "Wow64BitMode"; public const string RequireRunAsAdministrator = "RequireRunAsAdministrator"; public const string Default = "Default"; public static bool Enabled(string flagValue) { return InitializeTestMachine.GlobalTestFlags.Contains(flagValue.ToLower()); } } public class InitializeTestMachine : IDisposable { public const string ANCMTestFlagsEnvironmentVariable = "%ANCMTestFlags%"; public static int SiteId = 40000; public const string PrivateFileName = "aspnetcore_private.dll"; public static string FullIisAspnetcore_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", PrivateFileName); public static string FullIisAspnetcore_path_original = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "aspnetcore.dll"); public static string FullIisAspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "inetsrv", PrivateFileName); public static string IisExpressAspnetcore_path; public static string IisExpressAspnetcore_X86_path; public static string IisExpressAspnetcoreSchema_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", "config", "schema", "aspnetcore_schema.xml"); public static string IisExpressAspnetcoreSchema_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "IIS Express", "config", "schema", "aspnetcore_schema.xml"); public static string FullIisAspnetcoreSchema_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "config", "schema", "aspnetcore_schema.xml"); public static int _referenceCount = 0; private static bool _InitializeTestMachineCompleted = false; private string _setupScriptPath = null; private static bool? _makeCertExeAvailable = null; public static bool MakeCertExeAvailable { get { if (_makeCertExeAvailable == null) { _makeCertExeAvailable = false; try { string makecertExeFilePath = TestUtility.GetMakeCertPath(); TestUtility.RunCommand(makecertExeFilePath, null, true, true); TestUtility.LogInformation("Verified makecert.exe is available : " + makecertExeFilePath); _makeCertExeAvailable = true; } catch { _makeCertExeAvailable = false; } } return (_makeCertExeAvailable == true); } } public static string TestRootDirectory { get { return Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "_ANCMTest"); } } private static string _globalTestFlags = null; public static string GlobalTestFlags { get { if (_globalTestFlags == null) { bool isElevated; WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent()); isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator); // check if this test process is started with the Run As Administrator start option _globalTestFlags = Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable); // // Check if ANCMTestFlags environment is not defined and the test program was started // without using the Run As Administrator start option. // In that case, we have to use the default TestFlags of UseIISExpress and UsePrivateANCM // if (!isElevated) { if (_globalTestFlags.ToLower().Contains("%" + ANCMTestFlagsEnvironmentVariable.ToLower() + "%")) { _globalTestFlags = TestFlags.UsePrivateANCM + ";" + TestFlags.UseIISExpress; } } // // convert in lower case // _globalTestFlags = _globalTestFlags.ToLower(); // // error handling: UseIISExpress and UseFullIIS can be used together. // if (_globalTestFlags.Contains(TestFlags.UseIISExpress.ToLower()) && _globalTestFlags.Contains(TestFlags.UseFullIIS.ToLower())) { _globalTestFlags = _globalTestFlags.Replace(TestFlags.UseFullIIS.ToLower(), ""); } // // adjust the default test context in run time to figure out wrong test context values // if (isElevated) { // add RunAsAdministrator if (!_globalTestFlags.Contains(TestFlags.RunAsAdministrator.ToLower())) { TestUtility.LogInformation("Added test context of " + TestFlags.RunAsAdministrator); _globalTestFlags += ";" + TestFlags.RunAsAdministrator; } } else { // add UseIISExpress if (!_globalTestFlags.Contains(TestFlags.UseIISExpress.ToLower())) { TestUtility.LogInformation("Added test context of " + TestFlags.UseIISExpress); _globalTestFlags += ";" + TestFlags.UseIISExpress; } // remove UseFullIIS if (_globalTestFlags.Contains(TestFlags.UseFullIIS.ToLower())) { _globalTestFlags = _globalTestFlags.Replace(TestFlags.UseFullIIS.ToLower(), ""); } // remove RunAsAdmistrator if (_globalTestFlags.Contains(TestFlags.RunAsAdministrator.ToLower())) { _globalTestFlags = _globalTestFlags.Replace(TestFlags.RunAsAdministrator.ToLower(), ""); } } if (MakeCertExeAvailable) { // Add MakeCertExeAvailable if (!_globalTestFlags.Contains(TestFlags.MakeCertExeAvailable.ToLower())) { TestUtility.LogInformation("Added test context of " + TestFlags.MakeCertExeAvailable); _globalTestFlags += ";" + TestFlags.MakeCertExeAvailable; } } if (!Environment.Is64BitOperatingSystem) { // Add X86Platform if (!_globalTestFlags.Contains(TestFlags.X86Platform.ToLower())) { TestUtility.LogInformation("Added test context of " + TestFlags.X86Platform); _globalTestFlags += ";" + TestFlags.X86Platform; } } if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) { // Add Wow64bitMode if (!_globalTestFlags.Contains(TestFlags.Wow64BitMode.ToLower())) { TestUtility.LogInformation("Added test context of " + TestFlags.Wow64BitMode); _globalTestFlags += ";" + TestFlags.Wow64BitMode; } // remove X86Platform if (_globalTestFlags.Contains(TestFlags.X86Platform.ToLower())) { _globalTestFlags = _globalTestFlags.Replace(TestFlags.X86Platform.ToLower(), ""); } } _globalTestFlags = _globalTestFlags.ToLower(); } return _globalTestFlags; } } public void InitializeIISServer() { // Check if IIS server is installed or not bool isIISInstalled = true; if (!File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiscore.dll"))) { isIISInstalled = false; } if (!File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "config", "applicationhost.config"))) { isIISInstalled = false; } if (!isIISInstalled) { throw new System.ApplicationException("IIS server is not installed"); } // Check websocket is installed if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiswsock.dll"))) { TestUtility.LogInformation("Websocket is installed"); } else { throw new System.ApplicationException("websocket module is not installed"); } // Clean up IIS worker process TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); // Reset applicationhost.config TestUtility.LogInformation("Restoring applicationhost.config"); IISConfigUtility.RestoreAppHostConfig(restoreFromMasterBackupFile: true); TestUtility.StartW3svc(); // check w3svc is running after resetting applicationhost.config if (IISConfigUtility.GetServiceStatus("w3svc") == "Running") { TestUtility.LogInformation("W3SVC service is restarted after restoring applicationhost.config"); } else { throw new System.ApplicationException("WWW service can't start"); } // check URLRewrite module exists if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "rewrite.dll"))) { TestUtility.LogInformation("Verified URL Rewrite module installed for IIS server"); } else { throw new System.ApplicationException("URL Rewrite module is not installed"); } if (IISConfigUtility.ApppHostTemporaryBackupFileExtention == null) { throw new System.ApplicationException("Failed to backup applicationhost.config"); } } public InitializeTestMachine() { _referenceCount++; // This method should be called only one time if (_referenceCount == 1) { TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Start"); _InitializeTestMachineCompleted = false; TestUtility.LogInformation("InitializeTestMachine::Start"); if (Environment.ExpandEnvironmentVariables("%ANCMTEST_DEBUG%").ToLower() == "true") { System.Diagnostics.Debugger.Launch(); } // // Clean up IISExpress processes // TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); // // Initalize IIS server // if (TestFlags.Enabled(TestFlags.UseFullIIS)) { InitializeIISServer(); } string siteRootPath = TestRootDirectory; if (!Directory.Exists(siteRootPath)) { // // Create a new directory and set the write permission for the SID of AuthenticatedUser // Directory.CreateDirectory(siteRootPath); DirectorySecurity sec = Directory.GetAccessControl(siteRootPath); SecurityIdentifier authenticatedUser = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null); sec.AddAccessRule(new FileSystemAccessRule(authenticatedUser, FileSystemRights.Modify | FileSystemRights.Synchronize, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow)); Directory.SetAccessControl(siteRootPath, sec); } foreach (string directory in Directory.GetDirectories(siteRootPath)) { bool successDeleteChildDirectory = true; try { TestUtility.DeleteDirectory(directory); } catch { successDeleteChildDirectory = false; TestUtility.LogInformation("Failed to delete " + directory); } if (successDeleteChildDirectory) { try { TestUtility.DeleteDirectory(siteRootPath); } catch { TestUtility.LogInformation("Failed to delete " + siteRootPath); } } } // // Intialize Private ANCM files for Full IIS server or IISExpress // if (TestFlags.Enabled(TestFlags.UsePrivateANCM)) { PreparePrivateANCMFiles(); } _InitializeTestMachineCompleted = true; TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() End"); } for (int i=0; i<120; i++) { if (_InitializeTestMachineCompleted) { break; } else { TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Waiting..."); Thread.Sleep(500); } } if (!_InitializeTestMachineCompleted) { throw new System.ApplicationException("InitializeTestMachine failed"); } } public void Dispose() { _referenceCount--; if (_referenceCount == 0) { TestUtility.LogInformation("InitializeTestMachine::Dispose() Start"); TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); RollbackIISApplicationhostConfigFile(); TestUtility.LogInformation("InitializeTestMachine::Dispose() End"); } } private void RollbackIISApplicationhostConfigFile() { if (IISConfigUtility.ApppHostTemporaryBackupFileExtention != null) { try { TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); } catch { TestUtility.LogInformation("Failed to stop IIS worker processes"); } try { IISConfigUtility.RestoreAppHostConfig(restoreFromMasterBackupFile: false); } catch { TestUtility.LogInformation("Failed to rollback applicationhost.config"); } try { TestUtility.StartW3svc(); } catch { TestUtility.LogInformation("Failed to start w3svc"); } IISConfigUtility.ApppHostTemporaryBackupFileExtention = null; } } private void PreparePrivateANCMFiles() { var solutionRoot = GetSolutionDirectory(); string outputPath = string.Empty; _setupScriptPath = Path.Combine(solutionRoot, "tools"); // First try with release build outputPath = Path.Combine(solutionRoot, "artifacts", "build", "AspNetCore", "bin", "Release"); // If release build is not available, try with debug build if (!File.Exists(Path.Combine(outputPath, "Win32", "aspnetcore.dll")) || !File.Exists(Path.Combine(outputPath, "x64", "aspnetcore.dll")) || !File.Exists(Path.Combine(outputPath, "x64", "aspnetcore_schema.xml"))) { outputPath = Path.Combine(solutionRoot, "artifacts", "build", "AspNetCore", "bin", "Debug"); } if (!File.Exists(Path.Combine(outputPath, "Win32", "aspnetcore.dll")) || !File.Exists(Path.Combine(outputPath, "x64", "aspnetcore.dll")) || !File.Exists(Path.Combine(outputPath, "x64", "aspnetcore_schema.xml"))) { throw new ApplicationException("aspnetcore.dll is not available; check if there is any build issue!!!"); } // // NOTE: // ANCM schema file can't be overwritten here // If there is any schema change, that should be updated with installing setup or manually copied with the new schema file. // if (TestFlags.Enabled(TestFlags.UseIISExpress)) { // // Initialize 32 bit IisExpressAspnetcore_path // IisExpressAspnetcore_path = Path.Combine(outputPath, "x64", "aspnetcore.dll"); IisExpressAspnetcore_X86_path = Path.Combine(outputPath, "Win32", "aspnetcore.dll"); } else // if use Full IIS server { bool updateSuccess = false; for (int i = 0; i < 3; i++) { updateSuccess = false; try { TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); TestUtility.ResetHelper(ResetHelperMode.StopW3svcStartW3svc); Thread.Sleep(1000); // Copy private file on Inetsrv directory TestUtility.FileCopy(Path.Combine(outputPath, "x64", "aspnetcore.dll"), FullIisAspnetcore_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); if (TestUtility.IsOSAmd64) { // Copy 32bit private file on Inetsrv directory TestUtility.FileCopy(Path.Combine(outputPath, "Win32", "aspnetcore.dll"), FullIisAspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); } updateSuccess = true; } catch { updateSuccess = false; } if (updateSuccess) { break; } } if (!updateSuccess) { throw new System.ApplicationException("Failed to update aspnetcore.dll"); } // update applicationhost.config for IIS server with the new private ASPNET Core file name if (TestFlags.Enabled(TestFlags.UseFullIIS)) { using (var iisConfig = new IISConfigUtility(ServerType.IIS, null)) { iisConfig.AddModule("AspNetCoreModule", FullIisAspnetcore_path, null); } } } } public static string GetSolutionDirectory() { var applicationBasePath = PlatformServices.Default.Application.ApplicationBasePath; var directoryInfo = new DirectoryInfo(applicationBasePath); do { var solutionFile = new FileInfo(Path.Combine(directoryInfo.FullName, "AspNetCoreModule.sln")); if (solutionFile.Exists) { return directoryInfo.FullName; } directoryInfo = directoryInfo.Parent; } while (directoryInfo.Parent != null); throw new Exception($"Solution root could not be located using application root {applicationBasePath}."); } } }