// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // - Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // - Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // - Neither the name of the Outercurve Foundation nor the names of its // contributors may be used to endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON // ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using System; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Data; using System.Reflection; using System.Collections; using System.Collections.Specialized; using System.Collections.Generic; using System.Xml; using System.Net.Mail; using System.Web; using System.Web.Caching; using WebsitePanel.Providers; using WebsitePanel.Providers.Web; using WebsitePanel.Providers.FTP; using WebsitePanel.Providers.Mail; using WebsitePanel.Providers.Database; using WebsitePanel.Providers.OS; using OS = WebsitePanel.Providers.OS; namespace WebsitePanel.EnterpriseServer { public class WebApplicationsInstaller { public const string PROPERTY_CONTENT_PATH = "installer.contentpath"; public const string PROPERTY_ABSOLUTE_CONTENT_PATH = "installer.absolute.contentpath"; public const string PROPERTY_VDIR_CREATED = "installer.virtualdircreated"; public const string PROPERTY_DATABASE_CREATED = "installer.databasecreated"; public const string PROPERTY_USER_CREATED = "installer.usercreated"; public const string PROPERTY_INSTALLED_FILES = "installer.installedfiles"; public const string PROPERTY_DELETE_FILES = "installer.deletefiles"; public const string PROPERTY_DELETE_VDIR = "installer.deletevdir"; public const string PROPERTY_DELETE_SQL = "installer.deletesql"; public const string PROPERTY_DELETE_DATABASE = "installer.deletedatabase"; public const string PROPERTY_DELETE_USER = "installer.deleteuser"; public static int InstallApplication(InstallationInfo inst) { // check account int accountCheck = SecurityContext.CheckAccount(DemandAccount.NotDemo | DemandAccount.IsActive); if (accountCheck < 0) return accountCheck; // check package int packageCheck = SecurityContext.CheckPackage(inst.PackageId, DemandPackage.IsActive); if (packageCheck < 0) return packageCheck; // install application WebApplicationsInstaller installer = new WebApplicationsInstaller(); return installer.InstallWebApplication(inst); } ApplicationInfo app = null; private string contentPath = null; private string siteId = null; private OS.OperatingSystem os = null; private DatabaseServer sql = null; private string serverIpAddressExternal = null; private string serverIpAddressInternal = null; private string webSiteName = ""; public int InstallWebApplication(InstallationInfo inst) { // place log record TaskManager.StartTask("APP_INSTALLER", "INSTALL_APPLICATION", inst.PackageId); TaskManager.WriteParameter("Virtual directory", inst.VirtualDir); TaskManager.WriteParameter("Database group", inst.DatabaseGroup); try { // get application info app = GetApplication(inst.PackageId, inst.ApplicationId); BackgroundTask topTask = TaskController.GetTopTask(); topTask.ItemName = app.Name; TaskController.UpdateTask(topTask); // check web site for existance WebSite webSite = WebServerController.GetWebSite(inst.WebSiteId); if (webSite == null) return BusinessErrorCodes.ERROR_WEB_INSTALLER_WEBSITE_NOT_EXISTS; TaskManager.WriteParameter("Web site", webSite.Name); webSiteName = webSite.Name; siteId = webSite.SiteId; // change web site properties if required if (String.IsNullOrEmpty(inst.VirtualDir)) { ChangeVirtualDirectoryProperties(webSite, app.WebSettings); WebServerController.UpdateWebSite(webSite); } // get OS service int osId = PackageController.GetPackageServiceId(inst.PackageId, "os"); os = new OS.OperatingSystem(); ServiceProviderProxy.Init(os, osId); // get remote content path contentPath = webSite.ContentPath; // create virtual dir if required if (!String.IsNullOrEmpty(inst.VirtualDir)) { // check if the required virtual dir already exists contentPath = Path.Combine(contentPath, inst.VirtualDir); WebVirtualDirectory vdir = null; int result = WebServerController.AddVirtualDirectory(inst.WebSiteId, inst.VirtualDir, contentPath); if (result == BusinessErrorCodes.ERROR_VDIR_ALREADY_EXISTS) { // the directory alredy exists vdir = WebServerController.GetVirtualDirectory( inst.WebSiteId, inst.VirtualDir); contentPath = vdir.ContentPath; } else { vdir = WebServerController.GetVirtualDirectory( inst.WebSiteId, inst.VirtualDir); inst[PROPERTY_VDIR_CREATED] = "True"; } // change virtual directory properties if required ChangeVirtualDirectoryProperties(vdir, app.WebSettings); WebServerController.UpdateVirtualDirectory(inst.WebSiteId, vdir); } // deploy application codebase ZIP and then unpack it string codebasePath = app.Codebase; string remoteCodebasePath = Path.Combine(contentPath, Path.GetFileName(app.Codebase)); // make content path absolute string absContentPath = FilesController.GetFullPackagePath(inst.PackageId, contentPath); // save content path inst[PROPERTY_CONTENT_PATH] = contentPath; inst[PROPERTY_ABSOLUTE_CONTENT_PATH] = absContentPath; // copy ZIP to the target server FileStream stream = File.OpenRead(codebasePath); int BUFFER_LENGTH = 5000000; byte[] buffer = new byte[BUFFER_LENGTH]; int readBytes = 0; while (true) { readBytes = stream.Read(buffer, 0, BUFFER_LENGTH); if (readBytes < BUFFER_LENGTH) Array.Resize(ref buffer, readBytes); FilesController.AppendFileBinaryChunk(inst.PackageId, remoteCodebasePath, buffer); if (readBytes < BUFFER_LENGTH) break; } // unpack codebase inst[PROPERTY_INSTALLED_FILES] = String.Join(";", FilesController.UnzipFiles(inst.PackageId, new string[] { remoteCodebasePath })); // delete codebase zip FilesController.DeleteFiles(inst.PackageId, new string[] { remoteCodebasePath }); // check/create databases if (!String.IsNullOrEmpty(inst.DatabaseGroup) && String.Compare(inst.DatabaseGroup, "None", true) != 0) { // database if (inst.DatabaseId == 0) { TaskManager.WriteParameter("Database name", inst.DatabaseName); // we should create a new database SqlDatabase db = new SqlDatabase(); db.PackageId = inst.PackageId; db.Name = inst.DatabaseName; inst.DatabaseId = DatabaseServerController.AddSqlDatabase(db, inst.DatabaseGroup); if (inst.DatabaseId < 0) { // rollback installation RollbackInstallation(inst); // return error return inst.DatabaseId; // there was an error when creating database } inst[PROPERTY_DATABASE_CREATED] = "True"; } else { // existing database SqlDatabase db = DatabaseServerController.GetSqlDatabase(inst.DatabaseId); inst.DatabaseName = db.Name; TaskManager.WriteParameter("Database name", inst.DatabaseName); } SqlUser user = null; // database user if (inst.UserId == 0) { TaskManager.WriteParameter("Database user", inst.Username); // NEW USER user = new SqlUser(); user.PackageId = inst.PackageId; user.Name = inst.Username; user.Databases = new string[] { inst.DatabaseName }; user.Password = inst.Password; inst.UserId = DatabaseServerController.AddSqlUser(user, inst.DatabaseGroup); if (inst.UserId < 0) { // rollback installation RollbackInstallation(inst); // return error return inst.UserId; // error while adding user } inst[PROPERTY_USER_CREATED] = "True"; } else { // EXISTING USER user = DatabaseServerController.GetSqlUser(inst.UserId); inst.Username = user.Name; TaskManager.WriteParameter("Database user", inst.Username); List databases = new List(); databases.AddRange(user.Databases); if (!databases.Contains(inst.DatabaseName)) { databases.Add(inst.DatabaseName); user.Databases = databases.ToArray(); DatabaseServerController.UpdateSqlUser(user); } } // check connectivity with SQL Server and credentials provided // load user item int sqlServiceId = PackageController.GetPackageServiceId(inst.PackageId, inst.DatabaseGroup); sql = new DatabaseServer(); ServiceProviderProxy.Init(sql, sqlServiceId); if (!sql.CheckConnectivity(inst.DatabaseName, inst.Username, inst.Password)) { // can't connect to the database RollbackInstallation(inst); return BusinessErrorCodes.ERROR_WEB_INSTALLER_CANT_CONNECT_DATABASE; } // read SQL server settings StringDictionary settings = ServerController.GetServiceSettings(sqlServiceId); serverIpAddressExternal = settings["ExternalAddress"]; if (settings.ContainsKey("InternalAddress")) { serverIpAddressInternal = settings["InternalAddress"]; } } // ********* RUN INSTALL SCENARIO *********** int scriptResult = RunInstallScenario(inst); if (scriptResult < 0) { // rollback installation RollbackInstallation(inst); // return error return scriptResult; } // add new installation to the database return 0; } catch (Exception ex) { // rollback installation RollbackInstallation(inst); throw TaskManager.WriteError(ex); } finally { TaskManager.CompleteTask(); } } private void ChangeVirtualDirectoryProperties(WebVirtualDirectory vdir, ApplicationWebSetting[] settings) { if (settings == null) return; // get type properties Type vdirType = vdir.GetType(); foreach (ApplicationWebSetting setting in settings) { PropertyInfo prop = vdirType.GetProperty(setting.Name, BindingFlags.Public | BindingFlags.Instance); if (prop != null) { prop.SetValue(vdir, ObjectUtils.Cast(setting.Value, prop.PropertyType), null); } } } private int RunInstallScenario(InstallationInfo inst) { string scenarioPath = Path.Combine(app.Folder, "Install.xml"); return RunScenario(scenarioPath, inst, true); } private string GetFullPathToInstallFolder(int userId) { string userhomeFolder = String.Empty; string[] osSesstings = os.ServiceProviderSettingsSoapHeaderValue.Settings; foreach (string s in osSesstings) { if (s.Contains("usershome")) { string[] split = s.Split(new char[] {'='}); userhomeFolder = split[1]; } } UserInfo info = UserController.GetUser(userId); return Path.Combine(userhomeFolder, info.Username); } private int RunScenario(string scenarioPath, InstallationInfo inst, bool throwExceptions) { // load XML document XmlDocument docScenario = new XmlDocument(); docScenario.Load(scenarioPath); // go through "check" section XmlNode nodeCheck = docScenario.SelectSingleNode("//check"); if (nodeCheck != null) { foreach (XmlNode nodeStep in nodeCheck.ChildNodes) { if (nodeStep.Name == "fileExists") { /* // check if the specified file exists string fileName = nodeStep.Attributes["path"].Value; fileName = ExpandVariables(fileName, inst); if (fileName.StartsWith("\\")) { fileName = fileName.Substring(1); } //get full path to instal folder PackageInfo package = PackageController.GetPackage(inst.PackageId); string fullPath = Path.Combine(GetFullPathToInstallFolder(package.UserId), fileName); if (os.FileExists(fullPath)) return BusinessErrorCodes.ERROR_WEB_INSTALLER_TARGET_WEBSITE_UNSUITABLE; */ } else if (nodeStep.Name == "sql") { string cmdText = nodeStep.InnerText; cmdText = ExpandVariables(cmdText, inst); DataSet dsResults = sql.ExecuteSqlQuery(inst.DatabaseName, cmdText); if (dsResults.Tables[0].Rows.Count > 0) return BusinessErrorCodes.ERROR_WEB_INSTALLER_TARGET_DATABASE_UNSUITABLE; } } } // go through "commands" section XmlNode nodeCommands = docScenario.SelectSingleNode("//commands"); if (nodeCommands != null) { foreach (XmlNode nodeCommand in nodeCommands.ChildNodes) { if (nodeCommand.Name == "processFile") { // process remote file string fileName = nodeCommand.Attributes["path"].Value; fileName = ExpandVariables(fileName, inst); byte[] fileBinaryContent = FilesController.GetFileBinaryContent(inst.PackageId, fileName); if (fileBinaryContent == null) throw new Exception("Could not process scenario file: " + fileName); string fileContent = Encoding.UTF8.GetString(fileBinaryContent); fileContent = ExpandVariables(fileContent, inst); FilesController.UpdateFileBinaryContent(inst.PackageId, fileName, Encoding.UTF8.GetBytes(fileContent)); } else if (nodeCommand.Name == "runSql") { string cmdText = nodeCommand.InnerText; if (nodeCommand.Attributes["path"] != null) { // load SQL from file string sqlPath = Path.Combine(app.Folder, nodeCommand.Attributes["path"].Value); if (!File.Exists(sqlPath)) continue; StreamReader reader = new StreamReader(sqlPath); cmdText = reader.ReadToEnd(); reader.Close(); } bool run = true; if (nodeCommand.Attributes["dependsOnProperty"] != null) { string[] propNames = nodeCommand.Attributes["dependsOnProperty"].Value.Split(','); foreach (string propName in propNames) { if (inst[propName.Trim()] == null) { run = false; break; } } } if (run) { try { cmdText = ExpandVariables(cmdText, inst); sql.ExecuteSqlNonQuerySafe(inst.DatabaseName, inst.Username, inst.Password, cmdText); } catch (Exception ex) { if (throwExceptions) throw ex; } } } } } return 0; } string appUrls = null; private string ExpandVariables(string str, InstallationInfo inst) { str = ReplaceTemplateVariable(str, "installer.contentpath", inst[PROPERTY_CONTENT_PATH]); str = ReplaceTemplateVariable(str, "installer.website", webSiteName); str = ReplaceTemplateVariable(str, "installer.virtualdir", inst.VirtualDir); string fullWebPath = webSiteName; if (!String.IsNullOrEmpty(inst.VirtualDir)) fullWebPath += "/" + inst.VirtualDir; // try to load domain info DomainInfo domain = ServerController.GetDomain(webSiteName); string fullWebPathPrefix = (domain != null && domain.IsSubDomain) ? "" : "www."; // app URLs if (appUrls == null) { // read web pointers List sitePointers = WebServerController.GetWebSitePointers(inst.WebSiteId); StringBuilder sb = new StringBuilder(); sb.Append(""); sb.Append(""); foreach (DomainInfo pointer in sitePointers) { string pointerWebPath = pointer.DomainName; if (!String.IsNullOrEmpty(inst.VirtualDir)) pointerWebPath += "/" + inst.VirtualDir; sb.Append(""); } sb.Append(""); appUrls = sb.ToString(); } str = ReplaceTemplateVariable(str, "installer.appurls", appUrls); string slashVirtualDir = ""; if (!String.IsNullOrEmpty(inst.VirtualDir)) slashVirtualDir = "/" + inst.VirtualDir; str = ReplaceTemplateVariable(str, "installer.slashvirtualdir", slashVirtualDir); str = ReplaceTemplateVariable(str, "installer.website.www", fullWebPathPrefix + webSiteName); str = ReplaceTemplateVariable(str, "installer.fullwebpath", fullWebPath); str = ReplaceTemplateVariable(str, "installer.fullwebpath.www", fullWebPathPrefix + fullWebPath); //Replace ObjectQualifierNormalized which is not defined on portal str = ReplaceTemplateVariable(str, "ObjectQualifierNormalized", ""); /* * Application installer variable 'installer.database.server' is obsolete * and should not be used to install Application Packs. * Instead, please use the following two variables: * - installer.database.server.external - defines external database address * - installer.database.server.internal - defines internal database address * * See TFS Issue 952 for details. */ //apply external database address str = ReplaceTemplateVariable(str, "installer.database.server", ((serverIpAddressExternal != null) ? serverIpAddressExternal : "")); str = ReplaceTemplateVariable(str, "installer.database.server.external", ((serverIpAddressExternal != null) ? serverIpAddressExternal : String.Empty)); //apply internal database address str = ReplaceTemplateVariable(str, "installer.database.server.internal", ((serverIpAddressInternal != null) ? serverIpAddressInternal : String.Empty)); str = ReplaceTemplateVariable(str, "installer.database", inst.DatabaseName); str = ReplaceTemplateVariable(str, "installer.database.user", inst.Username); str = ReplaceTemplateVariable(str, "installer.database.password", ((inst.Password != null) ? inst.Password : "")); foreach (string[] pair in inst.PropertiesArray) str = ReplaceTemplateVariable(str, pair[0], pair[1]); return str; } private string ReplaceTemplateVariable(string str, string varName, string varValue) { if (String.IsNullOrEmpty(str) || String.IsNullOrEmpty(varName)) return str; str = Regex.Replace(str, "\\$\\{" + varName + "\\}+", varValue, RegexOptions.IgnoreCase); str = Regex.Replace(str, "\\$\\{" + varName + ".mysql-escaped\\}+", EscapeMySql(varValue), RegexOptions.IgnoreCase); return Regex.Replace(str, "\\$\\{" + varName + ".mssql-escaped\\}+", EscapeMsSql(varValue), RegexOptions.IgnoreCase); } private string EscapeMySql(string str) { if (String.IsNullOrEmpty(str)) return str; return str.Replace("'", "\\'") .Replace("\"", "\\\"") .Replace("\n", "\\n") .Replace("\r", "\\r") .Replace("\t", "\\t") .Replace("\\", "\\\\") .Replace("\0", "\\0"); } private string EscapeMsSql(string str) { if (String.IsNullOrEmpty(str)) return str; return str.Replace("'", "''"); } private void RollbackInstallation(InstallationInfo inst) { // remove virtual dir if (inst[PROPERTY_VDIR_CREATED] != null) { // delete virtual directory WebServerController.DeleteVirtualDirectory(inst.WebSiteId, inst.VirtualDir); // delete folder FilesController.DeleteFiles(inst.PackageId, new string[] { inst[PROPERTY_CONTENT_PATH] }); } // remove database if (inst[PROPERTY_DATABASE_CREATED] != null) DatabaseServerController.DeleteSqlDatabase(inst.DatabaseId); // remove database user if (inst[PROPERTY_USER_CREATED] != null) DatabaseServerController.DeleteSqlUser(inst.UserId); } public static List GetCategories() { List categories = null; string key = "WebApplicationCategories"; // look up in the cache if (HttpContext.Current != null) categories = (List)HttpContext.Current.Cache[key]; if (categories == null) { string catsPath = Path.Combine(ConfigSettings.WebApplicationsPath, "Applications.xml"); if (File.Exists(catsPath)) { categories = new List(); // parse file XmlDocument doc = new XmlDocument(); doc.Load(catsPath); XmlNodeList nodesCategories = doc.SelectNodes("categories/category"); foreach (XmlNode nodeCategory in nodesCategories) { ApplicationCategory category = new ApplicationCategory(); category.Id = nodeCategory.Attributes["id"].Value; category.Name = GetNodeValue(nodeCategory, "name", category.Id); categories.Add(category); // read applications List catApps = new List(); XmlNodeList nodesApps = nodeCategory.SelectNodes("applications/application"); foreach (XmlNode nodeApp in nodesApps) catApps.Add(nodeApp.Attributes["name"].Value); category.Applications = catApps.ToArray(); } } // place to the cache if (HttpContext.Current != null) HttpContext.Current.Cache.Insert(key, categories, new CacheDependency(catsPath)); } return categories; } public static List GetApplications(int packageId) { return GetApplications(packageId, null); } public static List GetApplications(int packageId, string categoryId) { string key = "WebApplicationsList"; Dictionary apps = null; // look up in the cache if(HttpContext.Current != null) apps = (Dictionary)HttpContext.Current.Cache[key]; if (apps == null) { // create apps list apps = new Dictionary(); string appsRoot = ConfigSettings.WebApplicationsPath; string[] dirs = Directory.GetDirectories(appsRoot); foreach (string dir in dirs) { string appFile = Path.Combine(dir, "Application.xml"); if (!File.Exists(appFile)) continue; // read and parse web applications xml file XmlDocument doc = new XmlDocument(); doc.Load(appFile); XmlNode nodeApp = doc.SelectSingleNode("//application"); string appFolder = dir; // parse node ApplicationInfo app = CreateApplicationInfoFromXml(appFolder, nodeApp); // add to the collection apps.Add(app.Id, app); } // place to the cache if (HttpContext.Current != null) HttpContext.Current.Cache.Insert(key, apps, new CacheDependency(appsRoot)); } // filter applications based on category List categoryApps = new List(); // check if the application fits requirements PackageContext cntx = PackageController.GetPackageContext(packageId); List categories = GetCategories(); foreach (ApplicationCategory category in categories) { // skip category if required if (!String.IsNullOrEmpty(categoryId) && String.Compare(category.Id, categoryId, true) != 0) continue; // iterate through applications foreach (string appId in category.Applications) { if (apps.ContainsKey(appId) && IsApplicattionFitsRequirements(cntx, apps[appId])) categoryApps.Add(apps[appId]); } } return categoryApps; } public static ApplicationInfo GetApplication(int packageId, string applicationId) { // get all applications List apps = GetApplications(packageId); // check if the application fits requirements PackageContext cntx = PackageController.GetPackageContext(packageId); // find the application foreach (ApplicationInfo app in apps) { if (app.Id.ToLower() == applicationId.ToLower()) { return IsApplicattionFitsRequirements(cntx, app) ? app : null; } } return null; } public static bool IsApplicattionFitsRequirements(PackageContext cntx, ApplicationInfo app) { if (app.Requirements == null) return true; // empty requirements foreach (ApplicationRequirement req in app.Requirements) { // check if this is a group if (req.Groups != null) { bool groupFits = false; foreach (string group in req.Groups) { if (cntx.Groups.ContainsKey(group)) { groupFits = true; break; } } if (!groupFits) return false; } // check if this is a quota if (req.Quotas != null) { bool quotaFits = false; foreach (string quota in req.Quotas) { if (cntx.Quotas.ContainsKey(quota) && !cntx.Quotas[quota].QuotaExhausted) { quotaFits = true; break; } } if (!quotaFits) return false; } } return true; } #region private helper methods private static ApplicationInfo CreateApplicationInfoFromXml(string appFolder, XmlNode nodeApp) { ApplicationInfo app = new ApplicationInfo(); // category name app.CategoryName = GetNodeValue(nodeApp, "category", ""); // attributes app.Id = nodeApp.Attributes["id"].Value; app.Codebase = nodeApp.Attributes["codebase"].Value; app.SettingsControl = nodeApp.Attributes["settingsControl"].Value; app.Folder = appFolder; // child nodes app.Name = GetNodeValue(nodeApp, "name", ""); app.ShortDescription = GetNodeValue(nodeApp, "shortDescription", ""); app.FullDescription = GetNodeValue(nodeApp, "fullDescription", ""); app.Logo = GetNodeValue(nodeApp, "logo", ""); app.Version = GetNodeValue(nodeApp, "version", ""); app.Size = Int32.Parse(GetNodeValue(nodeApp, "size", "0")); app.HomeSite = GetNodeValue(nodeApp, "homeSite", ""); app.SupportSite = GetNodeValue(nodeApp, "supportSite", ""); app.DocsSite = GetNodeValue(nodeApp, "docSite", ""); app.Manufacturer = GetNodeValue(nodeApp, "manufacturer", ""); app.License = GetNodeValue(nodeApp, "license", ""); // process codebase path app.Codebase = Path.Combine(appFolder, app.Codebase); // web settings List settings = new List(); XmlNodeList nodesWebSettings = nodeApp.SelectNodes("webSettings/add"); foreach (XmlNode nodeSetting in nodesWebSettings) { ApplicationWebSetting setting = new ApplicationWebSetting(); setting.Name = nodeSetting.Attributes["name"].Value; setting.Value = nodeSetting.Attributes["value"].Value; settings.Add(setting); } app.WebSettings = settings.ToArray(); // requirements List requirements = new List(); XmlNodeList nodesRequirements = nodeApp.SelectNodes("requirements/add"); foreach (XmlNode nodesRequirement in nodesRequirements) { ApplicationRequirement req = new ApplicationRequirement(); if (nodesRequirement.Attributes["group"] != null) req.Groups = nodesRequirement.Attributes["group"].Value.Split('|'); if (nodesRequirement.Attributes["quota"] != null) req.Quotas = nodesRequirement.Attributes["quota"].Value.Split('|'); req.Display = true; if (nodesRequirement.Attributes["display"] != null) req.Display = Utils.ParseBool(nodesRequirement.Attributes["display"].Value, true); requirements.Add(req); } app.Requirements = requirements.ToArray(); return app; } private static string GetNodeValue(XmlNode parentNode, string nodeName, string defaultValue) { XmlNode node = parentNode.SelectSingleNode(nodeName); if (node != null) { return node.InnerText.Trim(); } // return default value return defaultValue; } #endregion } }