1、解决方案里有两个项目 一个是类库 生成dll 一个是winform的 调用我的类库 现用visual studio 2005的安装项目 生成setup.exe和setup.msi 可以安装 但是如何在安装完成后就立即自动运行我的winform呢
在解决方案里新建一个项目 是类库 然后在类库里添加新项(安装程序类) 在类里重载comit方法 这个方法在安装完成时调用(表现出来是在安装最后点完成之前调用的)
public override void Commit(System.Collections.IDictionary savedState)
{
base.Commit(savedState);
string path = this.Context.Parameters["Path"]+"MyConstructor.exe";//
System.Diagnostics.Process.Start(path);
//这个Context.Parameters["Path"]是在安装部署项目里自定义操作的属性中通过设置CustomActionData属性为/Path="[TARGETDIR]/" 就把用户选择的路径用Path这个变量返回了
}
把这个类库的项目输出添加到部署方案里 放到应用程序文件夹里 然后在自定义操作那的提交节点添加自定义操作 目标就选该类库的输出 这时生成安装项目 发现安装完毕后找不到InsClass.InstallState文件 于是百度了一下 发现要把该自定义操作在安装节点也要添加一份 说是在那个阶段生成这个InstallState文件 然后就基本能做到安装完毕后运行程序了
2、
Visual Studio安装项目学习笔记
项目组要开发一个插件,schedule很紧,安装程序由我负责。本来是想用NSIS来做的,原型都做好了,上头却以各种各样的理由否决了,于是只剩下3个途径(只考虑免费的或者已经有license的工具)——WiX(Windows Install XML)、InstallShield和Visual Studio安装项目。WiX很强大,用起来本应是和NSIS差不多的(除了一个是用XML另一个是用脚本语言),但前者因缺乏各种辅助工具、插件、sample code而显得难以上手,希望WixEdit和WiX 3.0会以后完善点吧。从零开始学WiX还不如学InstallShield,因为后者不少同事懂,不过据说bug比较多。从schedule考虑只能用VS了,customizability差一些,以后有时间的话学了InstallShield可以考虑重做。
于是开始一边上网查资料一边开发原型,发现VS安装项目还是不错的,WYSIWYG就是方便,WYSIAYG的缺陷可以通过编程和修改 MSI文件来克服。下面说明如何实现(仅作备考,都是些很基础的操作)
一、需求
要安装的主要是一个用C#(Visual Studio 2005)开发的系统服务,安装程序除了拷贝文件之外要做的是:检测并安装.NET Framework 2.0;安装完毕后启动该系统服务;一些快捷方式和注册表项创建;在遇到错误的时候产生log输出。
二、建立安装项目
在与主项目相同的Solution下建立一个Setup Project(在Setup and Deployment类别下),右击该项目点选Add→Project Output...加入主项目的输出项。安装项目的属性中除了一些显示信息之外有几个比较重要——DetectNewerInstalledVersion、 RemovePreviousVersions、Version、UpgradeCode和ProductCode,前两个就不说明了,后面三个和安装卸载程序有很大关系。一般而言,同一个项目的不同版本应该保持相同的UpgradeCode和不同的ProductCode、Version,在改Version后VS会自动提示生成新 ProductCode,所以不会漏掉。
加入了项目输出之后,右击安装项目点选View→Launch Conditions可以发现安装程序已经可以检测.NET Framework了,该项的 AllowLaterVersions属性可以根据需要修改。
右击安装项目选择属性后点击Prerequisites...按钮,可以看见.NET Framework 2.0已经勾选了,我把Windows Installer 3.1 也顺便勾上。下面可以选择是把这些必备程序集成到安装包中还是让用户从网上下载。
三、安装界面
右击安装项目点选View→User Interface可以修改安装对话框,不过自定义程度实在是很低……比如说我想加个对话框让用户输入用户名和密码,却发现只有明文的text box。网上有人提供修改VS资源的解决办法,这显然不好,像我们公司的项目都是在特定的机器(Build Master)上生成的,总不能改那上面的资源吧。另一个方法就是修改生成的MSI文件,在Aaron Stebner的这篇post上可以找到一个在安装完成对话框中加入“马上运行该程序”勾选框的javascript脚本,其原理是通过MSI的类似SQL数据库操作修改其中的数据。至于MSI里有什么数据可以用Orca查看,找到需要改的项后修改那个javascript脚本,最后把这个脚本放在VS项目的 PostBuildEvents里运行。不过后来因为需要输入的东西太多,在安装程序里输入不大好,改为安装完毕后运行一个config程序来做了。
四、注册表
类似的,右击安装项目点选View→Registry可以定义注册表操作,可以设置是否在卸载后删除,蛮方便的。
五、自定义操作
安装了生成的MSI之后就会在控制面板的添加删除程序中加入卸载项,但开始菜单里什么都没有,要在程序项里加入卸载快捷方式有3个方法:
1. 建一个批处理文件(可以再wrap成exe)执行"msiexec /x "然后建立快捷方式(右击安装项目点选View→File System处理)
2. 在system32目录拷个msiexec.exe(可以改名成uninstall.exe之类的)然后建立快捷方式(Arguments项添加"/x [ProductCode]" )
3. 编程实现添加指向msiexec.exe的快捷方式
我选择第3种方式,也就是使用Custom Actions,具体方法是在主项目的系统服务主类的Design视图中右击空白部分选择Add Installer。生成一个System.Configuration.Install.Installer的子类ProjectInstaller,打开其Design视图可以看见两个 component,分别设置相应属性。当然也可以用一般的方式生成Installer子类——右击项目添加Installer类,然后在构造方法中构造 System.ServiceProcess.ServiceProcessInstaller和System.ServiceProcess.ServiceInstaller组件。
在安装项目View→Custom Actions里的Custom Actions根节点中添加有Installer子类的项目主输出(可以看见每个Custom Action的Installer Class属性都自动设成True)后,就可以重写以下的方法实现自定义操作:
void Install(System.Collections.IDictionary)
void Commit(System.Collections.IDictionary)
void Rollback(System.Collections.IDictionary)
void Uninstall(System.Collections.IDictionary)
void OnBeforeInstall(System.Collections.IDictionary)
void OnAfterInstall(System.Collections.IDictionary)
void OnBeforeRollback(System.Collections.IDictionary)
void OnBeforeRollback(System.Collections.IDictionary)
void OnBeforeUninstall(System.Collections.IDictionary)
void OnAfterUninstall(System.Collections.IDictionary)
void OnCommitting(System.Collections.IDictionary)
void OnCommitted(System.Collections.IDictionary)
比如在安装完毕后启动服务就可以通过重写OnAfterInstall方法实现。添加卸载程序快捷方式需要在Install和Uninstall两个Custom Action的CustomActionData属性里输入“/productcode=[ProductCode] /productname= [ProductName]”,并在项目中添加对Windows Script Host Object Model的Reference以及using IWshRuntimeLibrary,代码(参考了这篇post)如下:
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
System.Collections.Specialized.StringDictionary parameters = this.Context.Parameters;
string pc = parameters["productcode"];
string pn = parameters["productname"];
string programShortcutFolder = String.Format(@"{0}/{1}",
Environment.GetFolderPath(Environment.SpecialFolder.Programs),
pn
);
WshShell shell = new WshShell();
if (!Directory.Exists(programShortcutFolder))
Directory.CreateDirectory(programShortcutFolder);
IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(
programShortcutFolder + @"/Uninstall.lnk"
);
shortcut.TargetPath = Environment.GetFolderPath(Environment.SpecialFolder.System) + @"/msiexec.exe";
shortcut.WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.System);
shortcut.WindowStyle = 1;
shortcut.Description = "Description blah blah blah";
shortcut.IconLocation = Environment.GetFolderPath(Environment.SpecialFolder.System) + @"/msiexec.exe";
shortcut.Arguments = "/x " + pc;
shortcut.Save();
}
public override void Uninstall(System.Collections.IDictionary savedState)
{
base.Uninstall(savedState);
System.Collections.Specialized.StringDictionary parameters = this.Context.Parameters;
string pn = parameters["productname"];
string programShortcutFolder = String.Format(@"{0}/{1}",
Environment.GetFolderPath(Environment.SpecialFolder.Programs),
pn
);
if (Directory.Exists(programShortcutFolder))
Directory.Delete(programShortcutFolder,true);
}
六、遗留问题
● 虽然可以通过重写Installer的方法来输出log,但这样太局限了,如果有什么其他问题可能要通过运行“setup /l*vx install.log”来查看MSI安装log。
● 默认的卸载界面过于简单,我想在最后显示一个对话框提示已成功卸载,却找不到获取Installer当前Window对象的方法,哪位同学有什么好办法?
P.S. 我一开始没想到向Custom Action传参数,想通过读sln和vdproj文件来获取ProductCode和ProductName,找到了一下几篇文章,还蛮好玩的:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2088820&SiteID=1
http://forums.microsoft.com/msdn/showpost.aspx?postid=59504&siteid=1
http://msdn2.microsoft.com/en-us/library/ms228772(VS.80).aspx
如果哪位同学刚好遇到以下异常的话可以参考一下
System.Runtime.InteropServices.COMException was unhandled
ErrorCode=-2147418111
Message="Call was rejected by callee. (Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED))