精华内容
下载资源
问答
  • Windows Installer使用Microsoft Software Installation (MSI)包文件来安装程序,每个包文件都有...恶意行为者可以滥用这些文件中的自定义操作来执行恶意脚本,植入恶意软件,并能定位用户电脑中金融应用程序所在位...

    Windows Installer使用Microsoft Software Installation (MSI)包文件来安装程序,每个包文件都有一个关系型数据库,其中包含安装或删除程序所需的指令和数据。

    趋势科技最近发现了一些恶意MSI文件,它们能绕过传统的安全解决方案,下载并执行其它文件。恶意行为者可以滥用这些文件中的自定义操作来执行恶意脚本,植入恶意软件,并能定位用户电脑中金融应用程序所在位置。

    分析恶意MSI文件

    我们在几个恶意.msi文件样本中发现了JScript / VBScript脚本,但脚本文件并不完整,部分代码似乎被截断并放在了文件的其他部分,并且脚本没有直接调用wscript.exe来运行,因为安装程序msiec .exe本身就带有解释器。

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图1..MSI文件中包含的可能被截断的JavaScript脚本

    我们使用了Orca MSI Editor工具,它允许用户查看和编辑数据库文件表,进而了解文件传送的方式和位置,我们可在CustomAction表中查找相关问题脚本。CustomAction表允许用户将自定义代码和数据集成到安装中,代码源可以是来自特定数据库、已安装文件或现有可执行文件流。该表列出了各种信息,如操作、类型、源、目标和扩展类型。中国菜刀

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图2.Orca MSI Editor:包含JavaScript的CustomAction

    恶意JS代码(由趋势科技检测为Trojan.JS.MSAIHA.A)通过访问下列地址hxxps[:]//s3-eu-west-1[.]amazonaws[.]com/{random characters}/image2[.]png来下载文件。下载的文件或创建的.txt文件将存储在以下任一文件夹中:

    · %User Startup%\

    · %User Profile%\Saved Games

    · %User Profile%\Contacts

    · %User Profile%\Links

    · %User Profile%\Music

    下载文件(.exe,.msi或来自zip的转储文件)的文件名为jesus或dump,而对于创建的.txt文件,文件名则在desktop.txt,desktop和desktop.ini之间变换。

    文件%Application Data%/ {yyyyMM} .ini是感染标记,如果有它,则恶意软件将不会继续其例程。恶意软件还会下载一个加密的.zip文件,然后使用带有硬编码0x29的XOR对其进行解密。

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图3.保存在Microsoft文件夹中的.zip文件的内容

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图4.执行context_snapshot.exe的木马

    这个.zip文件里有常规文件,如iLua.inf,msvcr120.dll和msvcp120.dll;也有由Avira(小红伞,由德国的Avira公司所开发的杀毒软件)数字签名的文件;还有 AutoIt相关文件和加密的动态链接库(DLL)。该木马通过AutoIt(一个使用类似BASIC脚本语言的免费软件,设计用于Windows GUI中进行自动化操作)来解密加密的DLL并执行context_snapshot.exe来注入其自身的DLL,并能成伪装合法的进程。

    此处的AutoIt脚本(图3中的v8_context_snapshot.src,趋势科技检测为Trojan.AutoIt.AUTINJECT.AA)受密码保护,因此我们通过一个修改过的myAut2Exe(一个用于密码猜测、生成路径名以及其他信息的工具)对其解码。

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图5.v8_context_snapshot.src的日志输出

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图6.将 Avira可执行文件注入到加密的DLL之中

    该木马使用解密后的AutoIt脚本进行注入操作,此AutoIt脚本我们在网上也发现过类似的,攻击者将其修改成了适合自己使用的版本。此外,AutoIt脚本还附加了一个DLL,随后会加载到内存中,这么做的目的是为了执行数字签名的Avira文件(context_snapshot.exe),然后在Avira可执行文件的进程中注入加密的DLL (Jlib.dll),并将其作为合法进程传递。

    在我们与Avira公司阐述了研究结果后,Avira回应道:

    我们的首要任务是保护我们的用户,因此我们立即检测了产品中所有文件以及外部链接。恶意软件为了能够在合法进程的上下文中执行恶意代码并绕过安全解决方案,利用了我们的一个Avira可执行程序,在常规安装的上下文中注入恶意代码。不过只有在用户机器上未启用Avira保护时,此项步骤才能执行成功,否则是不会允许任何第三方进程或组件修改/注入进程的。在过去,我们并没有收到过类似的报告。这一问题目前正在调查中,因此我们将在未来几天提供更多信息。

    其他与Trojan.PS1.MSAIHA.A相关的样本也可以重启目标计算机并使用Dropbox链接下载上述.zip文件。有些恶意样本甚至会检查以下文件夹名称是否存在,如果存在,则继续程序:

    · %AppDataLocal%\ Aplicativo Itau(与巴西银行有关)

    · %Program Files%\ AppBrad(与巴西的银行和金融服务公司有关)

    · %ProgramFiles%\ Diebold \ Warsaw(与金融和零售技术公司有关)

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图7.伪装成Adobe Acrobat Reader DC弹出窗口

    恶意MSI文件另一个值得注意的地方是它们会伪装成合法应用,如图7所示的Adobe Acrobat Reader DC,会将用户重定向到网站www[.]adobe[.]com/br/(域名位于葡萄牙)。

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图8.MSI文件将用户重定向到此站点

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图9.从恶意URL下载的垃圾邮件包含了一个.zip文件

    电子邮件由葡萄牙语书写,内容简明扼要,并要求收件人紧急处理附件文件。附件是一个标题为“Fatur432952-532-674.zip”的.zip文件,下载地址是一个恶意链接,如下图框线中所示,其中地址里的“image2.png”是下载附件时的存档。

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图10.下载存档的脚本

    存档文件包含dump.msi(检测为Trojan.JS.MSAIHA.A),dump.exe(检测为TrojanSpy.Win32.CASBANEIRO.XLB)和ssleay64.dll(检测为TrojanSpy.Win32.CASBANEIRO.XLB)。

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图11.垃圾邮件中MSI文件的感染流程

    恶意payload通过检查hxxps[:]//www[.]localizaip[.]com[.]br/api/iplocation[.]php中的国家代码来将攻击目标锁定在巴西和葡萄牙,具体来说可能与银行和金融等方面信息有关,甚至可能会记录用户的输入信息。天空彩

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图12.执行dump.exe例程的流程图

    以下是一些解密字符串在回调函数中创建的窗口:

    · AMARELO_PASS_6

    · AMARELO_PASS_8

    · AMARELO_PASS_CERT

    · AMARELO_QR

    · AMARELO_CERT_SIMPLES

    · AMARELO_PASS_SIMPLES

    · AMARELO_PASS_QR

    · SICREDI_ASS

    · SICREDO_TOKEN_FISICO

    · SICREDO_TOKEN_MOB

    · SICREDO_PASS_QR

    · SANTA_ASSI_ELECTRO

    · SANTA_TOKEN

    · SANTA_N_SERIE

    · SANTA_PASS_QR

    · SANTA_POS_TABELA

    · CEF_ASS_NUMERO

    · CEF_ASS_NUMER0_LETRAS

    · CEF_PASS_QR

    · NORD_POS_CARTAO

    · NORD_PASS_QR

    · BANESTER_CERTIFICADO

    · BANESTES_COD_ACCESSOBANESTES_PASS_QR

    · BANRI_SENHA

    · BANRI_SENHA_TECLADO

    · SAFRA_TOKEN_MOB

    · SAFRA_TOKEN_DISPLAY

    · SAFRA_SENHA_TECLADO

    · SAFRA_PASS_QR

    · DESCO_POS_TABELA

    · DESCO_CERT

    · DESCO_TOKEN_6

    · DESCO_TOKEN_8

    · DESCO_PASS_QR

    · DESCO_CEL_VISOR

    · DESCO_PISCA

    · BRB_CODIGO

    · BRB_SENHA_TECLADO

    · BRB_PASS_QR

    · ITA_DATA

    · ITA_SENH6

    · ITA_TOKAPP

    · ITA_TOKCELL

    · ITA_QR

    滥用MSI中的自定义操作

    执行安装时,除了标准的内置操作之外,有时开发人员可能需要编写自定义操作。微软列举了下列可能需要自定义操作的场景:

    · 在安装过程中,必须启动安装在用户机器上或与应用程序一起安装的可执行文件。

    · 在安装过程中必须调用DLL中定义的特殊函数。

    · 在安装过程中必须使用用编程语言Microsoft Visual Basic Scripting Edition或Microsoft JScript文字脚本文本编写的函数。

    · 有些操作必须在安装脚本执行后才能执行。

    · 时间和进度信息必须添加到ProgressBar控件和TimeRemaining文本控件中。

    考虑到上述情况,对于攻击者来说,滥用第一和第二种的情况是不太可能的。

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图13.Orca MSI Editor:CustomAction中包含了VBScript

    在检查了另一个包含VBScript的样本(参见图13)之后,我们可以推断恶意软件创建者使用的是Advanced Installer(安装包制作工具)来构建样本的,因为该样本需要第三方库(如powershellscriptlauncher.dll)来运行PowerShell脚本,进而查看日志和进程。

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图14.包含其他自定义操作的Advanced Installer

    我们能够添加或修改样本中的自定义操作,例如在JS,VBS和PowerShell脚本之间选择执行,或者使用Advanced Installer来加载库。此举可能有助于恶意行为者滥用其功能,比如他们就可以轻松修改正常的MSI包并在其中插入恶意脚本。

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图15.指示现有和新创建的PowerShell脚本的自定义操作

    攻击观察:通过滥用Windows Installer MSI中的自定义操作来运行恶意JS/VBS/PowerShell脚本

    图16.添加一个新的自定义操作来运行PowerShell脚本

    目前,恶意软件下载的档案中包含了多组(DLL side-loading)恶意软件。

    缓解和解决方案

    我们注意到,恶意行为者滥用MSI包最常见的目的通常为了安装不需要的应用程序(PUAs),而不一定是为了植入恶意软件。而使用MSI包和自定义操作的恶意软件安装则是另一种需要警惕的机制,因为它可能会绕过传统安全软件的检测方法。

    在本例中,恶意行为者可能正在测试不同的传播方法,而根据垃圾邮件中的语言、重定向的站点以及在分析过程中遇到的文件夹路径,我们都可以判定攻击者的重点目标放在了巴西和葡萄牙上。想要防范此类攻击,首先我们建议用户避免安装未知文件和访问未知链接,这些链接可能会重定向到下载恶意文件的站点。用户还可以通过使用最新的安全补丁更新系统,或者使用能够抵御攻击的解决方案来阻止此类攻击。

    展开全文
  • mysql msi 自定义安装详解一、msi安装包下载二、msi 安装详解2.1 双击msi打开安装程序 -> 选择自定义安装 -> Next2.2 选择需安装的产品 -> 修改默认安装路径 -> Next2.3 可能会需要C++ 2013 安装环境, ...

    一、msi安装包下载

    下载地址(官网): https://dev.mysql.com/downloads/mysql/
    在这里插入图片描述

    二、msi 安装详解

    2.1 双击msi打开安装程序 -> 选择自定义安装 -> Next

    *注: 大家可以按需选择对应的下载方式!

    在这里插入图片描述

    2.2 选择需安装的产品 -> 修改默认安装路径 -> Next

    *注: 建议大家修改安装路径, 不要放在默认的C盘!

    在这里插入图片描述

    2.3 可能会需要C++ 2013 安装环境, 若没有则 -> Next

    建议大家直接去microsoft 官网下载, 我直接Install一直失败, 不知道为啥!
    安装完成, 可以返回上一步, 重新执行下一步操作.
    在这里插入图片描述

    2.4 执行安装 -> Next

    在这里插入图片描述

    2.4 安装完成后, 进行产品配置(类型/账户/win服务) -> 安装结束

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    三、配置系统环境变量

    非强制配置, 好处: 可在cmd命令行中 执行mysql 命令

    *注: 非强制配置, 好处: 可在cmd命令行中 执行mysql 命!

    1.我的电脑 → 右键 → 属性 → 高级系统设置 → 环境变量
    2.新建变量名: MYSQL_HOME, 变量值: E:\mysql\MySQL Server 5.7(自己的安装目录)
    3.PATH,编辑,新建%MYSQL_HOME%\bin

    四、安装成功否测试

    4.1 命令行方式: ( 前提是已经配置环境变量 )

    Win + R -> 输入 cmd -> 输入命令: mysql -V;
    在这里插入图片描述

    4.2 客户端执行 / cmd 命令行中登陆:mysql -u用户名 -p密码;

    1. show variables like ‘%version%’;
    2. status;,或者:\s;
    3. select version();
    展开全文
  • 自定义msi安装包的执行过程

    千次阅读 2016-03-24 16:07:50
    有时候我们需要在程序中执行另一个程序的安装,这就需要我们去自定义msi安装包的执行过程。

     有时候我们需要在程序中执行另一个程序的安装,这就需要我们去自定义msi安装包的执行过程。

     

    比如我要做一个安装管理程序,可以根据用户的选择安装不同的子产品。当用户选择了三个产品时,如果分别显示这三个产品的安装交互UI显然是不恰当的。我们期望用一个统一的自定义UI去取代每个产品各自的UI

     

    平时使用msiexec.exe习惯了,所以最直接的想法就是在一个子进程中执行:

             msiexec.exe /qn


    这样固然是能够完成任务,但是不是太简陋了? 安装开始后我们想取消这次安装怎么办? 或者我们还想要拿到一些安装进度的信息。

     

    其实可以通过调用三个windowsAPI 轻松搞定这个事儿!下面的C# demo用一个自定义Form来指示多个MSI文件的安装过程。Form上放的是一个滚动条,并且配合一个不断更新的label

     

    下面是安装过程中的UI



    点击Cancel按钮取消安装后的UI:



    先看一下这三个API:

    [DllImport("msi.dll", CharSet = CharSet.Auto)]

    internal static extern int MsiSetInternalUI(int dwUILevel, IntPtr phWnd);


    在调用msiexec.exe时,我们通过指定 /q参数让安装过程显示不同的UI。如果不显示UI的话就要使用参数 /qn 。MsiSetInternalUI方法就是干这个事儿的。通过下面的调用就可以去掉msi中自带的UI:

    NativeMethods.MsiSetInternalUI(2, IntPtr.Zero)


    [DllImport("msi.dll", CharSet = CharSet.Auto)]

    internal static extern MsiInstallUIHandler MsiSetExternalUI([MarshalAs(UnmanagedType.FunctionPtr)] MsiInstallUIHandlerpuiHandler, NativeMethods.InstallLogMode dwMessageFilter, IntPtr pvContext);

    MsiSetExternalUI 函数允许指定一个用户定义的外部UI handler用来处理安装过程中产生的消息。这个外部的UI handler会在内部的UI handler被调用前调用。 如果在外部的UI handler中返回非0的值,就说明这个消息已经被处理。

     

    这个外部的UI handler就是MsiSetExternalUI方法的第一个参数,我们通过实现这个handler来处理自己感兴趣的消息, 比如当安装进度变化后去更新进度条。或者通过它传递我们的消息给msi,比如说告诉msi,停止安装,执行cancel操作。使用这个方法需要注意的是,当你完成安装后一定要把原来的handler设回去。否则以后执行msi安装包可能会出问题。


    MSDN上有一个MsiInstallUIHandler 的demo,感兴趣的同学可以看看。

    [DllImport("msi.dll", CharSet = CharSet.Auto)]

    internal static extern uint MsiInstallProduct([MarshalAs(UnmanagedType.LPWStr)] string szPackagePath,[MarshalAs(UnmanagedType.LPWStr)] string szCommandLine);


    正如其名,这个是真正干活儿的方法。

    实在忍不住要介绍第四个方法,虽然它对实现当前的功能来说是可选的,但对一个产品来说,它却是用来救命的。

    [DllImport("msi.dll", CharSet = CharSet.Auto)]

    internal static extern uint MsiEnableLog(GcMsiUtil.NativeMethods.InstallLogMode dwLogMode,[MarshalAs(UnmanagedType.LPWStr)] string szLogFile, uint dwLogAttributes);

     

    这个方法会把安装log保存到你传递给它的文件路径。有了它生活就会happy很多,很多… 否则当用户告诉你安装失败时,你一定会抓狂的。

     

    好了,下面是MyInstaller demo的主要代码:

    InstallProcessForm.cs
    public partial class InstallProcessForm : Form
        {
            private MyInstaller _installer = null;
            private BackgroundWorker _installerBGWorker = new BackgroundWorker();
            internal InstallProcessForm()
            {
                InitializeComponent();
    
                _installer = new MyInstaller();
    
                _installerBGWorker.WorkerReportsProgress = true;
                _installerBGWorker.WorkerSupportsCancellation = true;
    
                _installerBGWorker.DoWork += _installerBGWorker_DoWork;
                _installerBGWorker.RunWorkerCompleted += _installerBGWorker_RunWorkerCompleted;
                _installerBGWorker.ProgressChanged += _installerBGWorker_ProgressChanged;
    
                this.Shown += InstallProcessForm_Shown;
            }
    
            private void InstallProcessForm_Shown(object sender, EventArgs e)
            {
                // 当窗口打开后就开始后台的安装
                _installerBGWorker.RunWorkerAsync();
            }
    
            private void _installerBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                // 消息通过 e.UserState 传回,并通过label显示在窗口上
                string message = e.UserState.ToString();
                this.label1.Text = message;
                if (message == "正在取消安装 ...")
                {
                    this.CancelButton.Enabled = false;
                }
            }
    
            private void _installerBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                // 安装过程结束
            }
    
            private void _installerBGWorker_DoWork(object sender, DoWorkEventArgs e)
            {
                BackgroundWorker bgWorker = sender as BackgroundWorker;
    
                // 开始执行安装方法
                _installer = new MyInstaller();
                string msiFilePath = "xxx.msi"; // msi file path
                _installer.Install(bgWorker, msiFilePath);
            }
    
            private void CancelButton_Click(object sender, EventArgs e)
            {
                _installer.Canceled = true;
         _installerBGWorker.CancelAsync();
            }
    }
    MyInstaller.cs
    internal class MyInstaller
        {
            private BackgroundWorker _bgWorker = null;
    
            public bool Canceled { get; set; }
    
            public void Install(BackgroundWorker bgWorker, string msiFileName)
            {
                _bgWorker = bgWorker;
    
                NativeMethods.MyMsiInstallUIHandler oldHandler = null;
                try
                {
                    string logPath = "test.log";
                    NativeMethods.MsiEnableLog(NativeMethods.LogMode.Verbose, logPath, 0u);
                    NativeMethods.MsiSetInternalUI(2, IntPtr.Zero);
    
                    oldHandler = NativeMethods.MsiSetExternalUI(new NativeMethods.MyMsiInstallUIHandler(MsiProgressHandler),
                                                    NativeMethods.LogMode.ExternalUI,
                                                    IntPtr.Zero);
                    string param = "ACTION=INSTALL";
                    _bgWorker.ReportProgress(0, "正在安装 xxx ...");
                    NativeMethods.MsiInstallProduct(msiFileName, param);
                }
                catch(Exception e)
                {
                    // todo
                }
                finally
                {
                    // 一定要把默认的handler设回去。
                    if(oldHandler != null)
                    {
                        NativeMethods.MsiSetExternalUI(oldHandler, NativeMethods.LogMode.None, IntPtr.Zero);
                    }
                }
            }
    
            //最重要的就是这个方法了,这里仅演示了如何cancel一个安装,更多详情请参考MSDN文档
            private int MsiProgressHandler(IntPtr context, int messageType, string message)
            {
                if (this.Canceled)
                {
                    if (_bgWorker != null)
                    {
                        _bgWorker.ReportProgress(0, "正在取消安装 ...");
                    }
                    // 这个返回值会告诉msi, cancel当前的安装
                    return 2;
                }
                return 1;
            }
        }
    
        internal static class NativeMethods
        {
            [DllImport("msi.dll", CharSet = CharSet.Auto)]
            internal static extern int MsiSetInternalUI(int dwUILevel, IntPtr phWnd);
    
            [DllImport("msi.dll", CharSet = CharSet.Auto)]
            internal static extern MyMsiInstallUIHandler MsiSetExternalUI([MarshalAs(UnmanagedType.FunctionPtr)] MyMsiInstallUIHandler puiHandler, NativeMethods.LogMode dwMessageFilter, IntPtr pvContext);
    
            [DllImport("msi.dll", CharSet = CharSet.Auto)]
            internal static extern uint MsiInstallProduct([MarshalAs(UnmanagedType.LPWStr)] string szPackagePath, [MarshalAs(UnmanagedType.LPWStr)] string szCommandLine);
    
            [DllImport("msi.dll", CharSet = CharSet.Auto)]
            internal static extern uint MsiEnableLog(NativeMethods.LogMode dwLogMode, [MarshalAs(UnmanagedType.LPWStr)] string szLogFile, uint dwLogAttributes);
    
            internal delegate int MyMsiInstallUIHandler(IntPtr context, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message);
    
            [Flags]
            internal enum LogMode : uint
            {
                None = 0u,
                Verbose = 4096u,
                ExternalUI = 20239u
            }
        }
    

     简单说明一下,用户定义的UI运行在主线程中,使用BackgroundWorker执行安装任务。在安装进行的过程中可以把cancel信息传递给MsiProgressHandler,当MsiProgressHandler检测到cancel信息后通过返回值告诉msi的执行引擎,执行cancel操作(msi的安装过程是相当严谨的,可不能简单的杀掉安装进程了事!)。

    这样,一个支持cancel的自定义UI的安装控制程序就OK了(demo哈)。如果要安装多个msi只需在Install方法中循环就可以了。

     

     总结一下,通过调用几个windows API,我们可以实现对msi安装过程的控制。这比调用msiexec.exe更灵活,也为程序日后添加新的功能打下了基础。


    感谢葡萄哥Nick 投稿





    展开全文
  • 自定义MSI 安装

    2006-11-08 19:03:00
    Vishnu Prasad H 探究了安装项目模板、编辑器、自定义安装程序以及更多内容。然后,他将所有内容放在一起,组成了一个部署数据库应用程序的示例。 与以前相比,现在部署 .NET 应用程序非常简单。现在 xcopy 部署也...

    深入了解在 Visual Studio .NET 中创建安装例程的基础知识!Vishnu Prasad H 探究了安装项目模板、编辑器、自定义安装程序以及更多内容。然后,他将所有内容放在一起,组成了一个部署数据库应用程序的示例。

    与以前相比,现在部署 .NET 应用程序非常简单。现在 xcopy 部署也成为了可能。但是,简单 xcopy 部署还是有限制的。它不能用于分层应用程序部署、配置设置、细化调整等等。将应用程序层与自定义配置集成在一个分布式框架中需要专业、可自定义的部署工具。基于 Microsoft Windows Installer (MSI) 的部署能够完成所有这些任务甚至更多任务。然而,要使其得以运行还需要一些努力。幸运的是,Visual Studio .NET 的安装和部署项目使这变得相当简单。

    部署示例

    我创建了一个示例应用程序,以演示 MSI 部署在 Visual Studio .NET 中的功能。这是一个非常简单的应用程序,只有一个显示客户的窗体。

    该应用程序具有一个 CustomSteps 类库。此库包括一个 SetupDB 组件类,它从 Installer 类继承。该组件类提供了自定义数据库安装。它对 OSQL 进程进行了自定义,并运行该进程以安装和删除数据库。我讲述的重点主要在这个类库。

    然后,您就拥有了 Customer 安装,这是一个安装项目。此项目提供了与用户进行接口的组件,可用来捕获诸如服务器名、数据库名、用户 ID 等数据库信息。然后,它会将这些文件复制到目标,并创建数据库、数据库的对象和基础数据。最后,它会在 CustomSteps 类库的帮助下对配置文件中的连接字符串属性进行配置。

    创建示例应用程序

    即使您打算在阅读本文的同时着手创建该示例,也请您去下载本文附带的源代码。(创建这个基本的 Customer 应用程序没有什么要指出的内容,因为包括它的目的只是为了最终了解安装程序如何运行!)

    创建一个空的解决方案,将其命名为 InstallerSample,然后将其添加到下载的 Customer 应用程序中。此应用程序只有一个窗体,其中带有一个显示数据库中客户的数据网格,如图 1 所示。有一个 Resources 文件夹,其中的 Customers.xml 文件配置了一些数据。请注意,该文件的 Build Action 属性被设置为 Content。引入此文件是为了演示这个 Build Action 的重要性。

    App.config 具有下面的配置设置。稍后,您将看到安装程序在运行时设置的这个值。

    <?xml version="1.0" encoding="utf-8" ?>  <configuration>       <appSettings>          <add key="connectionstring" value=""/>       </appSettings>  </configuration>

    创建 CustomSteps 库

    此步骤会帮助您处理这个自定义数据库安装。在安装项目中,您可以指定 Custom Actions。此项目的输出就是其中一个 Custom Action 步骤,因此在文件复制到目标之后称之为步骤。

    创建一个新的类库 CustomSteps,添加一个新的 Installer 类,然后将其重命名为 SetupDB.cs。在进入实施阶段之前,您必须决定安装数据库的方法。表 1 提供了一些常用方法。

    表 1. 数据库部署方法。
    模式说明

    备份还原

    SQL Server 提供了备份和还原数据库的选项。您可以使用 System.Data.SqlClient 命名空间以及特定的数据库存储过程进行还原。这可能是最简单的技术,但是它具有一些问题(如硬编码的名称)和其他一些配置考虑因素。

    SQL-DMO

    如果您打算部署基于 VB/COM/SQL 的应用程序,这就是最好的方法之一。SQL-DMO 提供了封装的 MS SQL Server 对象,以便用于管理目的。其中包括创建数据库和其他对象。您可以创建一个带有 DMO 对象的 EXE,DMO 对象会封装创建数据库的操作。

    脚本文件

    这可能是最好的综合性方法,因为它通常都是一种用来维护/管理脚本的最佳做法。您可以使用“OSQL”来执行这些脚本。但是,在转向部署之前,必须先运行一个有效的 SQL 脚本测试。另外,使用 BCP 实用程序以各种格式迁移大量数据通常也是一个很好的选择。

    ADO.NET

    这个选择的可靠性很好,但是会涉及大量的维护和编码工作。您可以创建一些 C# 方法来创建数据库、对象,并使用 SqlClient 命名空间插入语句。

    在该示例应用程序中有一个数据库,它只有两个表:tblCustomer 和 tblAddress。在此方案中,假设您已经决定采用基于脚本文件的部署。通常,在公司开发环境中,数据库及其对象是由 DBA 进行紧密控制的,而要求开发人员来维护自己的 DML 语句脚本。在生成时,可以运行一个预先生成的事件来创建 SQL 文件。因此,假设有四个 SQL 文件,如下所示:

    DataBase.sql — 用于创建数据库的脚本。

    DropDatabase.sql — 用于删除数据库的脚本。

    Objects.sql — 表和其他对象的脚本(如果需要的话)。

    StandingData.sql — 表的 DML 脚本。

    数据库的名称应由用户指定。然后,占位符可以在运行时替换为真正的数据库名称。在 Database.sql/DropDatabase.sql(请参阅下载中的文件)中,我使用 <> 作为占位符。将这些脚本文件复制到项目中一个名为 Scripts 的单独子文件夹中。请注意,即使在此处,这些脚本文件的 Build Action 属性仍然为 Content。

    另外,您可以拥有多个 Objects 和 StandingData SQL 脚本文件;您可能希望该实现足够灵活,可以处理这种情况。在 Resources 子文件夹下创建 InstallationFiles.xml,并将 Build Action 属性设置为 Embedded resource清单 1 显示了该 XML 文件的内容。

    清单 1. InstallationFiles.xml 内容。

    <?xml version="1.0" encoding="utf-8" ?> <configroot>  <Files>       <DataBase>              <Add>                   <File name="Database.sql"/>              </Add>              <Remove>                   <File name="DropDatabase.sql"/>              </Remove>       </DataBase>       <Objects>              <File name="Objects.sql"/>       </Objects>       <Records>              <File name="StandingData.sql"/>       </Records>  </Files></configroot></code>

    该 XML 文件可帮助您在运行时识别哪些文件用于创建或删除数据库及其对象和记录。您可以在 Files 下面添加多个元素,并使用要执行的 SQL 文件名来配置名称属性。

    现在,您已经拥有了这些脚本文件,以及一个提供有关每个脚本文件执行哪些操作的信息的 XML 文件。接下来,我要将注意力转移到 SetupDB.cs。它是从 System.Configuration.Install.Installer 继承的,后者是所有自定义安装程序的基类。请记下 Runinstaller(true) 属性;这样可以确保调用此 Custom Action。该 Installer 类通过重写以下四个关键方法来为您提供自定义选项:

    Install — 在安装步骤调用。

    Commit — 完成安装过程。

    Rollback — 将系统还原为安装前的状态。在 Install 步骤失败时调用 Rollback。通常,在重写该方法时,它应该执行与卸载相同的任务。

    Uninstall — 删除已经安装的文件,并重置/删除在安装过程中完成的任何配置。如果此步骤失败,通常无法还原到安装前的状态。

    另外,还有一些前面提到操作的 OnAfter 和 OnBefore 事件。但在此示例中,我只重写了 Install 和 Uninstall 方法。

    首先,我们来讲述 Install 方法。此方法必须执行下列步骤:

    1.

    捕获并计算用户输入的数据库连接详细信息。

    2.

    配置 App.Config 文件 connectionstring 设置中的连接字符串。

    3.

    以合适的顺序运行脚本文件。

    首先,将清单 2 中的代码添加到您的 SetupDb.cs 中。

    清单 2. Install 方法的代码。

    public override void Install (   System.Collections.IDictionary stateSaver ){bool TrustedConnection=false;base.Install(stateSaver);try{ if(this.Context!=null)    {StringDictionary parameters = Context.Parameters;    string[] keys =new string[parameters.Count];    parameters.Keys.CopyTo(keys,0);   // Set the StateServer collection values  for(int intKeys=0;intKeys<keys.Length;intKeys++)  {    if(keys[intKeys].Equals("database"))      stateSaver.Add("database",        parameters[keys[intKeys]].ToString());    else if(keys[intKeys].Equals("server"))      {stateSaver.Add("server",        parameters[keys[intKeys]].ToString());}    else if(keys[intKeys].Equals("username"))      {stateSaver.Add("username",        parameters[keys[intKeys]].ToString());}    else if(keys[intKeys].Equals("password"))      {stateSaver.Add("password",        parameters[keys[intKeys]].ToString());}    else if(keys[intKeys].Equals("target"))      {stateSaver.Add("target",        parameters[keys[intKeys]].ToString());}  }  // Evaluate connectionstring based on input  // can encrypt here...  string connectionstring= "Data Source=" +        stateSaver["server"].ToString() ;  connectionstring+= ";Initial Catalog=" +       stateSaver["database"].ToString() ;  if(stateSaver["username"]!=null &&     stateSaver["username"].ToString().Length!=0)  {    // Here you need to test the connection...    // and proceed    SqlConnection conn =       new SqlConnection( "Data Source=" +         stateSaver["server"].ToString() +         ";Initial Catalog=master;User Id=" +         stateSaver["username"].ToString() +         ";Password=" +         stateSaver["password"].ToString());    conn.Open();    conn.Close();    conn.Dispose();    connectionstring += ";User ID=" +       stateSaver["username"].ToString() ;    connectionstring += ";Password=" +       stateSaver["password"].ToString() ;  }  else{    // Here you need to test the connection...    // and proceed.    SqlConnection conn =      new SqlConnection( "Data Source=" +       stateSaver["server"].ToString()+       ";Initial Catalog=master;trusted_connection=yes");    conn.Open();    conn.Close();    conn.Dispose();    TrustedConnection=true;    stateSaver.Add("trustedconnection",true);    connectionstring+=";Trusted_connection=yes";}  // Set the Config file's connection string  XmlDocument doc = new XmlDocument();  doc.Load(stateSaver["target"].ToString()+    @"bin/Customer.exe.config");  XmlNode connectionNode =      doc.SelectSingleNode(    @"configuration/appSettings/" +    @"add[@key='connectionstring']");  if(connectionNode!=null)  {    connectionNode.Attributes["value"].Value =       connectionstring;    doc.Save( stateSaver["target"].ToString()+       @"bin/Customer.exe.config");    EventLog.WriteEntry("SetupDB",      "Configuration file processed...");   }  else  { // This error will ensure installation     // is uncomplete...    throw new       InstallException(      "Configuration file had no node " +      "declared for connection string.");   }  // Run the scripts  DataBaseInstaller dbInstall =null;  if(TrustedConnection)  {    dbInstall= new     DataBaseInstaller(stateSaver["server"].ToString(),      stateSaver["database"].ToString(),      stateSaver["target"].ToString());  }  else{    dbInstall= new       DataBaseInstaller(      stateSaver["server"].ToString(),      stateSaver["database"].ToString(),      stateSaver["username"].ToString(),      stateSaver["password"].ToString(),      stateSaver["target"].ToString());  }  dbInstall.CreateDataBase();  dbInstall.CreateObjects();  dbInstall.CreateRecords();  }}catch(InstallException inst){  throw (inst);}catch(Exception generic){  EventLog.WriteEntry("SetupDB",generic.Message,    EventLogEntryType.Error) ;  throw new InstallException(generic.Message);}}

    在清单 2 中,请注意方法的签名。它有一个名为 stateSaver 的 IDictionary 参数。它会在 Install、Commit、Rollback 等过程中保持该安装程序所需的信息。最初,此值为 null。只有当您调用 base.Install(StateSaver) 时,IDictionary 对象才会反映过程的当前状态。因此每个重写方法都需要调用基类,以便初始化 stateSaver。如果您在各个步骤中执行多个自定义安装程序,那么这就变得非常重要。接下来的步骤是获取通过 Custom Actions 属性 CustomActionData 传递的参数。

    StringDictionary parameters = Context.Parameters;

    这个 Context 属性非常重要,因为它可返回 System.Configuration.Install.InstallContext。甚至在执行 Install 方法之前,该安装程序就已经设置了安装程序的 Context 属性。表 2 指出了 InstallContext 的一些重要成员。

    表 2. InstallContext 的一些重要成员。
    成员说明

    LogMessage(string)

    记录指定记录器中的任何消息。该日志的路径是在安装程序的构造函数中指定的。日志文件的名称/值参数可以传递到自定义安装程序中。

    Parameters

    返回包含名称/值对的 StringDictionary。这是一个重要属性,它将返回 CustomActionData 中的命令行名称/值对。

    isParameterTrue(string)

    如果设置了指定的参数,则返回真,如果没有设置,则返回假。

    清单 2 中的第一部分代码会检索不同的键值对,并将它们设置到 stateSaver 中。其中包括用户输入的数据库值,如服务器名、数据库名、用户 ID、密码和安装的目标目录。当然,在您想要卸载时,这些详细信息中有一些是至关重要的。您需要将在卸载过程中删除的数据库名称、它的连接字符串等。因此,为了保持安装的状态,所有这些 stateSaver 值都由安装程序储存在一个名为 *.installstate 的文件中。安装后,您可以在文件夹中查看该文件。按照这种方式使用文件使您能够避开注册表。另外,当您构建真正的安装程序时,最好明智地选择使用 stateSaver。如果某个用户在安装后删除了此文件,安装程序在在卸载过程中就会遇到问题。

    清单 2 中的第二部分代码使用用户输入的值来检查数据库连接。如果用户没有输入用户 ID,此步骤就会对一个可信连接进行测试。这不是一个用来辨别集成模式验证选择或混合模式验证选择的简便方式。但是,现有用户界面编辑器中的限制又使得必须这样做。最终,此部分还会为数据库创建连接字符串。

    清单 2 中的第三部分代码可在应用程序配置文件中设置连接字符串。此部分会将配置文件加载为一个 XML 文档,遍历到 connectionstring 节点,并设置该值。请注意将该文件的路径放在一起的方式。

    清单 2 中的第四部分代码通过将运行脚本的任务委托给另一个类,来实际创建该数据库: DatabaseInstaller。

    将 DatabaseInstaller.cs(下载资料中的示例文件)复制到您的项目中。DatabaseInstaller 具有两个构造函数来传递用户输入的数据库详细信息。另外,它还有一个私有构造函数,如清单 3 所示。

    清单 3. 从嵌入式资源加载 InstallationFiles.xml。

    XmlDocument config=null;private DataBaseInstaller(){  System.IO.Stream stream =     System.Reflection.Assembly.GetExecutingAssembly().    GetManifestResourceStream(    "CustomSteps.Resources.InstallationFiles.xml");  config=new XmlDocument();  config.Load(stream);}

    此构造函数会加载包含有关这些脚本文件详细信息的 InstallationFiles.xml 文件。您还记得吧,您已经将此文件的 Build Action 显式设置为 Embedded resource。之后,您就拥有了封装创建数据库功能的各种成员。表 3 提供了有关上述内容的一些详细信息。

    表 3. DatabaseInstaller 的成员。
    成员说明

    CreateDataBase()

    调用以创建数据库的公共方法。

    DropDataBase()

    删除数据库的公共方法。

    CreateObjects()

    创建对象(如表、存储过程等)的公共方法。

    CreateRecords()

    在各种表中创建任何基础数据或配置数据。

    GetFullPath

    返回带有脚本文件完整路径的字符串的私有方法。它采用该脚本文件的名称。

    PopulateDatabaseNamePlaceHolder

    使用实际的数据库名称替换脚本文件中的占位符的私有方法。

    GetCommonProcessArguments

    创建参数的私有方法,这些参数将传递到 OSQL 以便创建数据库。

    ExecuteScripts

    对于一个给定的 XPath,此私有方法会获取脚本文件的文件名,并使用 OSQL 对它们进行递归执行。这是由 CreateObjects 和 CreateRecords 使用的。

    现在我将主要讲述 CreateDatabase() 方法,因为其他大多数方法所遵循的实现都是类似的。清单 4 提供了该方法的主要代码。

    清单 4. 数据库创建的代码片段。

    ProcessStartInfo processInfo =  new ProcessStartInfo("osql.exe");processInfo.WindowStyle=ProcessWindowStyle.Normal;// Get the name of the file from the assembly's // embedded resource.if(config !=null){  fileName = config.SelectSingleNode(    "configroot/Files/DataBase/Add/File").    Attributes["name"].Value;}else{  //Customized message..  throw new InstallException(    "Configuration for database file " +    "creation missing.");}//Get argumentsprocessInfo.Arguments=  GetCommonProcessArguments(fileName,"master");EventLog.WriteEntry("DatabaseInstaller",  processInfo.Arguments);PopulateDatabaseNamePlaceHolder(  GetFullPath(fileName));Process osql = Process.Start(processInfo);//Wait till it is done...osql.WaitForExit();EventLog.WriteEntry(   "DatabaseInstaller","Database created..");osql.Dispose();return true;

    ProcessStartInfo 可用于对您将要启动的进程(OSQL)进行更好的控制。它为您提供了一种用于设置参数、控制窗口样式等的简便方法。然后,它可以与清单 4 中所示的进程 结合使用。该进程启动之后,您可以等待它退出,也可以使用 WaitForExit 方法来控制退出时间。另外,您在构造函数中使用初始化的 XmlDocument 来选择一个预定义的节点,您在其中存储了数据库脚本文件的名称。然后,您将参数构建的过程委托给一个特定的方法 (GetCommonProcessArguments),同时传递了该脚本文件的名称以及要从中运行的数据库上下文的名称。在数据库创建过程中,您可以将 master 作为数据库传递,原因是您的应用程序数据库正处于创建过程中。下一行代码(方法 PopulateDatabaseNamePlaceHolder)使用用户提供的实际名称来替换数据库名称的占位符。

    请注意,无论何时,只要存在 try、catch 代码块或者发生了自定义异常,就会引发一个 InstallException。它的类型为 SystemException,该安装程序甚至会在 Commit、Rollback 和 Uninstall 阶段引发这种类型的异常。较好的做法是使用 EventLog 或自定义安装日志文件来跟踪并记录该错误。您可以使用 Context 属性来创建自定义日志。

    您已经为数据库创建成功自定义了安装!

    使用相似的方式,您可以重写 Installer 类的 Uninstall 方法,然后复制附带示例中的代码。此时您会看到,当用户确认时,删除数据库的任务就委托给 DatabaseInstaller 类。在接下来的步骤中,您将创建一个安装项目,并使用它进行部署。

    创建安装项目并进行安装

    最后,您要准备创建安装项目了。表 4 说明了 VS.NET 提供的各种部署方式(另请参见图 2)。

    表 4. 模板选项。
    模板说明

    Setup

    生成 MSI。通常在生成基于 Windows 的应用程序时使用。但是,您可以对其进行自定义,以满足任何形式的部署需要。

    WebSetup

    为 Web 部署创建虚拟目录。还可以使用此模板进行自定义。

    Merge Module

    它可帮助您将组件与其他应用程序共享,如 .NET Framework、MSDE 等。

    Cab

    输出一个压缩包 (.cab) 文件以便于分发。

    对于该示例应用程序,请选择一个安装项目模板,并将其命名为 Customer Setup。

    Visual Studio 提供了多种编辑器,用于创建安装应用程序。我将对其中的一些编辑器及其用法进行探究。

    图 3 显示的是文件系统编辑器。对于要传递到部署环境中的项目、文件等,您可以在这里添加来自它们的输出。您还可以选择专门的文件夹作为您文件的目标,如 System 文件夹、Program Files 文件夹等。

    在 Application 文件夹下创建两个子文件夹,将其分别命名为 bin 和 Install。然后选择 bin 文件夹,右键单击,并选择“添加”|“项目输出”,以获得如图 4 所示的对话框。

    在组合框中选择“Customer”项目,然后选择 Primary outputContent files。主输出表示项目输出,内容文件将包含标记为 Build Action 内容的文件。这就是 Build Action 属性非常关键的位置。您还可以复制源文件,但是此示例无需进行复制。

    转至 Install 文件夹,以及 CustomSteps 中的主文件和内容文件。

    现在您已经完成了向部署添加提交内容的过程。下一步就是设置用于安装的用户界面。

    用户界面编辑器默认情况下,您可以在用户界面编辑器中看到一组界面,如 target location 等。但是,对于这个应用程序,您需要捕获四个参数:服务器名、数据库名、用户 ID 和密码。右键单击“Start”部分,然后右键单击“Add Dialog”。选择为您提供四个参数的 Textboxes (A) 选项,并按图 5 所示确定它的位置。此时,您可以配置表 5 中所示的属性。 表 5. Textboxes (A) 对话框的重要属性。

    属性说明

    BannerText

    要为该屏幕显示的标志。

    Edit1Label

    第一个文本框的标签。设置为 Server Name。查找其他标签属性,并相应地进行设置。

    Edit1Property

    该属性非常重要:此处提供的名称要在 Custom Actions 中访问。将该属性设置为 SERVER_NAME。同样地,为示例中显示的其他属性设置 DATABASE_NAMEUSER_NAMEPASSWORD

    Edit1Value

    要设置为默认值的值。您可以指定可用的默认属性。例如,[ComputerName] 会将此文本框的默认值设置为用户计算机的名称。请检查示例应用程序中设置的值,以了解更多信息。

    Edit1Visible

    此值有助于您控制文本框的可见性。对于此示例,所有属性均为真。

    用户界面编辑器还有一些别的内容。您可以研究它的其他各种对话、它们的属性,以及一些可用于真正扩展您的自定义的默认 Windows 属性。但是,对于此示例,我就需要这些,所以我要转而说明启动条件编辑器了。

    启动条件编辑器

    在某些情况下,您的部署要迎合特定的运行库,如 .NET Framework、MSDE、MDAC 版本等。如果您没有什么先决系统要求,则没有要指出的内容,继续进行安装就可以了。启动条件编辑器可帮助您完成此任务。在此示例中,除了现有的默认“.NET Framework”条件之外,我还要添加一个条件。单击启动条件编辑器,然后右键单击“Search Target Machine”部分。在这里,您可以评估某个特定的条件。假设此安装程序是面对 Windows OS 4 和更高版本的。尽管可以使用默认的 Windows 条件,但是我要为您讲述如何创建自己的条件。选择 Add file registry search,然后按照图 6 所示设置这些值。

    Property 会为您的搜索评估指定一个名称。RegKey 是要搜索的目标关键字,Root 则是要搜索的注册表树的根。这些设置会搜索 Value 集合的 RegKey 路径(例如 CurrentVersion 值名称)。此搜索会返回它在 CurrentVersion 值名称中找到的任何值。

    右键单击“Launch Conditions”,然后添加一个新条件。图 7 提供了此条件的设置。请注意 Condition 属性。此属性应该计算为真,安装才能继续。在这里,您提供了 WINDOWSVERSION>= "4.0"。WINDOWSVERSION 是注册表搜索条件的名称。如果返回假,则安装程序会显示 Message 属性中配置的错误消息,并中止安装。您可以按照这种方式配置各种条件,以使您的部署具有条件。

    在下一部分中,您将看到如何使用自定义生成的 CustomSteps 安装程序。

    自定义操作编辑器您可以添加属于部署一部分的任何 EXE、脚本文件或 DLL 文件。在此示例中,您需要添加的只是 CustomSteps 类库。您可以在 Install 阶段添加此类库。为此,请右键单击“Install”部分,然后选择“Add Custom Action”。然后,遍历 Applications 文件夹/Install,然后选择主输出,如图 8 所示。将该步骤重命名为 Install database

    图 9 显示了为该 Custom Action 配置的属性。这些属性中最重要的是 Condition 和 CustomActionData。Condition 可确保此安装程序只有在满足特定条件时才运行。CustomActionData 则是向 CustomAction EXE/DLL 传递参数。

    在此示例中,我按照下列方式设置了这些属性:

    /server=[SERVER_NAME] /target="[TARGETDIR]/" /database=[DATABASE_NAME] /username=[USER_NAME] /password=[PASSWORD] /version=[WINDOWSVERSION]

    请注意,设置 Windows 默认属性(如 TARGETDIR)与设置用户界面编辑器或启动条件编辑器中您自己创建的属性(如 SERVER_NAME 和 WINDOWSVERSION)之间的区别。但是,通用格式为 /name=[value],后面紧跟反斜杠,具体取决于属性。另外,请注意 InstallerClass 属性值。此处传递的参数将用作 Context.Parameters。您可以看到这些参数在 CustomSteps 组件中是如何使用的。当您运行该安装程序时,自定义安装程序的 Install 方法会在复制了所有部署文件之后被调用。

    您又按照类似方法添加了 Uninstall 阶段的 Custom Action,并将其重命名为 Uninstall database。但是,这不需要任何参数。必需的参数将使用 stateSaver 集合提供。这就是为什么在安装过程中创建的 *.installstate 文件非常重要的原因了。您可以在 Install 文件夹中看到该文件。stateSaver 是使用此文件初始化的,因此建议您在安装过程中向 stateSaver 添加卸载操作所需的所有这些值。

    根据您的方案,还可以使用其他一些编辑器,如文件类型编辑器和注册表编辑器。但是,对于此示例,并不需要这些编辑器。

    您现在就可以进行安装了。生成解决方案和安装程序项目,然后进行安装和卸载,以此进行测试。

    小结

    对于可以使用 VS.NET 中的安装项目来实现实施的高级概念来说,此处讲述的这个过程只是冰山一角。很重要的一点是,要根据您自己的应用程序要求来分析和自定义您的部署。在此示例中,您看到了文件的 Build Action 属性的用法。

    请尝试减少注册表项的使用,并处理好最差的情况。请对于所有可能的方案情况(如已删除的了 *.installstate 文件)测试您的安装程序,并尝试以尽可能好的方式来处理这些方案。

    另外,在此示例中,我从来没有检查过活动数据库连接。这在卸载过程中是非常重要的。对于应用程序的卸载的重视程度应该给予与安装一样重要的重视程度,因为您肯定会要再次重新安装该应用程序。

    如果现在的 VS.NET 安装编辑器有限制的话,那么这个限制也会存在于用户界面编辑器中。我相信对于将来版本的 VS.NET 来说,最佳的解决方案是为开发人员提供一个可在设计时添加自己的用户界面的选项,并且带有用于对话框的条件选项。但是,目前您仅限于了 Custom Actions。

    异常处理也非常重要,因为总是应该在回滚/卸载之后保持安装前的状态。

    不要对于目标运行库时环境做作任何假设,。而是始终总是要使用启动条件来验证是否存在您需要的每项内容是否存在。

    最后,请一定要生成适合于您的应用程序的安装程序。此过程值得您花费项目时间和成本。

     
    展开全文
  • 自定义MSI 安装

    千次阅读 2005-03-24 22:57:00
    Vishnu Prasad H 探究了安装项目模板、编辑器、自定义安装程序以及更多内容。然后,他将所有内容放在一起,组成了一个部署数据库应用程序的示例。 与以前相比,现在部署 .NET 应用程序非常简单。现在 xcopy
  • 首先需要创建一个set up项目,后面需要该安装项目的“自定义安装”Editor对话框中设置自定义操作。 2.然后,我们要在项目中新增加一个项目(选择“类库(.net framework)”,这个是为了生成以后
  • 如何自定义msi安装程序

    千次阅读 2009-01-23 10:20:00
    VS2008和VS2005都提供了比较方便的安装程序工程,并且在工程中可以添加自定义的User Interface和自定义事件。但是VS2008提供的User Interface只限于集中很固定的Dialog(关于如何生成自定义的Dialog模板,请参考...
  • 在没有Votive的情况下为用托管代码编写的WIX创建自定义动作
  • Visual Studio Setup Project是制作msi安装程序msi文件的 1 安装程序在安装的时候总是希望先关闭正在运行的当前版本 2 安装程序在卸载的时候总是希望先关闭正在运行的当前版本 这个博客写的是比较详细的:...
  • 有时候我们需要在程序中执行另一个程序的安装,这就需要我们去自定义msi安装包的执行过程。 比如我要做一个安装管理程序,可以根据用户的选择安装不同的子产品。当用户选择了三个产品时,如果分别显示这三个产品...
  • (转载)自定义 MSI 安装

    2006-08-23 13:10:00
    自定义 MSI 安装 发布日期: 2/3/2005 | 更新日期: 2/3/2005 Vishnu Prasad H 本页内容 部署示例 创建示例应用程序 ...
  • 在制作安装包时用vbs来执行自定义操作需要做的有(本人的项目):1、在自定义操作---->提交----->右键添加自定义操作,将写好的vbs加载进来。2、右键设置vbs的一个属性CustomActionData为[TARGETDIR](这里是为了...
  • 自定义操作是控制打包部署的钥匙.怎样调试你的自定义操作代码呢? 使用下列方法之一: 第一.内部发消息 •在您的代码中添加对 System.Diagnostics.Debugger.Launch() 的调用。该方法会打开实时调试,并允许您将...
  • 用户如果通过InstallShield自带的Project Assistant创建一个Basic MSI类型的工程,默认的安装界面只有欢迎界面,License协议界面,用户信息界面,安装路径选择界面,自定义安装界面,以及安装结束界面,但安装需求...
  • 用户如果通过InstallShield自带的Project Assistant创建一个Basic MSI类型的工程,默认的安装界面只有欢迎界面,License协议界面,用户信息界面,安装路径选择界面,自定义安装界面,以及安装结束界面,但安装需求...
  • 之前写了Visual Studio创建安装程序包 - 文件系统主要是对目标计算机目录方面的内容,现在补上自定义操作以及对话框等内容。 通常会看某些安装程序不是直接点下一步、下一步就可以完成的,过程中需要输入一些信息,...
  • 相同的方式运行自定义操作。 自己试了下,试了下第1中方法比较简单,把System.Diagnostics.Debugger.Launch();添加到安装类中,下面两种不晓得怎么弄。上图 转自: ...
  • 支持18钟类型,分别是CODE39,CODE39EXT,INTERLEAVED25,CODE11,CODABAR,MSI,UPCA,IND25,MAT25,CODE93,EAN13,EAN8,UPCE,CODE128,CODE93EXT,POSTNET,PLANET,UCC128 . code: 要打印的条码内容. width(默认为...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,110
精华内容 3,644
关键字:

msi自定义操作