0

InstallShield 包括通过 XML 文件更改* 修改应用程序 .config 文件的能力...但是如果您想在多个安装包之间共享配置设置怎么办?

在我们的环境中,我们提供了一个 WPF 应用程序和一个 WinForms 应用程序,其中一个是管理控制台,另一个是数据收集应用程序(用户可以选择他们想要的单个安装程序)。我们还为两个应用程序使用的服务层提供了一个安装程序,因此每个客户位置的 Web 服务端点都不同。(实际环境稍微复杂一些,以上是复杂度的一个例子)

我们的方向是创建一个 XML 文件,该文件具有两个安装程序的通用配置设置,包括通用设置,InstallShield 读取这些值,然后更新每个应用程序的 .config 文件以指向相同的端点。有人做过吗?这可以在不使用 InstallScript 或自定义操作的情况下完成吗?

  • 将 xml 配置文件导入 XML 文件更改区域以读取配置文件中的每一行,并通过 XPath 为每一行创建添加请求(例如 add[@key="IsMultiTouch" 和 @value="True"])。这似乎不太理想,因为我怀疑它只是重新创建我希望它从开发团队以文件的当前状态读取的文件,并且只在安装期间修改 appsettings 中的值
4

2 回答 2

1

[后续] 使用 CtrlGetText() 从 InstallShield 对话框中提取值,将该数据传递给我的 dll 以包含在我的应用程序配置文件中。生成的 .config 文件条目包含大量“�”序列。尝试使用 InstallScript 清理这些内容的尝试效果不佳。最终不得不通过将 XmlDocument 翻转为字符串,在字符串上执行 .Replace("�", "") 将它们加载到 XmlDocument 中(以确保它有效),然后保存它,从而在 dll 中清理它们. InstallShield!= 太棒了。[/跟进]

XML 文件更改似乎不是一个好的方向,因此我编写了一个自定义操作,该操作在安装该功能后触发。自定义操作调用一个托管程序集,该程序集通过 xml 文件将更改应用到应用程序 .config 文件。

自定义操作

#define CO_APPCONFIG_DLL "MyCompany.InstallShield.AppConfig.dll"
#define CO_APPCONFIG_FQNAME "MyCompany.InstallShield.AppConfig.ConfigMgr"

#define CO_APPSETTINGS_NEW "MyApp_AppSettings.xml"
#define CO_APPSETTINGS_CONFIG "MyCompany.IOAnywhere.Dashboard.exe.config"
#define CO_APPSETTINGS_SECTION "CO_dashboard"

//---------------------------------------------------------------------------
// The Installed event is sent after the feature Dashboard
// is installed.
//---------------------------------------------------------------------------
export prototype Dashboard_Installed();
function Dashboard_Installed()
BOOL bResult;
OBJECT oAppConfig;
begin

try
    // Note: the configuration dll is in the support directory
    set oAppConfig = DotNetCoCreateObject(SUPPORTDIR ^ CO_APPCONFIG_DLL, CO_APPCONFIG_FQNAME, "");
catch
    MessageBox("Error Loading" + SUPPORTDIR ^ CO_APPCONFIG_DLL + ": " + Err.Number + " " + Err.Description, INFORMATION); 
    abort;
endcatch;

try
    // Note: the new configuration settings file should be in the same folder as the setup.exe
    bResult = oAppConfig.ConfigureSettings(CO_APPSETTINGS_NEW, TARGETDIR ^ CO_APPSETTINGS_CONFIG, CO_APPSETTINGS_SECTION);
catch
    MessageBox("Verify that the file " + CO_APPSETTINGS_NEW + " exists in the setup directory.  Error calling ConfigureSettings " + SUPPORTDIR ^ CO_APPCONFIG_DLL + " " + Err.Number + " " + Err.Description, INFORMATION);
endcatch;

end;

调用 DLL

using System;
using System.Xml;

/// <summary>
/// Called by InstallShield Installer process to apply appSettings to an installed applications .config file
/// </summary>
namespace CO.InstallShield.AppConfig
{
    /// <summary>
    /// ConfigMgr is the class that encapsulates functionality related to modifying a .config file
    /// </summary>
    public class ConfigMgr
    {
        /// <summary>
        /// ConfigureSettings applies changes from a common xml file to the applications .config file
        /// </summary>
        /// <remarks>
        /// Ensures required keys for the application are included in the .config file
        /// Applies common settings to the .config file
        /// Applies application specific settings to the .config file
        /// </remarks>
        /// <param name="configFilePath">Path to the xml file that has the setting that need to be applied</param>
        /// <param name="targetAppConfigPath">Path to the .config file for the appliction</param>
        /// <param name="targetAppName">Section in the xml file that has application specific settings</param>
        /// <returns>True if it was able to configure settings</returns>
        public bool ConfigureSettings(string configFilePath, string targetAppConfigPath, string targetAppName)
        {
            bool completed = false;

            try
            {
                XmlDocument configFileDoc = new XmlDocument();
                configFileDoc.Load(configFilePath);

                XmlDocument targetAppConfigDoc = new XmlDocument();
                targetAppConfigDoc.Load(targetAppConfigPath);

                // ensure the appSettings section exists
                AddRequiredSections(ref targetAppConfigDoc);

                // ensure all required keys exist in the target .config file
                AddRequiredKeys(configFileDoc.SelectSingleNode("configuration/" + targetAppName + "/requiredKeys"), ref targetAppConfigDoc);

                // loop through each key in the common section of the configuration file
                AddKeyValues(configFileDoc.SelectSingleNode("configuration/common/appSettings"), ref targetAppConfigDoc);

                // loop through each key in the app specific section of the configuration file - it will override the standard configuration
                AddKeyValues(configFileDoc.SelectSingleNode("configuration/" + targetAppName + "/appSettings"), ref targetAppConfigDoc);

                // save it off
                targetAppConfigDoc.Save(targetAppConfigPath);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                completed = true;
            }
            return completed;
        }
        private void AddRequiredSections(ref XmlDocument targeAppConfigDoc)
        {
            // Ensure the target .config file has an appSettings section... unusual but evidently possible
            if (targeAppConfigDoc.SelectSingleNode("configuration/appSettings") == null)
                targeAppConfigDoc.SelectSingleNode("configuration").AppendChild(targeAppConfigDoc.CreateNode(XmlNodeType.Element, "appSettings", null));
        }
        private void AddKeyValues(XmlNode configAppNodeSet, ref XmlDocument targetAppConfigDoc)
        {
            if (configAppNodeSet == null) return; // Nothing to do

            foreach (XmlNode configNode in configAppNodeSet.SelectNodes("add"))
            {
                XmlNode targetNode = targetAppConfigDoc.SelectSingleNode("configuration/appSettings/add[@key='" + configNode.Attributes["key"].Value + "']");
                if (targetNode != null)
                {
                    targetNode.Attributes["value"].Value = configNode.Attributes["value"].Value;
                }
            }
        }
        private void AddRequiredKeys(XmlNode targetAppNodeSet, ref XmlDocument targetAppConfigDoc)
        {
            if (targetAppNodeSet == null) return; // Nothing to do

            foreach (XmlNode targetNode in targetAppNodeSet.SelectNodes("key"))
            {
                // add the key if it doesn't already exist
                XmlNode appNode = targetAppConfigDoc.SelectSingleNode("configuration/appSettings/add[@key='" + targetNode.Attributes["value"].Value + "']");
                if (appNode == null)
                {
                    appNode = targetAppConfigDoc.SelectSingleNode("configuration/appSettings");
                    XmlNode newAddNode = targetAppConfigDoc.CreateNode(XmlNodeType.Element, "add", null);
                    XmlAttribute newAddNodeKey = targetAppConfigDoc.CreateAttribute("key");
                    newAddNodeKey.Value = targetNode.Attributes["value"].Value;
                    XmlAttribute newAddNodeValue = targetAppConfigDoc.CreateAttribute("value");
                    newAddNodeValue.Value = "NotSet";
                    newAddNode.Attributes.Append(newAddNodeKey);
                    newAddNode.Attributes.Append(newAddNodeValue);
                    appNode.AppendChild(newAddNode);
                }
            }
        }
    }
}
于 2012-06-26T17:14:49.027 回答
0

从安装的角度来看,您可以通过遵循记忆属性模式的变体来获得很多这样的信息。通过存储 XML 文件更改引用的属性值,创建一个共享组件,该组件在共享注册表项中注册各种配置信息。使用系统搜索尝试将这些值读回相同或相关安装的后续运行中。但是,这种方法不会更新相关项目中现有的单独配置文件。

为了解决这个问题(尽管您可能会发现单独的配置本身很有用),我建议找到一种方法来引用单个共享配置。这可能是通过使用配置文件的共享目录来实现的。它可能是通过直接引用共享注册表位置并跳过前面描述的 XML 文件更改来实现的。

于 2012-06-15T11:49:35.817 回答